18、【图】拓扑排序

一、拓扑排序介绍

拓扑排序(Topological Order)是指,将一个有向无环图(Directed Acyclic Graph简称DAG)进行排序进而得到一个有序的线性序列。

这样说,可能理解起来比较抽象。下面通过简单的例子进行说明!
例如,一个项目包括A、B、C、D四个子部分来完成,并且A依赖于B和D,C依赖于D。现在要制定一个计划,写出A、B、C、D的执行顺序。这时,就可以利用到拓扑排序,它就是用来确定事物发生的顺序的。

在拓扑排序中,如果存在一条从顶点A到顶点B的路径,那么在排序结果中B出现在A的后面。

二、拓扑排序的算法图解

拓扑排序算法的基本步骤:

1. 构造一个队列Q(queue) 和 拓扑排序的结果队列T(topological);
2. 把所有没有依赖顶点的节点放入Q;
3. 当Q还有顶点的时候,执行下面步骤:
3.1 从Q中取出一个顶点n(将n从Q中删掉),并放入T(将n加入到结果集中);
3.2 对n每一个邻接点m(n是起点,m是终点);
3.2.1 去掉边<n,m>;
3.2.2 如果m没有依赖顶点,则把m放入Q;
注:顶点A没有依赖顶点,是指不存在以A为终点的边。

以上图为例,来对拓扑排序进行演示。

第1步:将B和C加入到排序结果中。
    顶点B和顶点C都是没有依赖顶点,因此将C和C加入到结果集T中。假设ABCDEFG按顺序存储,因此先访问B,再访问C。访问B之后,去掉边<B,A>和<B,D>,并将A和D加入到队列Q中。同样的,去掉边<C,F>和<C,G>,并将F和G加入到Q中。
    (01) 将B加入到排序结果中,然后去掉边<B,A>和<B,D>;此时,由于A和D没有依赖顶点,因此并将A和D加入到队列Q中。
    (02) 将C加入到排序结果中,然后去掉边<C,F>和<C,G>;此时,由于F有依赖顶点D,G有依赖顶点A,因此不对F和G进行处理。
第2步:将A,D依次加入到排序结果中。
    第1步访问之后,A,D都是没有依赖顶点的,根据存储顺序,先访问A,然后访问D。访问之后,删除顶点A和顶点D的出边。
第3步:将E,F,G依次加入到排序结果中。

因此访问顺序是:B -> C -> A -> D -> E -> F -> G

三、拓扑排序的代码说明

拓扑排序是对有向无向图的排序。下面以邻接表实现的有向图来对拓扑排序进行说明。

1. 基本定义

 1 #define MAX 100
 2 // 邻接表
 3 class ListDG
 4 {
 5     private: // 内部类
 6         // 邻接表中表对应的链表的顶点
 7         class ENode
 8         {
 9             int ivex;           // 该边所指向的顶点的位置
10             ENode *nextEdge;    // 指向下一条弧的指针
11             friend class ListDG;
12         };
13 
14         // 邻接表中表的顶点
15         class VNode
16         {
17             char data;          // 顶点信息
18             ENode *firstEdge;   // 指向第一条依附该顶点的弧
19             friend class ListDG;
20         };
21 
22     private: // 私有成员
23         int mVexNum;             // 图的顶点的数目
24         int mEdgNum;             // 图的边的数目
25         VNode *mVexs;            // 图的顶点数组
26 
27     public:
28         // 创建邻接表对应的图(自己输入)
29         ListDG();
30         // 创建邻接表对应的图(用已提供的数据)
31         ListDG(char vexs[], int vlen, char edges[][2], int elen);
32         ~ListDG();
33 
34         // 深度优先搜索遍历图
35         void DFS();
36         // 广度优先搜索(类似于树的层次遍历)
37         void BFS();
38         // 打印邻接表图
39         void print();
40         // 拓扑排序
41         int topologicalSort();
42 
43     private:
44         // 读取一个输入字符
45         char readChar();
46         // 返回ch的位置
47         int getPosition(char ch);
48         // 深度优先搜索遍历图的递归实现
49         void DFS(int i, int *visited);
50         // 将node节点链接到list的最后
51         void linkLast(ENode *list, ENode *node);
52 };

