拓扑排序(DFS)

拓扑排序如何用DFS实现??
首先了解DFS生成树,
在这里插入图片描述
根据拓扑排序的定义可以知道,只有图没有环路时才有拓扑排序,由上述DFS生成树可知,当图的DFS生成树没有反祖边时没有环路,这时就可以找出图的拓扑排序(建议画一张图,然后用DFS遍历一遍,就能理解为什么得出拓扑排序)
该算法采用 DFS+栈
首先结构定义

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int MaxV = 100;
/*定义栈, 注意栈的数据类型为string*/
typedef struct SNode{
	string *Data;	
	int Top;
	int MaxSize;
} * Stack;
typedef struct ENode{
	int v1, v2;
	string va, vb;
} * Edge;
struct AdjVNode{
	int AdjV;
	AdjVNode *Next;
};
typedef struct VNode{
	string Data;
	bool tag;
	AdjVNode *EdgeFirst;
} AdjList[MaxV];
typedef struct GNode{
	AdjList L;
	int Nv, Ne;
} * LGraph;

栈的需要的操作

/*初始化栈*/
Stack CreateStack(int MaxSize)
{
	Stack S = new SNode;
	S->Data = new string[MaxSize];
	S->MaxSize = MaxSize;
	S->Top = -1;
	return S;
}
/*入栈*/
void Push(Stack S, string x)
{
	S->Data[++S->Top] = x;
}
/*出栈*/
string Pop(Stack S)
{
	return S->Data[S->Top--];
}
/*查找栈中有没有元素x, 并返回在栈中的位置, 如果不在栈中返回-1*/
int FindS(Stack S, string x)
{
	for (int i = S->Top; i >= 0; i--)
		if (S->Data[i] == x)
			return i;
	return -1;
}
/*将边由string型加入对应的int型*/
void Edge_Code(Edge E, Stack S)	//因为这里输入的顶点不再是固定的从0~Nv - 1的编号
{
	int k = FindS(S, E->va);
	if (k < 0) //如果顶点不在栈中, 压入栈
	{
		Push(S, E->va);
		E->v1 = S->Top; //得到新的v1的编号
	}
	else
		E->v1 = k; //得到已有v1的编号
	k = FindS(S, E->vb);
	if (k < 0) //如果顶点不在栈中, 压入栈
	{
		Push(S, E->vb);
		E->v2 = S->Top; //得到新的v2的编号
	}
	else
		E->v2 = k; //得到已有v2的编号
}

图的相关操作

/*初始化邻接表*/
LGraph CreateGraph(int Nv)
{
	LGraph G = new GNode;
	G->Nv = Nv; G->Ne = 0;
	for (int i = 0; i < G->Nv; i++)
	{
		G->L[i].EdgeFirst = NULL;
		G->L[i].tag = false;
	}
	return G;
}
/*插入边操作*/
void InsertEdge(LGraph G, Edge E)
{
	AdjVNode *V = new AdjVNode;
	V->AdjV = E->v2;
	V->Next = G->L[E->v1].EdgeFirst;
	G->L[E->v1].EdgeFirst = V;
}
/*建立新的图, 键盘输入*/
LGraph BuildGraph()
{
	int Nv;
	cin >> Nv;
	LGraph G = CreateGraph(Nv);

	cin >> G->Ne;
	Edge E = new ENode;
	Stack S = CreateStack(G->Nv);
	for (int i = 0; i < G->Ne; i++)
	{
		cin >> E->va >> E->vb;
		Edge_Code(E, S);
		InsertEdge(G, E);
		G->L[E->v1].Data = E->va;
		G->L[E->v2].Data = E->vb;
	}
	delete E;
	delete[] S->Data;
	delete S;
	return G;
}
/*删除图*/
void DeleteGraph(LGraph G)
{
	AdjVNode *V;
	for (int i = 0; i < G->Nv; i++)
		while (G->L[i].EdgeFirst)
		{
			V = G->L[i].EdgeFirst;
			G->L[i].EdgeFirst = V->Next;
			delete V;
		}
	delete G;
}

关键代码,拓扑排序算法

