数据结构C++——拓扑排序

一、前言

拓扑排序需要用到邻接表的相关知识,由于笔者在之前的文章中已经介绍过邻接表,此处不再过多赘述,对此部分还不太了解的读者欢迎移步此文章,共同学习!:
数据结构C++——栈
数据结构C++——图的邻接矩阵和邻接表.

二、拓扑排序的概念及作用

(1)有向无环图:一个无环的有向图称作有向无环图,简称DAG图
(2)AOV-网:用顶点表示活动,用弧表示活动间的优先关系的有向图称为顶点表示活动的网,简称AOV-网
(3)拓扑排序:在AOV-网中,不应该出现有向环,因为存在环意味着某项活动应以自己为先决条件。显然, 这是荒谬的。 对给定的 AOV-网应首先判定网中是否存在环。检测的办法是对有向图的顶点进行拓扑排序,若网中所有顶点都在它的拓扑有序序列中, 则该AOV-网中必定不存在环。
所谓拓扑排序就是将AOV-网中所有顶点排成一个线性序列,该序列满足:若在AOV-网中由顶点vi到顶点 vj有一条路径,则在该线性序列中的顶点 Vi必定在顶点Vj之前。
图解算法如下所示
在这里插入图片描述

三、拓扑排序的实现

①拓扑排序的实现原理

定义两个辅助数组结构分别用来存放各顶点入度和记录拓扑排序的顶点序号。从第一个无入度的顶点开始,将所有无入度的顶点依次输出并从已有图中摘除,同时将此结点与其他结点所依附的边摘除,最终输出的顶点序列就是拓扑排序序列,此处需要注意的是:有些有向无环图的拓扑排序序列的结果并不唯一

②拓扑排序中FindInDegree()函数的实现

FindInDegree()函数的实现:

FindInDegree()函数的实现思路:
1:定义两层循环遍历整个邻接表
2:定义指向各个边结点的指针,此指针从指向某结点链表的第一个邻接点开始,遍历整个顶点链表,当某边结点邻接点域等于i时,计数变量自增
3:邻接表遍历结束后,将计数变量的值赋予indegree[i]数组单元
void FindInDegree(ALGraph G, int indegree[]) {
	for (int i = 0; i < G.vexnum; i++) {
		int cnt = 0;//设置变量存储邻接点域为i的结点个数
		for (int j = 0; j < G.vexnum; j++) {
			ArcNode* p = new ArcNode;//定义指向各个边结点的指针
			p = G.vertices[j].firstarc;
			while (p) {//当p未指到单个链表的末尾时继续循环
				if (p->adjvex == i)//当某边结点邻接点域等于i时,计数变量++
					cnt++;
				p = p->nextarc;//指针不断向后指
			}
			indegree[i] = cnt;//将计数结果保留在indegree数组中
		}
	}
}

③拓扑排序的代码实现

拓扑排序的代码实现:

拓扑排序算法思路:
1:求出各结点的入度存入数组indegree中
2:遍历indegree[i]数组,将单元值为0的编号值i进栈
3:将栈顶元素保存在topo数组中,并将此元素出栈。
4:定义指向边结点的指针,使该指针从出栈元素的第一个边结点开始依次向后指,遍历某顶点元素的所有边结点,并将每个边结点对应的indegree数组单元值自减,当indegree单元值为0时,将该边结点邻接点域进栈
5:最后将m和有向图顶点比较,若两者相等,则该图是有向无环图。
Status TopologicalSort(ALGraph G, int topo[]) {
	//有向图G采用邻接表存储结构
	//若G无回路,则生成G的一个拓扑排序topo[]并返回OK,否则ERROR
	FindInDegree(G, indegree);//求出各结点的入度存入数组indegree中
	SqStack S;
	InitStack(S);//初始化栈
	for (int i = 0; i < G.vexnum; i++) {
		if (!indegree[i]) Push(S, i);//入度为0者进栈
	}
	int m = 0;//对输出顶点计数u,初始为0
	while (!StackEmpty(S)) {
		int i = 0;
		Pop(S, i);//将栈顶顶点vi出栈
		topo[m] = i;//将vi保存在拓扑序列数组topo中
		++m;//对输出顶点计数
		ArcNode* p = new ArcNode;
		p = G.vertices[i].firstarc;//p指向vi的第一个邻接点
		while (p != NULL) {
			int k = p->adjvex;//vk为vi的邻接点
			--indegree[k];//vi的每个邻接点的入度减一
			if (indegree[k] == 0) Push(S, k);//若入度减为0,则入栈
			p = p->nextarc;//p指向顶点vi下一个邻接结点
		}
	}
	if (m < G.vexnum) return ERROR;//该有向图有回路
	else return OK;
}

④完整测试代码

完整的测试代码

