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

 一.简介:

拓扑排序是什么?

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

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

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

二.代码部分:

//有向无环图的拓扑排序
#include
#include
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;ivexNum;i++)//初始化
	{
		inDegrees[i]=0;
	}
	for(int i=0;ivexNum;i++)
	{
		for(int j=0;jvexNum;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;ivexNum;i++)//入度为0的压栈
	{
		if(inDegrees[i]==0)
		{
			push(stack,i);
		}
	}
	while(!isEmpty(stack))//栈不为空,循环执行入度的减法(去掉输出顶点指向的下一个顶点的边)
	{
		int vexindex=pop(stack);//出栈的是顶点的下标
		top[index++]=vexindex;//保存顶点下标
		for(int j=0;jvexNum;j++)
		{
			if(G->arcs[vexindex][j])//下一个顶点有入度时减去
			{
				inDegrees[j]--;
				if(inDegrees[j]==0)//顶点入度减到0了直接入栈
				{
					push(stack,j);
				}
			}
		}
	}
	for(int i=0;ivexNum;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;ivexNum;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;ivexNum;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语言,数据结构,算法)