判断有向图是否有环

题目:LeetCode207. Course Schedule。在这里我提供三种解法:

解法一:

寻找从入度为0的点,删掉从它们出发的边。持续进行会构造新的入度为0的点。如果不能进行到底,则是带环的:ac代码。处理过程有点类似bfs

解法二:

回溯+记忆:ac代码如下。其中使用名为checked的数组,只是为了保存已确定没有连接到环的点,不然会TLE。

class Solution {
	boolean[] visits;
	boolean[] checked;
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        if(numCourses<2)
        	return true;
         
        //构造图的邻接表
        List<Integer>[] graph = new List[numCourses];
        for(int v=0;v<numCourses;++v) {
        	graph[v] = new LinkedList<Integer>();
        }
        for(int[] e : prerequisites) {
        	graph[e[0]].add(e[1]);//构造图的邻接表
        }

        visits = new boolean[numCourses];
        checked = new boolean[numCourses];
        for(int v=0;v<numCourses;++v) {
        		if(!dfs(graph,numCourses,v))
        			return false;
        }
        return true;
    }

	private boolean dfs(List<Integer>[] graph, int V, int v) {
		if(checked[v])
			return true;
		if(visits[v])
			return false;
		visits[v] = true;
		for(int to : graph[v]) {
			if(!dfs(graph,V,to)) {
				return false;
			}
		}
		visits[v] = false; //当没用使用checked,这句是必需的。但当使用了checked,没有这句也能ac
		checked[v] = true;
		return true;
	}
}

解法三:

学习了经典书籍《算法导论》中dfs时每个点的发现时间、完成时间的特点:回边的特点是,边连接的两点都还未标记完成时间,到达点已经标记了发现时间,且小于出发点的发现时间(这一句是一定的,不用判断)。ac代码如下。此外也如同解法二,使用名为checked的数组来保存已知结果。

class Solution {
	boolean[] checked;
	int seq = 0;
	Integer[] discover;
	Integer[] finish;

	public boolean canFinish(int numCourses, int[][] prerequisites) {
		if (numCourses < 2)
			return true;

		// 构造图的邻接表
		List<Integer>[] graph = new List[numCourses];
		for (int v = 0; v < numCourses; ++v) {
			graph[v] = new LinkedList<Integer>();
		}
		for (int[] e : prerequisites) {
			if (e[0] == e[1])
				return false;
			graph[e[0]].add(e[1]);// 构造图的邻接表
		}

		discover = new Integer[numCourses];
		finish = new Integer[numCourses];
		checked = new boolean[numCourses];
		for (int v = 0; v < numCourses; ++v) {
			if (!dfs(graph, numCourses, v))
				return false;
		}
		return true;
	}

	private boolean dfs(List<Integer>[] graph, int V, int v) {
		discover[v] = ++seq;
		if(checked[v]) {  //如果确定此点没有连接到环,则可把这一块当作一个点处理
			finish[v] = ++seq;
			return true;
		}
		for (int to : graph[v]) {
			if (discover[to]!=null && finish[to]==null /*&& discover[v]>discover[to]*/ || !dfs(graph, V, to))
				return false;
		}
		finish[v] = ++seq;
		checked[v] = true;
		return true;
	}
}

补注:上面对解法二、三的解释有点不够直观。本质就是遍历时每个点的状态会在这三种状态中变化:未访问(未发现)、已发现(刚开始对其dfs时)、已结束(即将退出其dfs时)。有环等价于有回边(backEdge),而回边的特点是指向已发现而还未结束的点(也就是当前结点的祖先)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_23204557

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值