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;
}