图的逆拓扑排序(回路识别)

背景

在学习拓扑排序的时候,老师提出了一个问题:在逆拓扑排序算法中如何识别出回路?
总所周知,拓扑排序必须在DAG(有向无环图)中实现,也就是说如果给定的图带有回路,就无法进行拓扑排序。
我经过思考,想出了一个识别方法,在此做出记录。
在这里插入图片描述

实现思路

何为回路,我的理解就是访问回头路,从系统栈的角度说,就是指向了还在栈中的节点。只要节点还在栈中节点就还在访问路径上,此时指向就是走回头路。若节点不在栈中,出栈了,访问路径自然也就把改点剔除,就不存在经过该的回路。

在这里插入图片描述
我们是基于DFS算法进行拓扑排序。DFS是基于递归实现的算法,从初始点开始,依次遍历第一个邻居节点,直到没有邻居为止,递归结束时输出节点,输出的顺序就是拓扑排序。
以上图为例,就是从0开始依次遍历1、3、4;因为4没有指向其他节点。递归结束。然后搜索剩余未遍历的点,再次进行递归遍历。
从系统栈的角度就是,依次将0、1、3、4压入栈。递归结束,输出4、3、1、0;第二轮递归把2压入,输出2。
在这里插入图片描述
DFS算法,每轮递归访问一次最深的路径,一共两轮,最终的排序次序为:4、3、1、0、2
在这里插入图片描述
如果没有回路,那么每一轮递归只会沿着路径做最深访问,不会出现重复(从系统栈的角度,指向了还在栈中的节点)。
比如第一张图的0、1、3、4没有出现重复访问,但第二张图中,0、1、3、4、2、3,在同一轮递归中访问了重复的节点3。说明出现了回路(走了回头路)

因此我们可以添加一个数组nowVisited[],记录每一轮递归节点被访问的情况。这样就有两个数组记录节点访问情况,visited[]记录全局访问情况,nowVisited[]只记录本轮递归的访问情况。
如果一个节点在本轮递归中没有访问,说明没有出现回路,继续执行代码。
如果一个节点在本轮递归被访问过了,说明出现了回路,代码立即中止执行。

代码

#include <iostream>
using namespace std;
//定义邻接矩阵
#define MAXVERTEXNUM 100
typedef struct {
	int vex[MAXVERTEXNUM];
	int edge[MAXVERTEXNUM][MAXVERTEXNUM];
	int vexnum, arcnum;
}Graph;
//寻找第一个邻接点
int FirstNeighbor(Graph G, int v)
{
	for (int i = 0; i < G.vexnum; ++i)
	{
		if (G.edge[v][i] != 0)
		{
			return i;
		}
	}
	return -1;
}
//寻找下一个邻接点
int NextNeighor(Graph G, int v, int w)
{
	for (int i = w+1; i < G.vexnum; ++i)
	{
		if (G.edge[v][i] != 0)
		{
			return i;
		}
	}
	return -1;
}
//DFS遍历找到排序次序
bool visited[MAXVERTEXNUM];
bool nowVisited[MAXVERTEXNUM];
bool warry = false;
void DFS(Graph G, int v) {
	visited[v] = true;//记录全局访问情况
	nowVisited[v] = true;//记录本轮访问情况
	for (int w = FirstNeighbor(G, v); w >= 0; w = NextNeighor(G, v, w)) {//依次递归访问v的邻居节点
		if (nowVisited[w] == true) {//如果本轮已经访问过了v的邻居节点w,出现回路,立即中止代码
			cout<<"\n出现回路:"<< G.vex[v] <<"=>"<< G.vex[w];
			exit(0);
		}
		if (visited[w] == false) {//没被访问就继续递归,沿着该点路径继续延长
			DFS(G, w);
		}
	}//for
	cout << G.vex[v] << "<-";
	nowVisited[v] = false;//本轮结束,消去本轮对应的访问记录。
}
//防止遗漏
void DFSTraverse(Graph G) {
	for (int v = 0; v < G.vexnum; ++v) {//初始化数组。
		visited[v] = false;
		nowVisited[v] = false;
	}
	for (int v = 0; v < G.vexnum; ++v)//防止出现遗漏
		if (visited[v]==false)
			DFS(G, v);
}
//测试代码
void test()
{
	//初始化图
	Graph G;
	G.arcnum = 5;
	G.vexnum = 5;
	//记录节点的信息(id、权重等等),如邻接矩阵中0可以对应"a"节点或者"1"节点
	for (int i = 0; i <=G.arcnum; ++i)
		G.vex[i] = i+1;
	//这里邻接矩阵用0、1、2、3代表节点,以便适应数组下标
	for (int i = 0; i <= G.arcnum; ++i)
	{
		for (int j = 0; j <= G.arcnum; ++j)
		{
			G.edge[i][j] = 0;
		}
	}
	//设置连接的有向边
	G.edge[0][1] = 1;
	G.edge[1][2] = 1;
	G.edge[2][3] = 1;
	G.edge[4][1] = 1;
	G.edge[2][4] = 1;
	G.edge[4][3] = 1;
	//执行逆排序
	DFSTraverse(G);
}
int main()
{
	test();
	return 0;
}

新开通了本人的公众号,欢迎关注:燕南路GISer ,专注GIS干货分享,不定期更新。
主要兴趣:GIS、时空数据挖掘、python、机器学习深度学习
CSDN的部分内容会重写再搬迁到公众号,欢迎关注!
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

燕南路GISer

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

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

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

打赏作者

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

抵扣说明:

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

余额充值