【C语言数据结构】拓扑排序(代码演示)

 一.简介:

拓扑排序是什么?

拓扑排序是对有向图进行排序的一种算法,它可以得到一个顶点的线性序列,使得对于图中的任意一条有向边 (u, v),顶点 u 在序列中都出现在顶点 v 的前面。换句话说,如果存在一条有向边 (u, v),那么在排序后的序列中,顶点 u 出现在顶点 v 的前面。

主要用于解决有向无环图(DAG)相关的问题(但不限于有向无环图),比如任务调度、依赖关系分析等。通过拓扑排序,我们可以确定一组任务的执行顺序,或者确定一组任务之间的执行依赖关系。

特别注意的是,拓扑排序并不适用于无向图,因为无向图不存在顶点的指向性,而且很可能存在环路,因此无法进行拓扑排序。

二.代码部分:

//有向无环图的拓扑排序
#include<stdio.h>
#include<stdlib.h>
typedef struct graph
{
	char* vexs;//顶点数值
	int** arcs;//邻接矩阵
	int vexNum;//顶点数
	int arcNum;//边数
}Graph;

typedef struct Node//栈的建立与存顶点下标有关
{
	int data;
	struct Node* next;
}Node;

Node* initStack()
{
	Node* stack=(Node*)malloc(sizeof(Node));
	stack->data=0;
	stack->next=NULL;
	return stack;
}

void push(Node* stack,int data)//压栈
{
	Node* node=(Node*)malloc(sizeof(Node));
	node->data=data;
	node->next=stack->next;
	stack->next=node;
	stack->data++;
}

int isEmpty(Node* stack)//判断是否为空栈
{
	if(stack->next==NULL)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}

int pop(Node* stack)//出栈
{
	if(isEmpty(stack))
	{
		return -1;
	}
	else
	{
		Node* node=stack->next;		
		int data=node->data;
		stack->next=node->next;
		free(node);
		stack->data--;
		return data;
	}
}

Graph* initGraph(int vexNum)//分配空间
{
	Graph* G = (Graph*)malloc(sizeof(Graph));
	G -> vexs = (char*)malloc(sizeof(char) * vexNum);
	G -> arcs = (int**)malloc(sizeof(int*) * vexNum);
	for (int i = 0 ; i < vexNum; i++) 
	{
		G -> arcs[i] = (int*)malloc(sizeof(int) * vexNum);
	}
	G -> vexNum = vexNum;
	G -> arcNum = 0;
	return G;
}

void createGraph(Graph* G, char* vexs, int* arcs)//创建图
{
	for (int i = 0 ; i < G -> vexNum; i++) 
	{
		G -> vexs[i] = vexs[i];
		for (int j = 0; j < G -> vexNum; j++) 
		{
			G -> arcs[i][j] = *(arcs + i * G -> vexNum + j);
			if (G -> arcs[i][j] != 0)
				G -> arcNum ++;
		}
	}
	G -> arcNum /= 2;
}

int* findInDegrees(Graph* G)//找出入度
{
	int* inDegrees=(int*)malloc(sizeof(int)*G->vexNum);
	for(int i=0;i<G->vexNum;i++)//初始化
	{
		inDegrees[i]=0;
	}
	for(int i=0;i<G->vexNum;i++)
	{
		for(int j=0;j<G->vexNum;j++)
		{
			if(G->arcs[i][j])
			{
				inDegrees[j]++;
			}
		}
	}
	return inDegrees;
}

void topologicalSort(Graph* G)//拓扑排序
{
	int index=0;
	int* top=(int*)malloc(sizeof(int)*G->vexNum);//存下标的数组
	int* inDegrees=findInDegrees(G);
	Node* stack=initStack();
	for(int i=0;i<G->vexNum;i++)//入度为0的压栈
	{
		if(inDegrees[i]==0)
		{
			push(stack,i);
		}
	}
	while(!isEmpty(stack))//栈不为空,循环执行入度的减法(去掉输出顶点指向的下一个顶点的边)
	{
		int vexindex=pop(stack);//出栈的是顶点的下标
		top[index++]=vexindex;//保存顶点下标
		for(int j=0;j<G->vexNum;j++)
		{
			if(G->arcs[vexindex][j])//下一个顶点有入度时减去
			{
				inDegrees[j]--;
				if(inDegrees[j]==0)//顶点入度减到0了直接入栈
				{
					push(stack,j);
				}
			}
		}
	}
	for(int i=0;i<G->vexNum;i++)//依次输出入度为零的顶点
	{
		printf("%c ",G->vexs[top[i]]);
	}
	printf("\n");
}

void DFS(Graph* G,int* flag,int index)//深度优先遍历
{
	printf("%c ",G->vexs[index]);
	flag[index]=1;//已经访问过顶点标记为1,之后不会再访问
	for(int i=0;i<G->vexNum;i++)
	{
		if(G->arcs[index][i]==1&&!flag[i])
		{
			DFS(G,flag,i);
		}
	}
}

