1. 什么是拓扑排序
设G=(V,E)是一个具有n个顶点的有向图,若< vi,vj >是图中的边(即从顶点vi到vj有一条路径),则顶点vi必须排在顶点vj之前,那么满足这个条件的顶点序列就是拓扑序列。在一个有向图中找一个拓扑序列的过程就称为拓扑排序。
我们通过一个例子来举例说明。
图1
比如,计算机专业的学生必须完成一系列规定的基础课和专业课才能毕业,有一些课程会有一些先修课程。例如高等数学和程序设计这两门课程来说,是没有先修课的,而对于数据结构这门课程来1说,在学习这门课之前必须先修程序设计和离散数学两门课,而在学习离散数学时,还需要先修高等数学课程。
对于课程之间的先后关系用一个有向图来表示:
图2
从图2中可以看出,一个有向图的拓扑排序可以有好几种
,比如< C0,C2 >有一条边,那么C0就必须在C2的前面,对于< C1,C6 >有一条边,那么C1必须在C6的前面。而对于C0和C1之间没有边的,那么C0和C1谁在前,谁在后都可以的。
2. 数据的存储结构
图3
有向图的存储结构定义:
typedef struct ANode
{
int adjvex;
InfoType info;
struct ANode *nextarc;
} ArcNode; //边表节点类型
typedef struct Vnode
{
Vertex data;
int count; //存放顶点入度
ArcNode *firstarc;
} VNode; //表头节点类型
typedef VNode AdjList[MAXV];
typedef struct
{
AdjList adjlist;
int n,e;
} ALGraph; //完整的图邻接表类型
3. 拓扑排序步骤
1 . 从有向图中选择一个没有前驱(即入度为0)的顶点并且输出它。
2 . 从图中删去该顶点,并且删去从该顶点发出的全部有向边。
3 . 重复上述两步,直到剩余的网中不再存在没有前驱的顶点为止。
图4
如图4所示,对于图G来说,C0和C1的入度都是为0的,这里我们选择C0开始,把顶点C0删除掉,并删除从C0出发的所有出边。
图5
如图5所示,然后选择C2开始,把C2顶点删除掉,并删除从C2出发的所有出边。
图6
如图6所示,再选择C1开始,把C1顶点删除掉,并删除从C1出发的所有出边。
图7
再选择C6开始,把C6顶点删除掉,并删除从C6出发的所有出边。
图8
再选择C3开始,把C3顶点删除掉,并删除从C3出发的所有出边。
图9
最后依次选择C5,C4顶点全部都删除掉。
4. 拓扑排序算法
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXV 7
typedef int InfoType;
typedef struct ANode
{
int adjvex;
InfoType info;
struct ANode *nextarc;
} ArcNode; //边表节点类型
typedef int Vertex;
typedef struct Vnode
{
Vertex data;
int count; //存放顶点入度
ArcNode *firstarc;
} VNode; //表头节点类型
typedef VNode AdjList[MAXV];
typedef struct
{
AdjList adjlist;
int n,e;
} ALGraph; //完整的图邻接表类型
//图的定义:邻接矩阵
typedef struct MGRAPH{
int n; //顶点数
int e; //边数
int deges[MAXV][MAXV]; //邻接矩阵
} MGraph;
/*
将邻接矩阵转换成邻接表
MGraph *g:表示邻接矩阵
*/
ALGraph *MatToList(MGraph *g)
{
int i;
int j;
ArcNode *p = NULL;
ALGraph *G = (ALGraph *)malloc(sizeof(ALGraph));
//给所有头节点的指针域置初值
for(i = 0; i < g->n; i++)
{
G->adjlist[i].firstarc = NULL;
}
//根据邻接矩阵建立邻接表中节点
for(i = 0; i < g->n; i++)
{
for(j = g->n - 1; j >= 0; j--)
{
if(g->deges[i][j] != 0)
{
p = (ArcNode *)malloc(sizeof(ArcNode));
p->adjvex = j;
//采用头插法,插入邻接表
p->nextarc = G->adjlist[i].firstarc;
G->adjlist[i].firstarc = p;
}
}
}
G->n = g->n;
G->e = g->e;
return G;
}
//拓扑排序算法
void TopSort(ALGraph *G)
{
int i,j;
int top;
ArcNode *p;
//先初始化
for (i=0; i<G->n; i++)
{
G->adjlist[i].count=0;
}
//求所有顶点的入度
for (i=0; i<G->n; i++)
{
p=G->adjlist[i].firstarc;
while (p!=NULL)
{
G->adjlist[p->adjvex].count++;
p=p->nextarc;
}
}
//入栈度为0的顶点
int St[MAXV];
top=-1;
for (i=0; i<G->n; i++)
{
if (G->adjlist[i].count==0)
{
top++;
St[top]=i;
}
}
while (top>-1) //栈不为空时循环
{
//出栈并输出
i=St[top];
top--;
printf("%d ",i);
//"删除"顶点,同时把相邻顶点入度均减1
//比如在删除顶点1来说,需要把顶点3,4,6的入度都-1
p=G->adjlist[i].firstarc;
while (p!=NULL)
{ //记录相邻的顶点
j=p->adjvex;
//并把相邻的顶点的入度-1
G->adjlist[j].count--;
//如果该相邻的顶点入度减一后为零的话,则需要入栈
if (G->adjlist[j].count==0)
{
top++;
St[top]=j;
}
//找下一个相邻的顶点
p=p->nextarc;
}
}
}
int main(void)
{
int A[7][7] = {
{0,0,1,0,0,0,0},
{0,0,0,1,1,0,1},
{0,0,0,1,0,0,0},
{0,0,0,0,1,1,0},
{0,0,0,0,0,0,0},
{0,0,0,0,0,0,0},
{0,0,0,0,0,1,0}
};
int i;
int j;
MGraph g;
ALGraph *ag = NULL;
g.e = 8;
g.n = 7;
//转换为邻接矩阵
for(i = 0; i < g.n; i++)
{
for(j = 0; j < g.n; j++)
{
g.deges[i][j] = A[i][j];
}
}
//转换为邻接表
ag = MatToList(&g);
printf("\n拓扑排序: ");
//拓扑排序
TopSort(ag);
printf("\n\n");
return 0;
}
测试结果: