Course Schedule 课程表

现在你总共有 n 门课需要选,记为 0 到 n-1

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们: [0,1]

给定课程总量以及它们的先决条件,判断是否可能完成所有课程的学习?

示例 1:

输入: 2, [[1,0]] 
输出: true
解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。

示例 2:

输入: 2, [[1,0],[0,1]]
输出: false
解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

说明:

  1. 输入的先决条件是由边缘列表表示的图形,而不是邻接矩阵。详情请参见图的表示法
  2. 你可以假定输入的先决条件中没有重复的边。

提示:

  1. 这个问题相当于查找一个循环是否存在于有向图中。如果存在循环,则不存在拓扑排序,因此不可能选取所有课程进行学习。
  2. 通过 DFS 进行拓扑排序 - 一个关于Coursera的精彩视频教程(21分钟),介绍拓扑排序的基本概念。
  3. 拓扑排序也可以通过 BFS 完成。

这道题的思路是找图中是否存在环结构,如果存在环结构,就返回false,否则返回true,所以这道题简化成在找寻有向图是否存在环结构,我们使用邻接表来表示图结构,如果存在指向u到v的一条边,意味着v的课程需要先修u,使用vector<unordered_set<int>> graph来保存图结构。

思路一:BFS,步骤为:首先构建图结构,然后寻找每个节点的入度(indegree),找寻入度为0的节点,如果找不到直接返回false(因为存在圆环),然后把这个节点的邻接点的入度都减一,再寻找每个节点的入度是否为0,一直重复以上操作,如果没有返回false,最后返回true,证明图中没有圆环。

参考代码:

class Solution {
public:
vector<unordered_set<int>> make_graph(int numCourses, vector<pair<int, int>>& prerequisites) {
	vector<unordered_set<int>> graph(numCourses);
	for (auto item : prerequisites) {
		graph[item.second].insert(item.first);
	}
	return graph;
}
vector<int> calculate_indegree(vector<unordered_set<int>> &graph) {
	vector<int> indegree_node(graph.size());
	for (auto neibors : graph) {
		for (auto neibor : neibors) {
			indegree_node[neibor]++;
		}
	}
	return indegree_node;
}
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
	vector<unordered_set<int>> graph = make_graph(numCourses, prerequisites);
	vector<int> indegree_node = calculate_indegree(graph);
	for (int i = 0; i < numCourses; i++) {
		int j = 0;
		for (; j < numCourses; j++) {
			if (indegree_node[j]==0) break;
		}
		if (j == numCourses) return false;
		indegree_node[j] = -1;
		for (auto neibor : graph[j]) {
			indegree_node[neibor]--;
		}
	}
	return true;
}
};

思路二:DFS,采用深搜的思路有点不一样,每次寻找一个节点,然后对其邻接点的每一个节点再次寻找其周围的邻接点,如果发现这个节点已经在之前找过了,证明有环,返回false,否则一直遍历结束。这里其实是对每一个节点(startVertex)都采用dfs,所以我们定义两个bool数组来保存:1 visited数组,迄今为止遍历到的所有节点的路径(即节点是否被访问过);2 onPath数组,保存每一次dfs的节点路径。

对于visited数组,只会被赋值true,一旦这个节点被赋值为true,意味着这个节点被访问过且经过这个节点的路径一定无环(因为我们是通过onPath数组来寻找是否有环的,和visited无关)

对于onPath数组,在每次dfs访问到节点时被赋值为true,退出栈时又被赋值为false(相当于对于下一次起始节点startVertex的dfs而言onPath全是false,onPath只对每一次dfs负责)

参考代码:

class Solution {
public:
vector<unordered_set<int>> make_graph(int numCourses, vector<pair<int, int>>& prerequisites) {
	vector<unordered_set<int>> graph(numCourses);
	for (auto item : prerequisites) {
		graph[item.second].insert(item.first);
	}
	return graph;
}
bool has_cycle(vector<unordered_set<int>> &graph, vector<int> &visited, vector<int> &onPath,int startVertex) {
	if (visited[startVertex]) return false;
	visited[startVertex] = onPath[startVertex] = true;
	for (auto neibor : graph[startVertex]) {
		if (onPath[neibor] || has_cycle(graph,visited,onPath,neibor)) {
			return true;
		}
	}
	onPath[startVertex] = false;
	return false;
}
bool canFinish(int numCourses, vector<pair<int, int>>& prerequisites) {
	vector<unordered_set<int>> graph = make_graph(numCourses, prerequisites);
	vector<int> visited(numCourses, false), onPath(numCourses, false);
	for (int i = 0; i < numCourses; i++) {
		if (!visited[i] && has_cycle(graph,visited,onPath,i)) {
			return false;
		}
	}
	return true;
}
};

猜你喜欢

转载自blog.csdn.net/qq_26410101/article/details/82759042