int main()
{
	Graph* G=initGraph(6);
	int* flag=(int*)malloc(sizeof(int)*G->vexNum);
	for(int i=0;i<G->vexNum;i++)//首先赋值为0,表示未访问任何顶点
	{
		flag[i]=0;
	}
	int arcs[6][6]={
		0,1,1,1,0,0,
		0,0,0,0,0,0,
		0,1,0,0,1,0,
		0,0,0,0,1,0,
		0,0,0,0,0,0,
		0,0,0,1,1,0
	};
	createGraph(G,"123456",(int*)arcs);
	DFS(G,flag,0);
	printf("\n");
	topologicalSort(G);
	return 0;
}

运行结果:

1 2 3 5 4
6 1 4 3 5 2
(空行)  

三.解释:

为啥这段代码中要使用栈(stack)?

答:在拓扑排序中,首先找到入度为 0 的顶点(即没有任何边指向该顶点),将其压入栈中。然后不断地从栈中弹出顶点,并将与之相连的顶点的入度减 1。如果某个顶点的入度减为 0,则将其压入栈中。重复这个过程直到栈为空,最终栈中顶点的出栈顺序就是拓扑排序的结果。

因此,使用栈可以方便地实现拓扑排序的过程,对算法的实现和理解都非常有帮助。

拓扑排序中的DFS遍历有什么作用?

答:可以用来判断图中各个顶点连通性。通过遍历过程中访问到的顶点,可以判断哪些顶点是连通的,哪些是孤立的。

因此依据以上代码运行的结果来看,要是从1顶点开始遍历,最后无法输出6顶点,那么6顶点在有向无环图中是孤立顶点,无法输出。(反之要是从6顶点开始遍历,最后1顶点无法输出,1也可以叫孤立顶点)

NO.34

<C语言数据结构>

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
拓扑排序是一种常用的图算法,用于解决有向无环图(DAG)中的排序问题。其基本思想是通过不断选择入度为0的顶点来构建排序序列。下面是一个基于邻接表的C语言实现: ``` #include <stdio.h> #include <stdlib.h> #define MAX_VERTEX_NUM 100 // 最大顶点数 #define ERROR -1 // 错误标识 // 邻接表结构体 typedef struct EdgeNode { int adjvex; // 邻接点编号 struct EdgeNode *next; // 指向下一个邻接点的指针 } EdgeNode; typedef struct VertexNode { int data; // 顶点数据 EdgeNode *firstEdge; // 指向第一个邻接点的指针 int indegree; // 顶点的入度 } VertexNode, AdjList[MAX_VERTEX_NUM]; typedef struct { AdjList adjList; // 邻接表 int vexNum, edgeNum; // 顶点数和边数 } Graph; // 初始化图 void initGraph(Graph *G, int vexNum) { G->vexNum = vexNum; G->edgeNum = 0; for (int i = 0; i < vexNum; i++) { G->adjList[i].data = i; // 顶点数据 G->adjList[i].firstEdge = NULL; // 邻接表为空 G->adjList[i].indegree = 0; // 入度为0 } } // 添加边 void addEdge(Graph *G, int u, int v) { EdgeNode *e = (EdgeNode *) malloc(sizeof(EdgeNode)); e->adjvex = v; e->next = G->adjList[u].firstEdge; G->adjList[u].firstEdge = e; G->adjList[v].indegree++; // 修改入度 G->edgeNum++; // 边数加1 } // 拓扑排序 int topSort(Graph *G, int *result) { int count = 0; // 计数器 int front = 0, rear = 0; // 队列的头尾指针 int queue[MAX_VERTEX_NUM]; // 存储入度为0的顶点 for (int i = 0; i < G->vexNum; i++) { if (G->adjList[i].indegree == 0) { queue[rear++] = i; // 入度为0的顶点入队 } } while (front != rear) { // 队列非空 int u = queue[front++]; // 出队一个顶点 result[count++] = u; // 存储排序结果 for (EdgeNode *e = G->adjList[u].firstEdge; e != NULL; e = e->next) { int v = e->adjvex; if (--G->adjList[v].indegree == 0) { queue[rear++] = v; // 入度为0的顶点入队 } } } if (count != G->vexNum) { // 存在环路 return ERROR; } return 0; } int main() { Graph G; int vexNum = 6; // 顶点数 int result[MAX_VERTEX_NUM]; // 存储排序结果 initGraph(&G, vexNum); addEdge(&G, 0, 1); addEdge(&G, 0, 2); addEdge(&G, 1, 3); addEdge(&G, 2, 3); addEdge(&G, 2, 4); addEdge(&G, 3, 5); if (topSort(&G, result) == ERROR) { printf("存在环路\n"); } else { printf("拓扑排序结果:\n"); for (int i = 0; i < vexNum; i++) { printf("%d ", result[i]); } printf("\n"); } return 0; } ``` 以上代码中,`initGraph()`函数用于初始化图,`addEdge()`函数用于添加边,`topSort()`函数用于执行拓扑排序,并将排序结果存储在`result`数组中。在`topSort()`函数中,我们使用队列来存储入度为0的顶点,并依次从队列中取出顶点来构建排序序列。如果排序序列的长度不等于顶点数,则说明存在环路,返回错误标识。否则,将排序序列输出即可。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

木鳶戾天

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

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

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

打赏作者

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

抵扣说明:

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

余额充值