数据结构(三十五)拓扑排序
一、拓扑排序的定义
1.AOV网:在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,称为AOV网(Activity On Vertex Network)。
2.拓扑序列:设G={V,E}是一个具有n个顶点的有向图,V中的顶点序列v1,v2,...,vn,满足若从顶点vi到vj有一条路径,则在顶点序列中顶点vi必在vj之前,这样的顶点序列为一个拓扑序列。
3.拓扑排序:拓扑排序是对一个有向图构造拓扑序列的过程。构造时会有两个结果,如果此网的全部顶点都被输出,则说明它是不存在环(回路)的AOV网;如果输出的顶点少了,说明这个网存在环(回路),不是AOV网。
例如,
这样的AOV拓扑序列不止一条。序列v0v1v2v3v4v5v6v7v8v9v10v11v12v13v14v15v16是一个拓扑序列,序列v0v1v4v3v2v7v6v5v8v10v9v12v11v14v13v15v16。
二、拓扑排序算法
1.拓扑排序算法的基本思路:
从AOV网中选择一个入度为0的顶点输出,然后删去此顶点,并删除以此顶点为尾的弧,继续重复此步骤,直到输出全部顶点或者AOV网中不存在入度为0的顶点为止。
2.由于拓扑排序的过程中,需要删除顶点,显然用邻接表更加方便,因此需要创建一个邻接表。同时算法过程中始终要查找入度为0的顶点,因此在原来的顶点表结构中,增加一个入度域in,得到的顶点结点结构和边结点结构就是下面这样。
- 顶点表结点结构
package bigjun.iplab.topologicalSort; /** * 图的邻接表存储结构中的顶点结点类 */ public class VertexNode { public int in; // 顶点的入度 public Object data; // 顶点的符号信息 public EdgeNode firstEdge; // 指向第一条依附于该顶点的弧 public VertexNode(int in, Object data) { this(in, data, null); } public VertexNode(int in, Object data, EdgeNode firstEdge) { this.in = in; this.data = data; this.firstEdge = firstEdge; } }
- 边表结点结构
package bigjun.iplab.topologicalSort; /** * 用于拓扑排序算法中的邻接表存储结构中的边(或弧)结点类 */ public class EdgeNode { public int adjVex; // 该弧所指向的顶点在顶点数组中的下标 public EdgeNode nextEdge; // 指向下一条表示边或弧的结点类 public EdgeNode(int adjVex) { this(adjVex, null); } public EdgeNode(int adjVex, EdgeNode nextEdge) { this.adjVex = adjVex; this.nextEdge = nextEdge; } }
3.拓扑排序算法的C语言实现
#include "stdio.h" #include "stdlib.h" #include "io.h" #include "math.h" #include "time.h" #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXEDGE 20 #define MAXVEX 14 #define INFINITY 65535 typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ /* 邻接矩阵结构 */ typedef struct { int vexs[MAXVEX]; int arc[MAXVEX][MAXVEX]; int numVertexes, numEdges; }MGraph; /* 邻接表结构****************** */ typedef struct EdgeNode /* 边表结点 */ { int adjvex; /* 邻接点域,存储该顶点对应的下标 */ int weight; /* 用于存储权值,对于非网图可以不需要 */ struct EdgeNode *next; /* 链域,指向下一个邻接点 */ }EdgeNode; typedef struct VertexNode /* 顶点表结点 */ { int in; /* 顶点入度 */ int data; /* 顶点域,存储顶点信息 */ EdgeNode *firstedge;/* 边表头指针 */ }VertexNode, AdjList[MAXVEX]; typedef struct { AdjList adjList; int numVertexes,numEdges; /* 图中当前顶点数和边数 */ }graphAdjList,*GraphAdjList; /* **************************** */ void CreateMGraph(MGraph *G)/* 构件图 */ { int i, j; /* printf("请输入边数和顶点数:"); */ G->numEdges=MAXEDGE; G->numVertexes=MAXVEX; for (i = 0; i < G->numVertexes; i++)/* 初始化图 */ { G->vexs[i]=i; } for (i = 0; i < G->numVertexes; i++)/* 初始化图 */ { for ( j = 0; j < G->numVertexes; j++) { G->arc[i][j]=0; } } G->arc[0][4]=1; G->arc[0][5]=1; G->arc[0][11]=1; G->arc[1][2]=1; G->arc[1][4]=1; G->arc[1][8]=1; G->arc[2][5]=1; G->arc[2][6]=1; G->arc[2][9]=1; G->arc[3][2]=1; G->arc[3][13]=1; G->arc[4][7]=1; G->arc[5][8]=1; G->arc[5][12]=1; G->arc[6][5]=1; G->arc[8][7]=1; G->arc[9][10]=1; G->arc[9][11]=1; G->arc[10][13]=1; G->arc[12][9]=1; } /* 利用邻接矩阵构建邻接表 */ void CreateALGraph(MGraph G,GraphAdjList *GL) { int i,j; EdgeNode *e; *GL = (GraphAdjList)malloc(sizeof(graphAdjList)); (*GL)->numVertexes=G.numVertexes; (*GL)->numEdges=G.numEdges; for(i= 0;i <G.numVertexes;i++) /* 读入顶点信息,建立顶点表 */ { (*GL)->adjList[i].in=0; (*GL)->adjList[i].data=G.vexs[i]; (*GL)->adjList[i].firstedge=NULL; /* 将边表置为空表 */ } for(i=0;i<G.numVertexes;i++) /* 建立边表 */ { for(j=0;j<G.numVertexes;j++) { if (G.arc[i][j]==1) { e=(EdgeNode *)malloc(sizeof(EdgeNode)); e->adjvex=j; /* 邻接序号为j */ e->next=(*GL)->adjList[i].firstedge; /* 将当前顶点上的指向的结点指针赋值给e */ (*GL)->adjList[i].firstedge=e; /* 将当前顶点的指针指向e */ (*GL)->adjList[j].in++; } } } } /* 拓扑排序,若GL无回路,则输出拓扑排序序列并返回1,若有回路返回0。 */ Status TopologicalSort(GraphAdjList GL) { EdgeNode *e; int i,k,gettop; int top=0; /* 用于栈指针下标 */ int count=0;/* 用于统计输出顶点的个数 */ int *stack; /* 建栈将入度为0的顶点入栈 */ stack=(int *)malloc(GL->numVertexes * sizeof(int) ); for(i = 0; i<GL->numVertexes; i++) if(0 == GL->adjList[i].in) /* 将入度为0的顶点入栈 */ stack[++top]=i; while(top!=0) { gettop=stack[top--]; printf("%d -> ",GL->adjList[gettop].data); count++; /* 输出i号顶点,并计数 */ for(e = GL->adjList[gettop].firstedge; e; e = e->next) { k=e->adjvex; if( !(--GL->adjList[k].in) ) /* 将i号顶点的邻接点的入度减1,如果减1后为0,则入栈 */ stack[++top]=k; } } printf("\n"); if(count < GL->numVertexes) return ERROR; else return OK; } int main(void) { MGraph G; GraphAdjList GL; int result; CreateMGraph(&G); CreateALGraph(G,&GL); result=TopologicalSort(GL); printf("result:%d",result); return 0; }
4.拓扑排序算法的Java语言实现
实现类:
package bigjun.iplab.topologicalSort; import bigjun.iplab.linkStack.LinkStack; public class TopologicalSort { public static void TopoSort(DirectedNetwork G) throws Exception { int count = 0; // 统计输出顶点的个数 LinkStack S = new LinkStack(); for (int i = 0; i < G.getVexNum(); i++) {// 将入度为0的顶点入栈 if (G.getVexs()[i].in == 0) { S.stackPush(i); } } System.out.println("对AOV网进行拓扑排序得到打印结果为: "); while (!S.isStackEmpty()) { int i = (int) S.stackPop(); System.out.print(G.getVex(i) + "->"); count++; // 遍历顶点Vi的边链表,将每一个弧头对应的顶点的入度减1,也就是把Vi和连接自己的弧断开 for (EdgeNode edge = G.getVexs()[i].firstEdge; edge != null; edge = edge.nextEdge) { int k = edge.adjVex; if (--G.vexs[k].in == 0) { S.stackPush(k); } } if (count > G.getVexNum()) throw new Exception("这是一个有回环的图!"); } System.out.println("Over!"); } }
测试代码(以下图的AOV网为例):
public static DirectedNetwork createDN_ForTopologicalSort() { EdgeNode e_0_4 = new EdgeNode(4); EdgeNode e_0_5 = new EdgeNode(5, e_0_4); EdgeNode e_0_11 = new EdgeNode(11, e_0_5); VertexNode v0 = new VertexNode(0, "V0", e_0_11); EdgeNode e_1_2 = new EdgeNode(2); EdgeNode e_1_4 = new EdgeNode(4, e_1_2); EdgeNode e_1_8 = new EdgeNode(8, e_1_4); VertexNode v1 = new VertexNode(0, "V1", e_1_8); EdgeNode e_2_5 = new EdgeNode(5); EdgeNode e_2_6 = new EdgeNode(6, e_2_5); EdgeNode e_2_9 = new EdgeNode(9, e_2_6); VertexNode v2 = new VertexNode(2, "V2", e_2_9); EdgeNode e_3_2 = new EdgeNode(2); EdgeNode e_3_13 = new EdgeNode(13, e_3_2); VertexNode v3 = new VertexNode(0, "V3", e_3_13); EdgeNode e_4_7 = new EdgeNode(7); VertexNode v4 = new VertexNode(2, "V4", e_4_7); EdgeNode e_5_12 = new EdgeNode(12); EdgeNode e_5_8 = new EdgeNode(8, e_5_12); VertexNode v5 = new VertexNode(3, "V5", e_5_8); EdgeNode e_6_5 = new EdgeNode(5); VertexNode v6 = new VertexNode(1, "V6", e_6_5); VertexNode v7 = new VertexNode(2, "V7"); EdgeNode e_8_7 = new EdgeNode(7); VertexNode v8 = new VertexNode(2, "V8", e_8_7); EdgeNode e_9_11 = new EdgeNode(11); EdgeNode e_9_10 = new EdgeNode(10, e_9_11); VertexNode v9 = new VertexNode(1, "V9", e_9_10); EdgeNode e_10_13 = new EdgeNode(13); VertexNode v10 = new VertexNode(1, "V10", e_10_13); VertexNode v11 = new VertexNode(2, "V11"); EdgeNode e_12_9 = new EdgeNode(12); VertexNode v12 = new VertexNode(1, "V12", e_12_9); VertexNode v13 = new VertexNode(2, "V13"); VertexNode[] vexs = {v0,v1,v2,v3,v4,v5,v6,v7,v8,v9,v10,v11,v12,v13}; int vertexNum = vexs.length; int edgeNum = 20; return new DirectedNetwork(vertexNum, edgeNum, vexs); } public static void main(String[] args) throws Exception { DirectedNetwork DN_ForTopoSort = createDN_ForTopologicalSort(); TopologicalSort.TopoSort(DN_ForTopoSort); }
输出:
对AOV网进行拓扑排序得到打印结果为:
V3->V1->V2->V6->V9->V10->V13->V0->V4->V5->V12->V8->V7->V11->Over!
结合例子分析代码执行过程:
初始化,count=0,遍历所有的顶点结点,将入度为0的结点的数组下标入栈,即S:0,1,3 然后进入while循环, -栈不为空,3出栈i等于3,打印V3,count=1,进入for循环,遍历v3的弧链表,k=13,将顶点v13的入度减1变为1,k=2,将顶点v2的入度减1变为1,将顶点v3上的弧删除
-栈不为空,1出栈i等于1,打印V1,count=2,进入for循环,遍历v1的弧链表,k=8,v8入度为1,k=4,v4入度为1,k=2,v2入度为0,将v2入栈,即S:2,0。将顶点v1上的弧删除
-其余同理
总结来说,为了保证弧尾的顶点在弧头之前被访问,拓扑排序算法的过程就是,先找入度为0的顶点(即最开始的弧尾),首先访问,访问完之后,将对应弧另一端的顶点的入度减1,并将这个弧删除,并继续找入度为0的点。