(1) ListDG是邻接表对应的结构体。 mVexNum是顶点数,mEdgNum是边数;mVexs则是保存顶点信息的一维数组。
(2) VNode是邻接表顶点对应的结构体。 data是顶点所包含的数据,而firstEdge是该顶点所包含链表的表头指针。
(3) ENode是邻接表顶点所包含的链表的节点对应的结构体。 ivex是该节点所对应的顶点在vexs中的索引,而nextEdge是指向下一个节点的。

2. 拓扑排序

 1 /*
 2  * 拓扑排序
 3  *
 4  * 返回值:
 5  *     -1 -- 失败(由于内存不足等原因导致)
 6  *      0 -- 成功排序,并输入结果
 7  *      1 -- 失败(该有向图是有环的)
 8  */
 9 int ListDG::topologicalSort()
10 {
11     int i,j;
12     int index = 0;
13     int head = 0;           // 辅助队列的头
14     int rear = 0;           // 辅助队列的尾
15     int *queue;             // 辅组队列
16     int *ins;               // 入度数组
17     char *tops;             // 拓扑排序结果数组,记录每个节点的排序后的序号。
18     ENode *node;
19 
20     ins   = new int[mVexNum];
21     queue = new int[mVexNum];
22     tops  = new char[mVexNum];
23     memset(ins, 0, mVexNum*sizeof(int));
24     memset(queue, 0, mVexNum*sizeof(int));
25     memset(tops, 0, mVexNum*sizeof(char));
26 
27     // 统计每个顶点的入度数
28     for(i = 0; i < mVexNum; i++)
29     {
30         node = mVexs[i].firstEdge;
31         while (node != NULL)
32         {
33             ins[node->ivex]++;
34             node = node->nextEdge;
35         }
36     }
37 
38     // 将所有入度为0的顶点入队列
39     for(i = 0; i < mVexNum; i ++)
40         if(ins[i] == 0)
41             queue[rear++] = i;          // 入队列
42 
43     while (head != rear)                // 队列非空
44     {
45         j = queue[head++];              // 出队列。j是顶点的序号
46         tops[index++] = mVexs[j].data;  // 将该顶点添加到tops中,tops是排序结果
47         node = mVexs[j].firstEdge;      // 获取以该顶点为起点的出边队列
48 
49         // 将与"node"关联的节点的入度减1;
50         // 若减1之后,该节点的入度为0;则将该节点添加到队列中。
51         while(node != NULL)
52         {
53             // 将节点(序号为node->ivex)的入度减1。
54             ins[node->ivex]--;
55             // 若节点的入度为0,则将其"入队列"
56             if( ins[node->ivex] == 0)
57                 queue[rear++] = node->ivex;  // 入队列
58 
59             node = node->nextEdge;
60         }
61     }
62 
63     if(index != mVexNum)
64     {
65         cout << "Graph has a cycle" << endl;
66         delete queue;
67         delete ins;
68         delete tops;
69         return 1;
70     }
71 
72     // 打印拓扑排序结果
73     cout << "== TopSort: ";
74     for(i = 0; i < mVexNum; i ++)
75         cout << tops[i] << " ";
76     cout << endl;
77 
78     delete queue;
79     delete ins;
80     delete tops;
81 
82     return 0;
83 }

说明:
(01) queue的作用就是用来存储没有依赖顶点的顶点。它与前面所说的Q相对应。
(02) tops的作用就是用来存储排序结果。它与前面所说的T相对应。

