1.Kahn
算法Kahn 算法实际上用的是贪心算法思想,思路非常简单、好懂。定义数据结构的时候,如果 s 需要先于 t 执行,那就添加一条 s 指向 t 的边。所以,如果某个顶点入度为 0, 也就表示,没有任何顶点必须先于这个顶点执行,那么这个顶点就可以执行了。
我们先从图中,找出一个入度为 0 的顶点,将其输出到拓扑排序的结果序列中(对应代码中就是把它打印出来),并且把这个顶点从图中删除(也就是把这个顶点可达的顶点的入度都减 1)。我们循环执行上面的过程,直到所有的顶点都被输出。最后输出的序列,就是满足局部依赖关系的拓扑排序。
我把 Kahn 算法用代码实现了一下,你可以结合着文字描述一块看下。不过,你应该能发现,这段代码实现更有技巧一些,并没有真正删除顶点的操作。代码中有详细的注释,你自己来看,我就不多解释了。
public void topoSortByKahn() {
int[] inDegree = new int[v]; // 统计每个顶点的入度
for (int i = 0; i < v; ++i) {
for (int j = 0; j < adj[i].size(); ++j) {
int w = adj[i].get(j); // i->w
inDegree[w]++;
}
}
LinkedList<Integer> queue = new LinkedList<>();
for (int i = 0; i < v; ++i) {
if (inDegree[i] == 0) queue.add(i);
}
while (!queue.isEmpty()) {
int i = queue.remove();
System.out.print("->" + i);
for (int j = 0; j < adj[i].size(); ++j) {
int k = adj[i].get(j);
inDegree[k]--;
if (inDegree[k] == 0) queue.add(k);
}
}
}
2.DFS
图上的深度优先搜索我们前面已经讲过了,实际上,拓扑排序也可以用深度优先搜索来实现。不过这里的名字要稍微改下,更加确切的说法应该是深度优先遍历,遍历图中的所有顶点,而非只是搜索一个顶点到另一个顶点的路径。关于这个算法的实现原理,我先把代码贴在下面,下面给你具体解释。
public void topoSortByDFS() {
// 先构建逆邻接表,边s->t表示,s依赖于t,t先于s
LinkedList<Integer> inverseAdj[] = new LinkedList[v];
for (int i = 0; i < v; ++i) { // 申请空间
inverseAdj[i] = new LinkedList<>();
}
for (int i = 0; i < v; ++i) { // 通过邻接表生成逆邻接表
for (int j = 0; j < adj[i].size(); ++j) {
int w = adj[i].get(j); // i->w
inverseAdj[w].add(i); // w->i
}
}
boolean[] visited = new boolean[v];
for (int i = 0; i < v; ++i) { // 深度优先遍历图
if (visited[i] == false) {
visited[i] = true;
dfs(i, inverseAdj, visited);
}
}
}
private void dfs(
int vertex, LinkedList<Integer> inverseAdj[], boolean[] visited) {
for (int i = 0; i < inverseAdj[vertex].size(); ++i) {
int w = inverseAdj[vertex].get(i);
if (visited[w] == true) continue;
visited[w] = true;
dfs(w, inverseAdj, visited);
} // 先把vertex这个顶点可达的所有顶点都打印出来之后,再打印它自己
System.out.print("->" + vertex);
}
207. 课程表
1.Kahn 算法实现
有向图可以描述一种依赖关系
该算法将有向图中指向一个点的边,转变成该点的入度,用indgree数组存储。
入度为0的点,是起始点,也就是有向图入口,没有其他点指向它,没有任何点先于这个点执行。
for(int i=0;i<n;i++){
if(!indegree[i])queue[rear++]=i; //入度为0,全部要加进队列中
}
从入度为0的点进入图,删除从它出发的边,也就是入度值减一
if(prerequisites[i][1]==t){
int tmp=prerequisites[i][0];
indegree[tmp]--;
题目要求:想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]。 也就是prerequisited[i][1]先于prerequisited[i][0]执行。
bool canFinish(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize){
int n=numCourses;
int count=numCourses;
int *indegree=malloc(sizeof(int)*n);
memset(indegree,0,sizeof(int)*n);
for(int i=0;i<prerequisitesSize;i++){
int tmp=prerequisites[i][0];
indegree[tmp]++;
}
int *queue=malloc(sizeof(int)*(n+2));
int rear=0,front=0;
for(int i=0;i<n;i++){
if(!indegree[i])queue[rear++]=i; //入度为0,全部要加进队列中
}
while(rear!=front){
int t=queue[front++];
count--;
for(int i=0;i<prerequisitesSize;i++){
if(prerequisites[i][1]==t){
int tmp=prerequisites[i][0];
indegree[tmp]--;
if(indegree[tmp]==0)queue[rear++]=tmp; //新出现入度为零的点,也加进队列中
}
}
}
//判断有没有入度不为0的点,证明有环
// for(int i=0;i<n;i++){
// if(indegree[i])return false;
// }
//return true;
return count==0;
方法二:深度优先遍历 DFS
原理是通过 DFS 判断图中是否有环。
算法流程:
借助一个标志列表 flags,用于判断每个节点 i (课程)的状态:
未被 DFS 访问:i == 0;
已被其他节点启动的 DFS 访问:i == -1;
已被当前节点启动的 DFS 访问:i == 1。
对 numCourses 个节点依次执行 DFS,判断每个节点起步 DFS 是否存在环,若存在环直接返回 FalseFalse。DFS 流程;
终止条件:
当 flag[i] == -1,说明当前访问节点已被其他节点启动的 DFS 访问,无需再重复搜索,直接返回 TrueTrue。
当 flag[i] == 1,说明在本轮 DFS 搜索中节点 i 被第 22 次访问,即 课程安排图有环 ,直接返回 FalseFalse。
将当前访问节点 i 对应 flag[i] 置 11,即标记其被本轮 DFS 访问过;
递归访问当前节点 i 的所有邻接节点 j,当发现环直接返回 FalseFalse;
当前节点所有邻接节点已被遍历,并没有发现环,则将当前节点 flag 置为 -1−1 并返回 TrueTrue。
若整个图 DFS 结束并未发现环,返回 TrueTrue。
复杂度分析:
时间复杂度 O(N + M)O(N+M): 遍历一个图需要访问所有节点和所有临边,NN 和 MM 分别为节点数量和临边数量;
空间复杂度 O(N + M)O(N+M): 为建立邻接表所需额外空间,adjacency 长度为 NN ,并存储 MM 条临边的数据。
作者:jyd
链接:https://leetcode-cn.com/problems/course-schedule/solution/course-schedule-tuo-bu-pai-xu-bfsdfsliang-chong-fa/
struct adj {
int val;
struct adj* next;
};
struct adj** createGraph(int numCourses, int** prerequisites, int prerequisitesSize) {
struct adj** adjs = (struct adj**)malloc(sizeof(struct adj*) * numCourses);
for (int i = 0; i < numCourses; i++) {
adjs[i] = NULL;
}
for (int i = 0; i < prerequisitesSize; i++) {
int id = prerequisites[i][1];
struct adj*node= (struct adj*)malloc(sizeof(struct adj));
node->val= prerequisites[i][0];
node->next=adjs[id];
adjs[id] = node;
}
return adjs;
}
void freeGraph(int numCourses, struct adj** adjs) {
struct adj* p;
struct adj* c;
for (int i = 0; i < numCourses; i++) {
p = adjs[i];
while (p) {
c = p->next;
free(p);
p = c;
}
}
free(adjs);
free(p);
free(c);
}
bool dfs(int numCourses, struct adj** adjs, int* visited, int i) {
if (visited[i] == 1)return false;
if(visited[i] == -1) return true;
visited[i] = 1;
struct adj* p = adjs[i];
while(p!=NULL){
if(!dfs(numCourses, adjs, visited, p->val)){
return false;
}
p = p->next;
}
visited[i] = -1;
return true;
}
bool canFinish(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize)
{
struct adj**adjs=createGraph(numCourses, prerequisites, prerequisitesSize);
int* visited = (int*)malloc(sizeof(int) * numCourses);
memset(visited, 0, sizeof(int) * numCourses);
for(int i=0;i<numCourses;i++){
if (!dfs(numCourses, adjs, visited, i)) {
freeGraph(numCourses, adjs);
free(visited);
return false;
}
}
freeGraph(numCourses, adjs);
free(visited);
return true;
}