/*拓扑排序DFS算法*/
/*tag: 0表示未访问, 1表示已访问且正常, -1表示已访问但有环*/
bool DFS(LGraph G, int u, Stack S)	//当前DFS函数退出时,下一访问顶点是兄弟或父亲
{									//给予DFS()返回值,false表示有环,true表示无环
	G->L[u].tag = -1;				//if:DFS(i - 1) == false && Tree(i - 1)中没有指向顶点i的边,就有 DFS(i) = false
	AdjVNode *V = G->L[u].EdgeFirst;//							 Tree树是DFS生成树
	while (V)
	{
		if(G->L[V->AdjV].tag == -1)	//此时表明在当前函数下,又回到了u点, 即有回路
			return false;
		if(G->L[V->AdjV].tag == 0 && !DFS(G, V->AdjV, S))	//顶点v没有访问过, 但是v的子节点出现有环,返回错误
			return false;		//有DFS()进入下一递归函数, 作为后判断
		/*函数运行到此处,表示以u为顶点的其中一条边访问完毕,下面进行u的另一条边的访问*/
		V = V->Next;
	}
	/*退出循坏后,表明,而上面没有return,则以u为顶点的*/
	G->L[u].tag = 1;		//对u所有的边访问完后,tag = 1表明正常
	Push(S, G->L[u].Data); 	//注意,入栈是后序(通过前面自己模拟DFS可以明白)
	return true;
}

bool TopSort(LGraph G, Stack S)	//返回值表示是否有拓扑排序, true表示有拓扑排序
{
	for (int i = 0; i < G->Nv; i++)
		if(G->L[i].tag == 0)	//没访问过才访问
			if(!DFS(G, i, S))	//一旦发现有环, 直接退出
				return false;
	return true;
}

实例一
在这里插入图片描述
输入

14 21
w z
v w
p z
p s
o s
p o
n o
o r
s r
o v
v x
y v
r y
r u
n u
u t
q t
n q
m r
m q
m x

在这里插入图片描述
若插入一条有向边<t, m>使之成为一个有环图
在这里插入图片描述
实例二
在这里插入图片描述在这里插入图片描述
输入1

13 15
0 1
0 5
0 6
2 0
2 3
3 5
5 4
6 4
7 6
8 7
6 9
9 10
9 12
9 11
11 12

在这里插入图片描述
输入2

13 22
0 1
0 5
2 0
2 3
3 2
3 5
4 2
4 3
5 4
6 0
6 4
6 9
7 6
7 8
8 7
8 9
9 10
9 11
10 12
11 4
11 12
12 9

在这里插入图片描述
参考
https://zhuanlan.zhihu.com/p/45051460
完整代码

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int MaxV = 100;
/*定义栈, 注意栈的数据类型为string*/
typedef struct SNode{
	string *Data;
	int Top;
	int MaxSize;
} * Stack;
typedef struct ENode{
	int v1, v2;
	string va, vb;
} * Edge;
struct AdjVNode{
	int AdjV;
	AdjVNode *Next;
};
typedef struct VNode{
	string Data;
	int tag;
	AdjVNode *EdgeFirst;
} AdjList[MaxV];
typedef struct GNode{
	AdjList L;
	int Nv, Ne;
} * LGraph;
/*初始化栈*/
Stack CreateStack(int MaxSize)
{
	Stack S = new SNode;
	S->Data = new string[MaxSize];
	S->MaxSize = MaxSize;
	S->Top = -1;
	return S;
}
/*入栈*/
void Push(Stack S, string x)
{
	S->Data[++S->Top] = x;
}
/*出栈*/
string Pop(Stack S)
{
	return S->Data[S->Top--];
}
/*查找栈中有没有元素x, 并返回在栈中的位置, 如果不在栈中返回-1*/
int FindS(Stack S, string x)
{
	for (int i = S->Top; i >= 0; i--)
		if (S->Data[i] == x)
			return i;
	return -1;
}
/*将边由string型加入对应的int型*/
void Edge_Code(Edge E, Stack S)
{
	int k = FindS(S, E->va);
	if (k < 0) //如果顶点不在栈中, 压入栈
	{
		Push(S, E->va);
		E->v1 = S->Top; //得到新的v1的编号
	}
	else
		E->v1 = k; //得到已有v1的编号
	k = FindS(S, E->vb);
	if (k < 0) //如果顶点不在栈中, 压入栈
	{
		Push(S, E->vb);
		E->v2 = S->Top; //得到新的v2的编号
	}
	else
		E->v2 = k; //得到已有v2的编号
}
/*初始化邻接表*/
LGraph CreateGraph(int Nv)
{
	LGraph G = new GNode;
	G->Nv = Nv; G->Ne = 0;
	for (int i = 0; i < G->Nv; i++)
	{
		G->L[i].EdgeFirst = NULL;
		G->L[i].tag = 0;
	}
	return G;
}
/*插入边操作*/
void InsertEdge(LGraph G, Edge E)
{
	AdjVNode *V = new AdjVNode;
	V->AdjV = E->v2;
	V->Next = G->L[E->v1].EdgeFirst;
	G->L[E->v1].EdgeFirst = V;
}
/*建立新的图, 键盘输入*/
LGraph BuildGraph()
{
	int Nv;
	cin >> Nv;
	LGraph G = CreateGraph(Nv);

	cin >> G->Ne;
	Edge E = new ENode;
	Stack S = CreateStack(G->Nv);
	for (int i = 0; i < G->Ne; i++)
	{
		cin >> E->va >> E->vb;
		Edge_Code(E, S);
		InsertEdge(G, E);
		G->L[E->v1].Data = E->va;
		G->L[E->v2].Data = E->vb;
	}
	delete E;
	delete[] S->Data;
	delete S;
	return G;
}
/*删除图*/
void DeleteGraph(LGraph G)
{
	AdjVNode *V;
	for (int i = 0; i < G->Nv; i++)
		while (G->L[i].EdgeFirst)
		{
			V = G->L[i].EdgeFirst;
			G->L[i].EdgeFirst = V->Next;
			delete V;
		}
	delete G;
}
/*拓扑排序DFS算法*/
/*tag: 0表示未访问, 1表示已访问且正常, -1表示已访问但有环*/
bool DFS(LGraph G, int u, Stack S)	//当前DFS函数退出时,下一访问顶点是兄弟或父亲
{									//给予DFS()返回值,false表示有环,true表示无环
	G->L[u].tag = -1;				//if:DFS(i - 1) == false && Tree(i - 1)中没有指向顶点i的边,就有 DFS(i) = false
	AdjVNode *V = G->L[u].EdgeFirst;//							 Tree树是DFS生成树
	while (V)
	{
		if(G->L[V->AdjV].tag == -1)	//此时表明在当前函数下,又回到了u点, 即有回路
			return false;
		if(G->L[V->AdjV].tag == 0 && !DFS(G, V->AdjV, S))	//顶点v没有访问过, 但是v的子节点出现有环,返回错误
			return false;		//有DFS()进入下一递归函数, 作为后判断
		/*函数运行到此处,表示以u为顶点的其中一条边访问完毕,下面进行u的另一条边的访问*/
		V = V->Next;
	}
	/*退出循坏后,表明,而上面没有return,则以u为顶点的*/
	G->L[u].tag = 1;		//对u所有的边访问完后,tag = 1表明正常
	Push(S, G->L[u].Data); 	//注意,入栈是后序(通过前面自己模拟DFS可以明白)
	return true;
}

