6-13图-拓扑排序

拓扑排序

一.基础知识

1.AOV网(用顶点表示活动的网)
(Activity On Vertex NetWork)

用DAG图(有向无环图)表示⼀个工程,顶点表示活动,有向边<Vi, Vj>表示活动Vi必须先于活动Vj进行

因此:AOV网一定是有向无环图

如:番茄炒蛋的AOV网
在这里插入图片描述
2.拓扑排序

把顶点排成一排,且必须满足以下条件

(1) 每个顶点出现且只出现一次
(2)AOV网中A→B,表示拓扑排序中A排在B之前

这种序列成为原图的一个拓扑排序

注:
(1)有向无环图才有拓扑排序
(2)拓扑排序结果可能不唯一
在这里插入图片描述
二.拓扑排序的构建

首先,准备厨具和买菜任选一
在这里插入图片描述
如果选择准备厨具
在这里插入图片描述
下一个必定选买菜
在这里插入图片描述
然后打鸡蛋、洗番茄任选一。如果选择洗番茄
在这里插入图片描述
切番茄、打鸡蛋任选一。如果选择切番茄
在这里插入图片描述
再依次选择打鸡蛋、下锅炒、吃。完成
在这里插入图片描述

描述总结:

①从AOV网中选择一个没有前驱(入度为0)的顶点并输出
②从网中删除该顶点和所有以它为起点的有向边
③重复①和②直到当前的AOV网为空或当前网中不存在无前驱的顶点为止(不存在回路),即不存在回路才能将其加入拓扑序列,因此,拓扑排序可以用来判断一个有向图是否有环(回路)

如:当前所有顶点的入度>0,无法执行①
在这里插入图片描述

三.算法实现

indegree[]:当前顶点入度
print[]:记录拓扑序列
栈S:保存度为0的顶点(也可用队列、数组)
在这里插入图片描述
在此图中,indegree[]={0,1,0,2,2}
print[]={-1,-1,-1,-1,-1}
在这里插入图片描述

开始

for (int i = 0; i < G.vexnum; i++)
		if (indegree[i] == 0)
			push(S, i);//将所有入度为0的顶点进栈

此时0、2入栈
在这里插入图片描述

int count = 0;//计数,记录当前已经输出的顶点数
while (!IsEmpty(S))
	{
		Pop(S, i);//栈S中弹出2,剩0
		print[count++] = i;//输出顶点i=2
	}

回顾:print[]:记录拓扑序列

2出栈,此时count=0,指向0号位的2
print[count++]表示先print[count](记录2为拓扑序列),再count++(count指向下一个,如图)
在这里插入图片描述
在这里插入图片描述
然后
通过邻接矩阵,找到2指向的结点(3和4),把这些弧去掉
在这里插入图片描述
目的:将所有i=2指向的顶点的入度-1,并且将入度为0的顶点压入栈S

ArcNode* firstarc;//指向第一条依附于该顶点的弧的指针,即2→3的弧
struct ArcNode* nextarc;//指向下一条弧的指针,即2→4的弧
int adjvex;//该弧所指向的顶点位置,即3

当前i=2

for (p = G.vertices[i].firstarc; p; p = p->nextarc) {
//第一次p=第一条依附于2的弧的指针:2→3的箭头
//第二次p=指向下一条弧的指针:2→4的箭头
			v = p->adjvex;
//第一次v=该弧所指向的顶点位置,即v=3
//第二次v=该弧所指向的顶点位置,即v=4
			if (!(--indegree[v]))
				push(S, v);//若该节点入度为0,则入栈
//原来顶点3和顶点4的入读均为2,-1均为1,不入栈
		}

if (!(- -indegree[v])) 相当于先indegree[v] - - ,再判断if
if(!x)表示:若x为假(为0),则执行后面语句
如:顶点3的入度减1,再进行判断,如果为0,则压入栈(此处顶点3的入度减1后是1,不入栈)

(因为从网中删除顶点2和所有以它为起点的有向边)

注:如果此处改为if(!(indegree[v]- -)) 则含义为先判断if,再让indegree[v]-1

更新indegree数组
在这里插入图片描述
然后

