【数据结构】AOV网——拓扑排序

相关概念

AOV网

AOV网(Activity On Vertex Network)用顶点表示活动。边是无权的,仅仅用来表示前驱与后继关系。

前驱与后继

有向边的起点称为终点的前驱,有向边的终点称为起点的后继。拓扑排序的关注点在于前驱——一个结点的前驱结点全部被访问过后,该结点才能被访问。

拓扑序列

对一个有向无环图G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边∈E(G),则u在线性序列中出现在v之前。

 
>_< 看个图理解一下上面的定义吧!!!

【数据结构】AOV网——拓扑排序_第1张图片

 
 
 

拓扑排序算法(DFS)

▼ 课程表是否合理(是否存在拓扑序列?有向图是否有环?)

【DFS判环】
 思路: 在DFS过程中标记染色,如果出现闭环立即判错
 使用一个visted[]数组(我更喜欢称其为draw数组):
     	*  0   表示未被访问过
     	*  1   表示正在访问中,即在"本次"DFS路径中正在被访问,当再次访问到该处,说明出现闭环
     	* -1   表示"绝对安全",即某次DFS直到结束也没有出现闭环,当再次访问到该处,直接可以认为从该处开始没有闭环


private boolean hasLoop(List<List<Integer>> graph, int n) {
	// 多从DFS共用一个draw数组
	int[] draw = new int[n];
	// 考虑到图可能不完全连通,需要从每个点开始一次DFS
	for (int i = 0; i < n; i++) {
		if (!DFS(graph, i, draw)) {
			return false;
		}
	}
	return true;
}

/**
 * 0  表示未被访问过
 * 1  表示正在访问中,即在本次DFS路径中正在被访问,当再次访问到该处,说明出现闭环
 * -1 表示绝对安全,即某次DFS直到结束也没有出现闭环,当再次访问到该处,直接可以认为从该处开始没有闭环
*/
private boolean DFS(List<List<Integer>> graph, int cur, int[] draw) {
	if (draw[cur] == 1) {               // 出现闭环,直接判false
		return false;
	}
	if (draw[cur] == -1) {              // 从该点开始绝对安全,直接判true
		return true;
	}
	draw[cur] = 1;                      // cur点标记为正在本次DFS路径中
	for (int nxt : graph.get(cur)) {
		if (!DFS(graph, nxt, draw)) {
			return false;
		}
	}
	draw[cur] = -1;                     // 从cur开始的DFS彻底结束,标记cur点绝对安全
	return true;
}

▼ 排出一个合理的课程表(生成拓扑序列)

【DFS生成拓扑序列】
 由上面的DFS代码转化而来:
	 1. 用一个成员变量记录是否有环
	 2. 用一个栈结构存放拓扑序列
	 3. 每次判定出一个结点"绝对安全"时,就可以入栈。


private int[] topologicalSort(List<List<Integer>> graph, int n) {
	// 多从DFS共用一个draw数组
	int[] draw = new int[n];
	// 考虑到图可能不完全连通,需要从每个点开始一次DFS
	for (int i = 0; i < n; i++) {
		DFS(graph, i, draw);
	}
	// 取出拓扑序列
	if (hasLoop) {
		return new int[0];
	} else {
		int[] res = new int[n];
		int index = 0;
		while (!stack.isEmpty()) {
			res[index] = stack.pop();
		}
		return res;
	}
}

private ArrayDeque<Integer> stack = new ArrayDeque<>();     // 用栈记录拓扑序列
private boolean hasLoop = false;                            // 是否有环

private void DFS(List<List<Integer>> graph, int cur, int[] draw) {
	if (draw[cur] == 1) {
		hasLoop = true;
		return;
	}
	if (draw[cur] == -1) {
		return;
	}
	draw[cur] = 1;
	for (int nxt : graph.get(cur)) {
		DFS(graph, nxt, draw);
	}
	draw[cur] = -1;
	stack.push(cur);    // 绝对安全后,cur入栈
}

 
 
 

拓扑排序算法(BFS)