三、拓扑排序的C++实现

  1 /**
  2  * C++: 无回路有向图(Directed Acyclic Graph)的拓扑排序
  3  *      该DAG图是通过邻接表实现的。    7  */
  8 
  9 #include <iomanip>
 10 #include <iostream>
 11 #include <vector>
 12 #include <cstring>
 13 using namespace std;
 14 
 15 #define MAX 100
 16 // 邻接表
 17 class ListDG
 18 {
 19     private: // 内部类
 20         // 邻接表中表对应的链表的顶点
 21         class ENode
 22         {
 23             int ivex;           // 该边所指向的顶点的位置
 24             ENode *nextEdge;    // 指向下一条弧的指针
 25             friend class ListDG;
 26         };
 27 
 28         // 邻接表中表的顶点
 29         class VNode
 30         {
 31             char data;          // 顶点信息
 32             ENode *firstEdge;   // 指向第一条依附该顶点的弧
 33             friend class ListDG;
 34         };
 35 
 36     private: // 私有成员
 37         int mVexNum;             // 图的顶点的数目
 38         int mEdgNum;             // 图的边的数目
 39         VNode *mVexs;            // 图的顶点数组
 40 
 41     public:
 42         // 创建邻接表对应的图(自己输入)
 43         ListDG();
 44         // 创建邻接表对应的图(用已提供的数据)
 45         ListDG(char vexs[], int vlen, char edges[][2], int elen);
 46         ~ListDG();
 47 
 48         // 深度优先搜索遍历图
 49         void DFS();
 50         // 广度优先搜索(类似于树的层次遍历)
 51         void BFS();
 52         // 打印邻接表图
 53         void print();
 54         // 拓扑排序
 55         int topologicalSort();
 56 
 57     private:
 58         // 读取一个输入字符
 59         char readChar();
 60         // 返回ch的位置
 61         int getPosition(char ch);
 62         // 深度优先搜索遍历图的递归实现
 63         void DFS(int i, int *visited);
 64         // 将node节点链接到list的最后
 65         void linkLast(ENode *list, ENode *node);
 66 };
 67 
 68 /*
 69  * 创建邻接表对应的图(自己输入)
 70  */
 71 ListDG::ListDG()
 72 {
 73     char c1, c2;
 74     int v, e;
 75     int i, p1, p2;
 76     ENode *node1, *node2;
 77 
 78     // 输入"顶点数"和"边数"
 79     cout << "input vertex number: ";
 80     cin >> mVexNum;
 81     cout << "input edge number: ";
 82     cin >> mEdgNum;
 83     if ( mVexNum < 1 || mEdgNum < 1 || (mEdgNum > (mVexNum * (mVexNum-1))))
 84     {
 85         cout << "input error: invalid parameters!" << endl;
 86         return ;
 87     }
 88  
 89     // 初始化"邻接表"的顶点
 90     mVexs = new VNode[mVexNum];
 91     for(i=0; i<mVexNum; i++)
 92     {
 93         cout << "vertex(" << i << "): ";
 94         mVexs[i].data = readChar();
 95         mVexs[i].firstEdge = NULL;
 96     }
 97 
 98     // 初始化"邻接表"的边
 99     for(i=0; i<mEdgNum; i++)
100     {
101         // 读取边的起始顶点和结束顶点
102         cout << "edge(" << i << "): ";
103         c1 = readChar();
104         c2 = readChar();
105 
106         p1 = getPosition(c1);
107         p2 = getPosition(c2);
108         // 初始化node1
109         node1 = new ENode();
110         node1->ivex = p2;
111         // 将node1链接到"p1所在链表的末尾"
112         if(mVexs[p1].firstEdge == NULL)
113           mVexs[p1].firstEdge = node1;
114         else
115             linkLast(mVexs[p1].firstEdge, node1);
116     }
117 }
118 
119 /*
120  * 创建邻接表对应的图(用已提供的数据)
121  */
122 ListDG::ListDG(char vexs[], int vlen, char edges[][2], int elen)
123 {
124     char c1, c2;
125     int i, p1, p2;
126     ENode *node1, *node2;
127 
128     // 初始化"顶点数"和"边数"
129     mVexNum = vlen;
130     mEdgNum = elen;
131     // 初始化"邻接表"的顶点
132     mVexs = new VNode[mVexNum];
133     for(i=0; i<mVexNum; i++)
134     {
135         mVexs[i].data = vexs[i];
136         mVexs[i].firstEdge = NULL;
137     }
138 
139     // 初始化"邻接表"的边
140     for(i=0; i<mEdgNum; i++)
141     {
142         // 读取边的起始顶点和结束顶点
143         c1 = edges[i][0];
144         c2 = edges[i][1];
145 
146         p1 = getPosition(c1);
147         p2 = getPosition(c2);
148         // 初始化node1
149         node1 = new ENode();
150         node1->ivex = p2;
151         // 将node1链接到"p1所在链表的末尾"
152         if(mVexs[p1].firstEdge == NULL)
153           mVexs[p1].firstEdge = node1;
154         else
155             linkLast(mVexs[p1].firstEdge, node1);
156     }
157 }
158 
159 /* 
160  * 析构函数
161  */
162 ListDG::~ListDG() 
163 {
164     ENode *node;
165 
166     for(int i=0; i<mEdgNum; i++)
167     {
168         node = mVexs[i].firstEdge;
169         while (node != NULL)
170         {
171             delete node;
172             node = node->nextEdge;
173         }
174     }
175 
176     delete[] mVexs;
177 }
178 
179 /*
180  * 将node节点链接到list的最后
181  */
182 void ListDG::linkLast(ENode *list, ENode *node)
183 {
184     ENode *p = list;
185 
186     while(p->nextEdge)
187         p = p->nextEdge;
188     p->nextEdge = node;
189 }
190 
191 
192 /*
193  * 返回ch的位置
194  */
195 int ListDG::getPosition(char ch)
196 {
197     int i;
198     for(i=0; i<mVexNum; i++)
199         if(mVexs[i].data==ch)
200             return i;
201     return -1;
202 }
203 
204 /*
205  * 读取一个输入字符
206  */
207 char ListDG::readChar()
208 {
209     char ch;
210 
211     do {
212         cin >> ch;
213     } while(!((ch>='a'&&ch<='z') || (ch>='A'&&ch<='Z')));
214 
215     return ch;
216 }
217 
218 
219 /*
220  * 深度优先搜索遍历图的递归实现
221  */
222 void ListDG::DFS(int i, int *visited)
223 {
224     ENode *node;
225 
226     visited[i] = 1;
227     cout << mVexs[i].data << " ";
228     node = mVexs[i].firstEdge;
229     while (node != NULL)
230     {
231         if (!visited[node->ivex])
232             DFS(node->ivex, visited);
233         node = node->nextEdge;
234     }
235 }
236 
237 /*
238  * 深度优先搜索遍历图
239  */
240 void ListDG::DFS()
241 {
242     int i;
243     int *visited;       // 顶点访问标记
244 
245     visited = new int[mVexNum];
246     // 初始化所有顶点都没有被访问
247     for (i = 0; i < mVexNum; i++)
248         visited[i] = 0;
249 
250     cout << "== DFS: ";
251     for (i = 0; i < mVexNum; i++)
252     {
253         if (!visited[i])
254             DFS(i, visited);
255     }
256     cout << endl;
257 
258     delete[] visited;
259 }
260 
261 /*
262  * 广度优先搜索(类似于树的层次遍历)
263  */
264 void ListDG::BFS()
265 {
266     int head = 0;
267     int rear = 0;
268     int *queue;     // 辅组队列
269     int *visited;   // 顶点访问标记
270     int i, j, k;
271     ENode *node;
272 
273     queue = new int[mVexNum];
274     visited = new int[mVexNum];
275     for (i = 0; i < mVexNum; i++)
276         visited[i] = 0;
277 
278     cout << "== BFS: ";
279     for (i = 0; i < mVexNum; i++)
280     {
281         if (!visited[i])
282         {
283             visited[i] = 1;
284             cout << mVexs[i].data << " ";
285             queue[rear++] = i;  // 入队列
286         }
287         while (head != rear) 
288         {
289             j = queue[head++];  // 出队列
290             node = mVexs[j].firstEdge;
291             while (node != NULL)
292             {
293                 k = node->ivex;
294                 if (!visited[k])
295                 {
296                     visited[k] = 1;
297                     cout << mVexs[k].data << " ";
298                     queue[rear++] = k;
299                 }
300                 node = node->nextEdge;
301             }
302         }
303     }
304     cout << endl;
305 
306     delete[] visited;
307     delete[] queue;
308 }
309 
310 /*
311  * 打印邻接表图
312  */
313 void ListDG::print()
314 {
315     int i,j;
316     ENode *node;
317 
318     cout << "== List Graph:" << endl;
319     for (i = 0; i < mVexNum; i++)
320     {
321         cout << i << "(" << mVexs[i].data << "): ";
322         node = mVexs[i].firstEdge;
323         while (node != NULL)
324         {
325             cout << node->ivex << "(" << mVexs[node->ivex].data << ") ";
326             node = node->nextEdge;
327         }
328         cout << endl;
329     }
330 }
331 
332 /*
333  * 拓扑排序
334  *
335  * 返回值:
336  *     -1 -- 失败(由于内存不足等原因导致)
337  *      0 -- 成功排序,并输入结果
338  *      1 -- 失败(该有向图是有环的)
339  */
340 int ListDG::topologicalSort()
341 {
342     int i,j;
343     int index = 0;
344     int head = 0;           // 辅助队列的头
345     int rear = 0;           // 辅助队列的尾
346     int *queue;             // 辅组队列
347     int *ins;               // 入度数组
348     char *tops;             // 拓扑排序结果数组,记录每个节点的排序后的序号。
349     ENode *node;
350 
351     ins   = new int[mVexNum];
352     queue = new int[mVexNum];
353     tops  = new char[mVexNum];
354     memset(ins, 0, mVexNum*sizeof(int));
355     memset(queue, 0, mVexNum*sizeof(int));
356     memset(tops, 0, mVexNum*sizeof(char));
357 
358     // 统计每个顶点的入度数
359     for(i = 0; i < mVexNum; i++)
360     {
361         node = mVexs[i].firstEdge;
362         while (node != NULL)
363         {
364             ins[node->ivex]++;
365             node = node->nextEdge;
366         }
367     }
368 
369     // 将所有入度为0的顶点入队列
370     for(i = 0; i < mVexNum; i ++)
371         if(ins[i] == 0)
372             queue[rear++] = i;          // 入队列
373 
374     while (head != rear)                // 队列非空
375     {
376         j = queue[head++];              // 出队列。j是顶点的序号
377         tops[index++] = mVexs[j].data;  // 将该顶点添加到tops中,tops是排序结果
378         node = mVexs[j].firstEdge;      // 获取以该顶点为起点的出边队列
379 
380         // 将与"node"关联的节点的入度减1;
381         // 若减1之后,该节点的入度为0;则将该节点添加到队列中。
382         while(node != NULL)
383         {
384             // 将节点(序号为node->ivex)的入度减1。
385             ins[node->ivex]--;
386             // 若节点的入度为0,则将其"入队列"
387             if( ins[node->ivex] == 0)
388                 queue[rear++] = node->ivex;  // 入队列
389 
390             node = node->nextEdge;
391         }
392     }
393 
394     if(index != mVexNum)
395     {
396         cout << "Graph has a cycle" << endl;
397         delete queue;
398         delete ins;
399         delete tops;
400         return 1;
401     }
402 
403     // 打印拓扑排序结果
404     cout << "== TopSort: ";
405     for(i = 0; i < mVexNum; i ++)
406         cout << tops[i] << " ";
407     cout << endl;
408 
409     delete queue;
410     delete ins;
411     delete tops;
412 
413     return 0;
414 }
415 
416 int main()
417 {
418     char vexs[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
419     char edges[][2] = {
420         {'A', 'G'}, 
421         {'B', 'A'}, 
422         {'B', 'D'}, 
423         {'C', 'F'}, 
424         {'C', 'G'}, 
425         {'D', 'E'}, 
426         {'D', 'F'}}; 
427     int vlen = sizeof(vexs)/sizeof(vexs[0]);
428     int elen = sizeof(edges)/sizeof(edges[0]);
429     ListDG* pG;
430 
431     // 自定义"图"(输入矩阵队列)
432     //pG = new ListDG();
433     // 采用已有的"图"
434     pG = new ListDG(vexs, vlen, edges, elen);
435 
436     pG->print();   // 打印图
437     //pG->DFS();     // 深度优先遍历
438     //pG->BFS();     // 广度优先遍历
439     pG->topologicalSort();     // 拓扑排序
440 
441     return 0;
442 }
原文地址:https://www.cnblogs.com/Long-w/p/9788478.html