#include<iostream>
#include<string>
using namespace std;
#define MVNum 100
#define OK 1
#define ERROR 0
#define MaxInt 100
typedef string VerTexType;
typedef int Status;
typedef int SElemType;
typedef struct{
	SElemType* base;
	SElemType* top;
	int stacksize;
}SqStack;
typedef struct ArcNode {
	int adjvex;
	struct ArcNode* nextarc;
}ArcNode;
typedef struct VNode {
	VerTexType data;
	ArcNode* firstarc;
}VNode,AdjList[MVNum];
typedef struct {
	int vexnum, arcnum;
	AdjList vertices;
}ALGraph;
/*--------拓扑排序辅助数组的存储结构--------*/
int indegree[MVNum];//存放各顶点入度
int topo[MVNum];//记录拓扑序列的顶点编号
Status InitStack(SqStack& S) {
	S.base = new SElemType[MaxInt];
	if (!S.base) return ERROR;
	S.top = S.base;
	S.stacksize = MaxInt;
	return OK;
}
Status StackEmpty(SqStack S) {
	if (S.top == S.base) return OK;
	return ERROR;
}
Status Push(SqStack& S, SElemType e) {
	if (S.top - S.base == S.stacksize) return ERROR;
	*S.top = e;
	S.top++;
	return OK;
}
Status Pop(SqStack& S, SElemType& e) {
	if (S.base == S.top) return ERROR;
	S.top--;
	e = *S.top;
	return OK;
}
int LocateVex(ALGraph G, VerTexType v) {
	for (int i = 0; i < G.vexnum; i++) {
		if (G.vertices[i].data == v)
			return i;
	}
	return -1;
}
void CreateUDG(ALGraph& G) {
	cin >> G.vexnum >> G.arcnum;
	for (int i = 0; i < G.vexnum; i++) {
		cin >> G.vertices[i].data;
		G.vertices[i].firstarc = NULL;//初始化表头结点的指针域为NULL
	}
	for (int k = 0; k < G.arcnum; k++) {
		VerTexType v1, v2;
		cin >> v1 >> v2;
		int i = LocateVex(G, v1);
		int j = LocateVex(G, v2);
		ArcNode* p1 = new ArcNode;
		p1->adjvex = j;
		p1->nextarc = G.vertices[i].firstarc;
		G.vertices[i].firstarc = p1;
	}
}
void FindInDegree(ALGraph G, int indegree[]) {
	for (int i = 0; i < G.vexnum; i++) {
		int cnt = 0;//设置变量存储邻接点域为i的结点个数
		for (int j = 0; j < G.vexnum; j++) {
			ArcNode* p = new ArcNode;//定义指向各个边结点的指针
			p = G.vertices[j].firstarc;
			while (p) {//当p未指到单个链表的末尾时继续循环
				if (p->adjvex == i)//当某边结点邻接点域等于i时,计数变量++
					cnt++;
				p = p->nextarc;//指针不断向后指
			}
			indegree[i] = cnt;//将计数结果保留在indegree数组中
		}
	}
}
Status TopologicalSort(ALGraph G, int topo[]) {
	//有向图G采用邻接表存储结构
	//若G无回路,则生成G的一个拓扑排序topo[]并返回OK,否则ERROR
	FindInDegree(G, indegree);//求出各结点的入度存入数组indegree中
	SqStack S;
	InitStack(S);//初始化栈
	for (int i = 0; i < G.vexnum; i++) {
		if (!indegree[i]) Push(S, i);//入度为0者进栈
	}
	int m = 0;//对输出顶点计数u,初始为0
	while (!StackEmpty(S)) {
		int i = 0;
		Pop(S, i);//将栈顶顶点vi出栈
		topo[m] = i;//将vi保存在拓扑序列数组topo中
		++m;//对输出顶点计数
		ArcNode* p = new ArcNode;
		p = G.vertices[i].firstarc;//p指向vi的第一个邻接点
		while (p != NULL) {
			int k = p->adjvex;//vk为vi的邻接点
			--indegree[k];//vi的每个邻接点的入度减一
			if (indegree[k] == 0) Push(S, k);//若入度减为0,则入栈
			p = p->nextarc;//p指向顶点vi下一个邻接结点
		}
	}
	if (m < G.vexnum) return ERROR;//该有向图有回路
	else return OK;
}
/*输出拓扑排序后的结果*/
void PrintResult(ALGraph G) {
	if (TopologicalSort(G, topo)) {
		for (int i = 0; i < G.vexnum; i++) {
			cout << G.vertices[topo[i]].data << " ";
		}
	}
}
int main() {
	ALGraph G;
	CreateUDG(G);
	PrintResult(G);
	return 0;
}
输入:
6 8
v1 v2 v3 v4 v5 v6
v1 v2
v1 v3
v1 v4
v3 v2
v3 v5
v4 v5
v6 v4
v6 v5

输入数据构造的有向无环图
在这里插入图片描述
由于去掉一些无入度的结点后,此时可能有多个结点都无入度,可从中任选一个继续进行。因此,有些有向无环图的拓扑排序序列的结果并不唯一

输出:
v6 v1 v3 v2 v4 v5

四、总结

以上为笔者对于拓扑排序的一些见解,希望初学者都能有所收获,有技术不到位的地方,还望各位大佬指正。
同时,笔者的个人主页还有数据结构中其他部分的一些见解与分析,后续数据结构的相关知识还将陆续更新,欢迎大家访问且共同学习!

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