while (!IsEmpty(S))
{
		Pop(S, i);
		print[count++] = i;//输出顶点i
}

弹出0,0入print,count向后
在这里插入图片描述
此时栈空

for (p = G.vertices[i].firstarc; p; p = p->nextarc) {
		//将所有i指向的顶点的入度-1,并且将入度为0的顶点压入栈S
			v = p->adjvex;
			if (!(--indegree[v]))
				push(S, v);//入度为0,则入栈
		}
	}

在这里插入图片描述

G.vertices[i].firstarc->adjvex找到1
1的入度为1≠0,入度-1,此时1的入度为0,压入栈
在这里插入图片描述
继续
1出栈,加入print,count++
找到3,入度-1=0,3入栈
3出栈,加入print,count++
找到4,入度-1=0,4入栈

while (!IsEmpty(S))
	{
		Pop(S, i);//4出栈
		print[count++] = i;//4加入print,count变为5
		for (p = G.vertices[i].firstarc; p; p = p->nextarc) 
		{
			v = p->adjvex;
			if (!(--indegree[v]))
				push(S, v);
		}//跳过
	}//while结束
if (count < G.vexnum)
		return false;//排序失败,有向图中有回路
	else
		return true;//count=5=顶点数,成功

结果
在这里插入图片描述
最终代码

#define MaxVertexNum 100//图中顶点数目的最大值
typedef struct ArcNode {//边表结点
	int adjvex;//该弧所指向的顶点位置
	struct ArcNode* nextarc;//指向下一条弧的指针
	//InfoType info;//边的权值
}ArcNode;
typedef struct VNode {//顶点表结点
	VertexType data;//顶点信息
	ArcNode* firstarc;//指向第一条依附于该顶点的弧的指针
}VNode,AdjList[MaxVertexNum];
typedef struct {
	AdjList vertices;//邻接表
	int vexnum, arcnum;//图的顶点数和弧数
}Graph;//Graph是以邻接表存储图类型

bool TopologicalSort(Graph G) {
	InitStack(S);//初始化栈,存储入度为0的顶点
	for (int i = 0; i < G.vexnum; i++)
		if (indegree[i] == 0)
			push(S, i);//将所有入度为0的顶点进栈
	int count = 0;//计数,记录当前已经输出的顶点数
	while (!IsEmpty(S))
	{
		Pop(S, i);
		print[count++] = i;//输出顶点i
		for (p = G.vertices[i].firstarc; p; p = p->nextarc) {
		//将所有i指向的顶点的入度-1,并且将入度为0的顶点压入栈S
			v = p->adjvex;
			if (!(--indegree[v]))
				push(S, v);//入度为0,则入栈
		}
	}
	if (count < G.vexnum)
		return false;//排序失败,有向图中有回路
	else
		return true;//拓扑排序成功
}

四.时间复杂度
在代码中,每个顶点都需要处理一次,每条边也都需要处理一次,因此对于邻接表的时间复杂度为O(|V|+|E|)
另:邻接矩阵:O(|V|2)

五.逆拓扑排序

改变:从AOV网中选择一个没有后继(出度为0)的顶点并输出
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
完成
在这里插入图片描述
六.逆拓扑排序的实现(DFS算法)
(详情DFS)

第一次走到底,依次访问0134,print4
继续执行3,print3
继续执行1,print1
继续执行0,print0
访问2,print2
在这里插入图片描述
完整代码

void DFSTraverse(Graph G) {//对图G进行深度优先遍历
	for (v = 0; v < G.vexnum; ++v)
		visited[v] = FALSE;//初始化已访问标记数组
	for (v = 0; v < G.vexnum; ++v)
		if (!visited[v])
			DFS(G, v);
}
void DFS(Graph G, int v) {//从顶点v出发,深度优先遍历图G
	visited[v] = True;//设已访问标记
	for(w=FirstNeigbor(G,v);w>=0;w=NextNeighbor(G,v,w))
		if (!visited[w]) {//w为u的尚未访问的邻接顶点
			DFS(G, w);
		}
	print(v);
}
  • 11
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

卡__卡

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

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

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

打赏作者

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

抵扣说明:

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

余额充值