图论拓扑排序-Kahn 算法

拓扑排序:对有向无环图的点进行排序,使得u->v的边在排序后u点始终排在v点前面

适用场景:学生培养计划的安排,例如学习数据结构必须先修完程序设计。

注意:有环图无法进行拓扑排序;拓扑排序的结果可能不唯一。

Kahn算法核心思想:

1.统计各个结点的入度;

2.删除入度为0的结点和其与其他点的边;

3.重复2的步骤,直到图中所有点都被处理或者一直有未处理且入度不为0的点(说明图中出现环状。

由于要删除点和边,实际上没有必要删除,我们可以借助栈来操作

首先我们要定义图的相关数据和入度数组indegree

cpp 复制代码
int indegree[6];           //用于存放各个顶点的入度值
int g[6][6] = {            //图的邻接矩阵,0表示顶点到顶点本身,1表示i到j间有边,-1则表示没有边
	{0 ,1 ,-1,-1,-1,-1},
	{-1,0 ,1 ,-1,-1,-1},
	{-1,-1,0 ,1 ,-1,-1},
	{-1,-1,-1,0 ,-1,-1},
	{-1,1 ,-1,-1,0 ,1 },
	{-1,-1,-1,1 ,-1,0 }
};
typedef struct ANode {
	int adjvex;       //边的邻接点编号
	struct ANode* next;    //指向下一条边的指针
}ArcNode;             //边结点的类型
typedef struct Vnode {
	ArcNode* firstarc;  //指向第一个边结点
}VNode;                 //邻接表的头结点类型
typedef struct {
	VNode adjlist[6];   //邻接表的头结点数组
	int n, e;            //顶点数和边数
}AdjGraph;              //图的邻接表类型

其次,根据邻接矩阵创造邻接表并且求出各个点的入度值

cpp 复制代码
void createGraph(AdjGraph*& G) {
	G = (AdjGraph*)malloc(sizeof(AdjGraph));
	G->e = 6;
	G->n = 6;
	for (int i = 0;i < 6;i++) {
		G->adjlist[i].firstarc = NULL;
	}
	for (int i = 0;i < 6;i++) {
		for (int j = 0;j < 6;j++) {
			if (g[i][j] == 1) {               //若各个顶点间有直接边
				indegree[j]++;              //记录各个点的入度
				ArcNode* p = (ArcNode*)malloc(sizeof(ArcNode));
				p->adjvex = j;                 //储存j作为i的邻接点
				p->next = NULL;
				p->next = G->adjlist[i].firstarc;
				G->adjlist[i].firstarc = p;
			}
		}
	}
}

然后接着进行拓扑排序

例如:

其邻接表为:

我们已经求出各个点的入度值indegree,其中数组的索引为点的编号,对应值为入度值

{0,2,1,2,0,1,2}

首先我们找到入度值为0的点,也就是点0和点4,将其进栈,栈的数据为{0,4}

取栈顶值 ,也就是4,删除点4,输出4,并出栈,栈中还有{0};

从点4开始遍历点4的邻接点,其第一点可能是1或者5(暂且先将5作为第一个点),删除4->5 的边,5的入度减一,判断5的入度是否为0,为0,将5进栈。

接着到4的邻接点1,删除4->1的边,1的入度减一,判断1的入度为1,不进栈

此时的栈的数据为{0,5},接着重复上述步骤,直到栈为空时。

其代码为:

cpp 复制代码
void TopSort(AdjGraph* G) {
	stack<int>s;                       //定义一个储存int类型的栈
	for (int i = 0;i < 6;i++) {
		if (indegree[i] == 0) {          //找到一开始入度为0的结点,将其进栈
			s.push(i);
		}
	}
	while(!s.empty()){
		int j=s.top();                   //取头结点并出栈
		s.pop();
		printf("%d", j);                 //输出
		ArcNode* p = G->adjlist[j].firstarc;  //从入度为0的结点j开始
		while (p != NULL) {                  //遍历其邻接点
			int u = p->adjvex;
			indegree[u]--;                  //由于j已被删去(实际上没有删去只是出栈),删去j到u的边,u的入度减一
			if (indegree[u] == 0) {         //如果u的入度为0,则将u进栈
				s.push(u);
			}
			p = p->next;
		}
	}
}

完整代码为:

cpp 复制代码
#include<iostream>
#include<stack>
using namespace std;
int indegree[6];           //用于存放各个顶点的入度值
int g[6][6] = {            //图的邻接矩阵,0表示顶点到顶点本身,1表示i到j间有边,-1则表示没有边
	{0 ,1 ,-1,-1,-1,-1},
	{-1,0 ,1 ,-1,-1,-1},
	{-1,-1,0 ,1 ,-1,-1},
	{-1,-1,-1,0 ,-1,-1},
	{-1,1 ,-1,-1,0 ,1 },
	{-1,-1,-1,1 ,-1,0 }
};
typedef struct ANode {
	int adjvex;       //边的邻接点编号
	struct ANode* next;    //指向下一条边的指针
}ArcNode;             //边结点的类型
typedef struct Vnode {
	ArcNode* firstarc;  //指向第一个边结点
}VNode;                 //邻接表的头结点类型
typedef struct {
	VNode adjlist[6];   //邻接表的头结点数组
	int n, e;            //顶点数和边数
}AdjGraph;              //图的邻接表类型
void createGraph(AdjGraph*& G) {
	G = (AdjGraph*)malloc(sizeof(AdjGraph));
	G->e = 6;
	G->n = 6;
	for (int i = 0;i < 6;i++) {
		G->adjlist[i].firstarc = NULL;
	}
	for (int i = 0;i < 6;i++) {
		for (int j = 0;j < 6;j++) {
			if (g[i][j] == 1) {               //若各个顶点间有直接边
				indegree[j]++;              //记录各个点的入度
				ArcNode* p = (ArcNode*)malloc(sizeof(ArcNode));
				p->adjvex = j;                 //储存j作为i的邻接点
				p->next = NULL;
				p->next = G->adjlist[i].firstarc;
				G->adjlist[i].firstarc = p;
			}
		}
	}
}
void TopSort(AdjGraph* G) {
	stack<int>s;                       //定义一个储存int类型的栈
	for (int i = 0;i < 6;i++) {
		if (indegree[i] == 0) {          //找到一开始入度为0的结点,将其进栈
			s.push(i);
		}
	}
	while(!s.empty()){
		int j=s.top();                   //取头结点并出栈
		s.pop();
		printf("%d", j);                 //输出
		ArcNode* p = G->adjlist[j].firstarc;  //从入度为0的结点j开始
		while (p != NULL) {                  //遍历其邻接点
			int u = p->adjvex;
			indegree[u]--;                  //由于j已被删去(实际上没有删去只是出栈),删去j到u的边,u的入度减一
			if (indegree[u] == 0) {         //如果u的入度为0,则将u进栈
				s.push(u);
			}
			p = p->next;
		}
	}
}
int main() {
	AdjGraph* G;
	createGraph(G);
	TopSort(G);
	return 0;
}

总结:拓扑排序Kahn核心就是不断的删除入度为0的点和其的出边,直到处理完所有结点为止

此篇文章用于给自己巩固知识点,如果帮助到大家也是非常荣幸,如果文章有错误,感谢批评和指出。

相关推荐
啥都想学点的研究生14 小时前
一篇文章讲清楚:线性回归问题的求解——正规方程法
算法·机器学习·线性回归
青少儿编程课堂14 小时前
树状数组精讲:逆序对计数与离散化(附双语言解法)
算法·树状数组·离散化·信息学奥赛·逆序对
血小板要健康14 小时前
网格 dfs 与 FloodFill:从岛屿、区域到搜索路径
笔记·算法·leetcode·深度优先
VALENIAN瓦伦尼安教学设备15 小时前
设备状态检测振动分析实训台案例分析
大数据·数据库·人工智能·嵌入式硬件·算法
小小晓.15 小时前
C++ 值类别与完美转发深度剖析:从左值右值到 std::forward 源码解密
开发语言·c++·算法
怕浪猫15 小时前
用了两年 AI 编程工具后,我重新理解了什么是「资深工程师」
算法·面试·架构
hetao173383716 小时前
2026-08-27~29 hetao1733837 的刷题记录
c++·算法
云淡风轻~窗明几净16 小时前
宇宙管理学猜想
算法·图论
春风解人意16 小时前
从零开始学习嵌入式P28----线程
c语言·学习·算法
luj_176816 小时前
合法避税与投资评估实战指南
c语言·开发语言·网络·经验分享·算法