拓扑排序

210.课程表Ⅱ

在这里插入图片描述BFS:

  1. 「拓扑排序」是专门应用于有向图的算法;
  2. 这道题用 BFS 和 DFS 都可以完成,只需要掌握 BFS 的写法就可以了,BFS 的写法很经典;
  3. BFS 的写法就叫「拓扑排序」,这里还用到了贪心算法的思想,贪的点是:当前让入度为 0 的那些结点入队;
  4. 「拓扑排序」的结果不唯一;
  5. 删除结点的操作,通过「入度数组」体现,这个技巧要掌握;
  6. 「拓扑排序」的一个附加效果是:能够顺带检测有向图中是否存在环,这个知识点非常重要,如果在面试的过程中遇到这个问题,要把这一点说出来。
    算法流程
    1、在开始排序前,扫描对应的存储空间(使用邻接表),将入度为 0 的结点放入队列。

2、只要队列非空,就从队首取出入度为 0 的结点,将这个结点输出到结果集中,并且将这个结点的所有邻接结点(它指向的结点)的入度减 1,在减 1 以后,如果这个被减 1的结点的入度为 0,就继续入队。

3、当队列为空的时候,检查结果集中的顶点个数是否和课程数相等即可。

(思考这里为什么要使用队列?如果不用队列,还可以怎么做,会比用队列的效果差还是更好?)

在代码具体实现的时候,除了保存入度为 0 的队列,我们还需要两个辅助的数据结构:

1、邻接表:通过结点的索引,我们能够得到这个结点的后继结点;

2、入度数组:通过结点的索引,我们能够得到指向这个结点的结点个数。

class solution{
	 public int[] findOrder(int numCourses, int[][] prerequisites) {
	        if (numCourses <= 0) {
	            return new int[0];
	        }
	        //通过邻接表法构建图
	        HashSet[] adj = new HashSet[numCourses];
	        for(int i=0;i<numCourses;i++) {
	        	adj[i] = new HashSet<>();
	        }
	        //建立点的入度数表:
	        int[] inDegree = new int[numCourses];
	        for(int[] p:prerequisites) {
	        	adj[p[1]].add(p[0]);
	        	inDegree[p[0]]++;
	        }
	        Queue queue = new LinkedList();
	        //BFS框架
	        //将0度数点放入队列中
	        for(int i=0;i<numCourses;i++) {
	        	if(inDegree[i]==0) {
	        		queue.add(i);
	        	}
	        }
	        //res数组保存结果
	        int[] res = new int[numCourses];
	        int count = 0;
	        while(!queue.isEmpty()) {
	        	int head = (int) queue.poll();
	        	res[count] = head;
	        	count++;
	        	//将后继结点度数减一
	        	Set<Integer> successors = adj[head];
	        	for(int nextCourse:successors) {
	        		inDegree[nextCourse]--;
	        		//检测度数是否为0,是则入栈
	        		if(inDegree[nextCourse] == 0) {
	        			queue.offer(nextCourse);
	        		}
	        	}
	        }
	        //如果集合中点的数量不等于,存在环
	        if(count == numCourses) {
	        	return res;
	        }
	        return new int[0];
}
}

DFS版:

对于图中的任意一个节点,它在搜索的过程中有三种状态,即:

「未搜索」:我们还没有搜索到这个节点;

「搜索中」:我们搜索过这个节点,但还没有回溯到该节点,即该节点还没有入栈,还有相邻的节点没有搜索完成);

「已完成」:我们搜索过并且回溯过这个节点,即该节点已经入栈,并且所有该节点的相邻节点都出现在栈的更底部的位置,满足拓扑排序的要求。
通过上述的三种状态,我们就可以给出使用深度优先搜索得到拓扑排序的算法流程,在每一轮的搜索搜索开始时,我们任取一个「未搜索」的节点开始进行深度优先搜索。

我们将当前搜索的节点 u标记为「搜索中」,遍历该节点的每一个相邻节点 v:

如果 v 为「未搜索」,那么我们开始搜索 v,待搜索完成回溯到 u;

如果 v 为「搜索中」,那么我们就找到了图中的一个环,因此是不存在拓扑排序的;

如果 v 为「已完成」,那么说明 v 已经在栈中了,而 u 还不在栈中,因此 u 无论何时入栈都不会影响到 (u, v)
之前的拓扑关系,以及不用进行任何操作。
//DFS版
class solution{
//邻接表
List<List> edges;
//记录每个点的状态
int[] visited;
//记录结果
int[] result;
//判断是否有环
boolean valid = true;
//下标
int index;

	 public int[] findOrder(int numCourses, int[][] prerequisites) {
		 //构建图
		 edges = new ArrayList<List<Integer>>();
		 for(int i=0;i<numCourses;i++) {
			 edges.add(new ArrayList());
		 }
		 for(int[] info:prerequisites) {
			 edges.get(info[1]).add(info[0]);
		 }
		 visited = new int[numCourses];
		 result = new int[numCourses];
		 index = numCourses - 1;
		 //每次挑选一个未检索的结点,开始深度优先遍历
		 for(int i=0;i<numCourses&&valid;i++) {
			 if(visited[i] == 0) {
				 dfs(i);
			 }
		 }
		 //如果有环
		 if(!valid) {
			 return new int[0];
		 }
		 return result;
	 }

	private void dfs(int i) {
		// TODO Auto-generated method stub
		//将当前节点标记为检索中
		visited[i] = 1;
		//检索相邻结点
		for(int v:edges.get(i)) {
			//如果未检索,检索相邻结点
			if(visited[v] == 0) {
				
				dfs(v);
				//检测点的过程中发现了环
				if(!valid)return;
			}
			//如果发现了正在检测的点。说明找到了环
			else if(visited[v] == 1) {
				valid = false;
				return;
			}
		}
		//标记为已完成
		visited[i] = 2;
		result[index--] = i;
	}
}

蓝桥杯_发现环

在这里插入图片描述
拓扑排序思想,无向图度数

package lanqiao;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Scanner;

public class PREV_49 {
	//结点度数表
	static int[] degree;
	//邻接表
	static List<List<Integer>> gra;
	//结果
	static ArrayList result;
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int m = sc.nextInt();
		//初始化
		gra = new ArrayList<List<Integer>>();
		for(int i=0;i<=m;i++) {
			gra.add(new ArrayList<Integer>());
		}
		degree = new int[m+1];
		Queue queue = new LinkedList();
		result = new ArrayList();
		//构建无向图
		for(int i=0;i<m;i++) {
			int a = sc.nextInt();
			int b = sc.nextInt();
			gra.get(a).add(b);
			gra.get(b).add(a);
			degree[b]++;
			degree[a]++;
		}
		for(int i=1;i<=m;i++) {
			if(degree[i]==1) {
				queue.add(i);
				result.add(i);
			}
		}
		//BFS框架
		while(!queue.isEmpty()) {
			int head = (int) queue.poll();
			for(int item:gra.get(head)) {
				//将后继点度数减一
				--degree[item];
				if(degree[item]==1) {
					queue.add(item);
					result.add(item);
				}
			}
		}
		for(int i=1;i<=m;i++) {
			if(!result.contains(i)) {
				System.out.print(i+" ");
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值