▼ 课程表是否合理(是否存在拓扑序列?有向图是否有环?)

【BFS判环】
 思路: 使用degree[]入度数组,一个结点入度为0说明它的前驱结点都已完成
      	1. 初始化入度数组
      	2. 入度为0的结点先入队
      	3. 开始BFS,每取出一个结点即学习完一个结点,将它指向的所有结点入度减1
      	4. 如果入度减一后变为0,则该结点入队
      	5. 每次进行入队操作时,便计数+1,如果最后入队的次数等于总结点数,说明所有结点都已完成,无环


private boolean hasLoop(List<List<Integer>> graph, int n) {

	// 初始化入度数组
	int[] degree = new int[n];
	for (int i = 0; i < n; i++) {
		for (int j : graph.get(i)) {
			degree[j]++;
		}
	}

	// 初始化队列(将入度为0的结点加入队列中)
	ArrayDeque<Integer> queue = new ArrayDeque<>();
	for (int i = 0; i < n; i++) {
		if (degree[i] == 0) {
			queue.offer(i);
		}
	}

	// BFS,每取出一个结点即学习完一个结点,将它指向的所有结点入度减1
	int count = 0;
    while (!queue.isEmpty()) {
		int cur = queue.poll();
			for (int nxt : graph.get(cur)) {
				degree[nxt]--;
				if (degree[nxt] == 0) {
					queue.offer(nxt);
					count++;
				}
			}
	}

	// 如果所有结点都被访问过(学习过),则说明存在拓扑序列
	return count == n;
	
}

▼ 排出一个合理的课程表(生成拓扑序列)

【BFS判环】
 由上面的BFS代码转化而来
	1. 用一个暂存队列记录拓扑序列(别和DFS用的队列搞混了)
	2. 每次有结点入队时,就将它加到暂存队列中,最后再把暂存队列转化为拓扑序列
	3. 如果队列的长度等于结点的总个数,则返回序列;否则说明有环,无合法拓扑序列


private boolean hasLoop(List<List<Integer>> graph, int n) {

	// 初始化入度数组
	int[] degree = new int[n];
	for (int i = 0; i < n; i++) {
		for (int j : graph.get(i)) {
			degree[j]++;
		}
	}

 	// 最终结果,下标兼具计数的作用
	int[] res = new int[n];
	int count = 0;

	// 初始化队列(将入度为0的结点加入队列中)
	ArrayDeque<Integer> queue = new ArrayDeque<>();
	for (int i = 0; i < n; i++) {
		if (degree[i] == 0) {
			queue.offer(i);
			res[count++] = i;
		}
	}

	// BFS,每取出一个结点即学习完一个结点,将它指向的所有结点入度减1
    while (!queue.isEmpty()) {
		int cur = queue.poll();
			for (int nxt : graph.get(cur)) {
				degree[nxt]--;
				if (degree[nxt] == 0) {
					queue.offer(nxt);
					res[count++] = nxt;
				}
			}
	}

	// 如果队列的长度等于结点的总个数,则返回序列;否则说明有环,无合法拓扑序列
	return count == n ? res : new int[0];
	
}

 
 
 

补充说明

❶ 我们发现,DFS和BFS是两种不同的思路——【DFS+染色数组】VS【BFS+入度数组

❷【生成拓扑序列】的代码实现直接在【判环】的代码基础上修改就行了

❸ 生成拓扑序列时,我们应当意识到DFS和BFS的一个重要区别:

  • DFS是一个逆向生成序列的过程。 比如1 → 3 → 5,在DFS时,从1开始递归,但1最后完成递归。我们会依次得到5,3,1,因此这个记录序列的数据结构我们使用栈(Stack)
  • BFS是一个正向生成序列的过程。 比如1 → 3 → 5,在BFS时,入度为0的1先入队,之后3,5依次入度减为0而入队。我们会依次得到1,3,5,因此这个记录序列的数据结构我们使用队列(Queue)

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

☘️

你可能感兴趣的:(#,数据结构,数据结构,算法,拓扑)