bool TopSort(LGraph G, Stack S)	//返回值表示是否有拓扑排序, true表示有拓扑排序
{
	for (int i = 0; i < G->Nv; i++)
		if(G->L[i].tag == 0)	//没访问过才访问
			if(!DFS(G, i, S))	//一旦发现有环, 直接退出
				return false;
	return true;
}

int main()
{
	LGraph G = BuildGraph();
	Stack S = CreateStack(G->Nv);

	if(!TopSort(G, S))		//如果没有拓扑排序
		cout << "无拓扑排序,存在有向环!" << endl;
	else
	{
		cout << "拓扑排序为:" << endl;
		while (S->Top >= 0)
			cout << Pop(S) << " ";
		cout << endl;
	}

	DeleteGraph(G);
	delete[] S->Data;
	delete S;
	system("pause");
	return 0;
}
  • 5
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
拓扑排序是一种对有向无环图进行排序的算法,它可以用来确定前驱关系,验证图中是否有环等。拓扑排序的基本思想是将图中的节点按照一定的顺序排列,使得所有的有向边从排在前面的节点指向排在后面的节点。在实际应用中,拓扑排序常常被用来解决任务调度、编译顺序等问题。 在C++中,可以使用DFS算法进行拓扑排序。具体实现方法是,对于每个节点,先将其标记为正在访问中,然后遍历其所有的邻居节点,如果邻居节点已经被访问过了,就说明存在环,返回false;如果邻居节点还没有被访问过,就递归地对其进行访问。当所有的邻居节点都被访问完毕后,将该节点标记为已经访问过,并将其加入到拓扑序列中。最后,将拓扑序列倒序输出即可。 下面是一个使用DFS算法进行拓扑排序的C++代码示例: ``` const int maxn = 100; int c[maxn]; int topo[maxn], t, n; int G[maxn][maxn]; bool dfs(int u) { c[u] = -1; for(int v = 0; v < n; v++) { if(G[u][v]) { if(c[v] < 0) return false; else if(!c[v] && !dfs(v)) return false; } } c[u] = 1; topo[--t] = u; return true; } bool topoSort() { t = n; memset(c,0,sizeof(c)); for(int u = 0; u < n; u++){ if(!c[u] && !dfs(u)) return false; } return true; } int main() { char ch[100] = {'a', 'b', 'c', 'd'}; n = 4; G[0][1] = 1; G[2][1] = 1; G[3][2] = 1; if(!topoSort()){ printf("无法拓扑排序\n"); return 0; } for(int i = 0; i < 4; i++){ printf("%c ", ch[topo[i]]); } printf("\n"); } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值