拓扑排序的原理及实现

算法 同时被 2 个专栏收录
52 篇文章 0 订阅
4 篇文章 0 订阅

拓扑排序,顾名思义,就是一种排序方法。这是一种什么排序?这种排序的作用?然后怎么去实现这种排序算法?现在就让我们仔细研究下。

1、什么是拓扑排序,也就是拓扑排序的概念

实际上,拓扑排序是一种图论算法,该算法在《数据结构与算法》一书中有涉猎。引用维基百科的定义:

在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(英语:Topological sorting)。
(1)每个顶点出现且只出现一次;
(2)若A在序列中排在B的前面,则在图中不存在从B到A的路径。
也可以定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得如果存在一条从顶点A到顶点B的路径,那么在排序中B出现在A的后面。

是不是觉得看完概念还是很晕的感觉,下面就用一个实例来讲具体的拓扑排序样例。


(a)有向图网(AOV)  (b)输出v6后       (c)输出v1后    (d)输出v4后 (e)输出v3后 (f)输出v2后

                                                           输出排序结果:v6-v1-v4-v3-v2-v5

此拓扑排序的思想是:

(1)从有向图中选取一个没有前驱的顶点,并输出之;

(2)从有向图中删去此顶点以及所有以它为尾的弧;

重复上述两步,直至图空,或者图不空但找不到无前驱的顶点为止。没有前驱 – 入度为零,删除顶点及以它为尾的弧– 弧头顶点的入度减1。

何谓入度?

我觉得得先明白什么是度?度(Degree):一个顶点的度是指与该顶点相关联的边的条数,顶点v的度记作d(v)。

入度:对于有向图来说,一个顶点的度可细分为入度和出度。一个顶点的入度是指与其关联的各边之中,以其为终点的边数。

出度:出度则是相对的概念,指以该顶点为起点的边数。

以v6这个顶点为例,它的入度为0,出度为2。

以v5这个顶点为例,它的入度为3,出度为0。

以v4这个顶点为例,它的入度为2,出度为1。

以v3这个顶点为例,它的入度为1,出度为2。

以v2这个顶点为例,它的入度为2,出度为0。

以v1这个顶点为例,它的入度为0,出度为3。

经验证,一个有向五环图中所有顶点的入度之和(0+3+2+1+2+0=8)等于所有顶点的出度之和(2+0+1+2+0+3=8)。

2、拓扑排序的作用

不禁有人就问了,有很多排序算法啊,快速排序,插值排序,这个排序到底有什么优点呢?平常这种排序又用于哪种场景呢?

我们说快速排序是不稳定的,这是因为最后的快排结果中相同元素的出现顺序和排序前不一致了。如果用偏序的概念可以这样解释这一现象:相同值的元素之间的关系是无法确定的。因此它们在最终的结果中的出现顺序可以是任意的。而对于诸如插入排序这种稳定性排序,它们对于值相同的元素,还有一个潜在的比较方式,即比较它们的出现顺序,出现靠前的元素大于出现后出现的元素。因此通过这一潜在的比较,将偏序关系转换为了全序关系,从而保证了结果的唯一性。而拓扑排序就是一种将偏序转换为全序的一种算法。

这里要补充两个概念,偏序和全序?

偏序:有向图中两个顶点之间不存在环路,至于连通与否,是无所谓的。

全序:就是在偏序的基础之上,有向无环图中的任意一对顶点还需要有明确的关系(反映在图中,就是单向连通的关系,注意不能双向连通,那就成环了)。

意思就是讲,一个不确定的偏序关系经全序后就有一种确定的先后顺序了。

既然有先后,那么在实际生活中的选课问题,比如大一时一定要修完这门课,大二才学第二门课,这种排课问题就是拓扑排序问题。

3、用C++实现拓扑排序算法

实例1:

以下程序参考:http://www.cppblog.com/abilitytao/archive/2009/04/01/78586.html
  1. <span style=“font-family:Comic Sans MS;”>#include<iostream>  
  2. #include<cmath>  
  3. #include<cstdio>  
  4. #include<algorithm>  
  5. #include<stack>  
  6. #include<memory.h>   
  7.   
  8. using namespace std;  
  9. #define MAX 9999  
  10.   
  11. stack<int>mystack;  
  12. int indegree[MAX];  
  13.   
  14. struct node   
  15. {  
  16.     int adjvex;  
  17.     node* next;  
  18. }adj[MAX];  
  19.   
  20. int Create(node adj[],int n,int m)//邻接表建表函数,n代表定点数,m代表边数  
  21. {  
  22.     int i;  
  23.     node *p;  
  24.     for(i=1;i<=n;i++)  
  25.     {  
  26.           
  27.         adj[i].adjvex=i;  
  28.         adj[i].next=NULL;  
  29.     }  
  30.     for(i=1;i<=m;i++)  
  31.     {  
  32.         cout<<“请输入第”<<i<<“条边:”;  
  33.         int u,v;  
  34.         cin>>u>>v;  
  35.         p=new node;  
  36.         p->adjvex=v;  
  37.         p->next=adj[u].next;  
  38.         adj[u].next=p;  
  39.     }  
  40.     return 1;  
  41. }  
  42.   
  43.   
  44. void print(int n)//邻接表打印函数  
  45. {  
  46.     int i;  
  47.     node *p;  
  48.     for(i=1;i<=n;i++)  
  49.     {  
  50.         p=&adj[i];  
  51.         while(p!=NULL)  
  52.         {  
  53.             cout<<p->adjvex<<‘ ’;  
  54.             p=p->next;  
  55.         }  
  56.         cout<<endl;  
  57.     }  
  58. }  
  59.   
  60. void topsort(node adj[],int n)  
  61. {  
  62.   
  63.     int i;  
  64.     node *p;  
  65.     memset(indegree,0,sizeof(indegree));  
  66.     for(i=1;i<=n;i++)  
  67.     {  
  68.   
  69.         p=adj[i].next;  
  70.         while(p!=NULL)  
  71.         {  
  72.             indegree[p->adjvex]++;  
  73.             p=p->next;  
  74.         }  
  75.     }  
  76.     for(i=1;i<=n;i++)  
  77.     {  
  78.   
  79.         if(indegree[i]==0)  
  80.             mystack.push(i);  
  81.     }  
  82.     int count=0;  
  83.     while(mystack.size()!=0)  
  84.     {  
  85.   
  86.         i=mystack.top();  
  87.         mystack.pop();  
  88.         cout<<i<<‘ ’;  
  89.         count++;  
  90.         for(p=adj[i].next;p!=NULL;p=p->next)  
  91.         {  
  92.             int k=p->adjvex;  
  93.             indegree[k]–;  
  94.             if(indegree[k]==0)  
  95.                 mystack.push(k);  
  96.         }  
  97.     }  
  98.     cout<<endl;  
  99.     if(count<n)cout<<“有回路”<<endl;  
  100. }  
  101.   
  102. int main()  
  103. {  
  104.     int n;  
  105.     int m;  
  106.     cout<<“请输入顶点数及边数:”;  
  107.     cin>>n>>m;  
  108.     Create(adj,n,m);  
  109.     cout<<“输入的邻接表为:”<<endl;  
  110.     print(n);  
  111.     cout<<“拓扑排序结果为:”<<endl;  
  112.     topsort(adj,n);  
  113.     system(“pause”);  
  114.     return 0;  
  115. }</span>  
<span style="font-family:Comic Sans MS;">#include<iostream>




#include<cmath> #include<cstdio> #include<algorithm> #include<stack> #include<memory.h> using namespace std; #define MAX 9999 stack<int>mystack; int indegree[MAX]; struct node { int adjvex; node* next; }adj[MAX]; int Create(node adj[],int n,int m)//邻接表建表函数,n代表定点数,m代表边数 { int i; node *p; for(i=1;i<=n;i++) { adj[i].adjvex=i; adj[i].next=NULL; } for(i=1;i<=m;i++) { cout<<"请输入第"<<i<<"条边:"; int u,v; cin>>u>>v; p=new node; p->adjvex=v; p->next=adj[u].next; adj[u].next=p; } return 1; } void print(int n)//邻接表打印函数 { int i; node *p; for(i=1;i<=n;i++) { p=&adj[i]; while(p!=NULL) { cout<<p->adjvex<<' '; p=p->next; } cout<<endl; } } void topsort(node adj[],int n) { int i; node *p; memset(indegree,0,sizeof(indegree)); for(i=1;i<=n;i++) { p=adj[i].next; while(p!=NULL) { indegree[p->adjvex]++; p=p->next; } } for(i=1;i<=n;i++) { if(indegree[i]==0) mystack.push(i); } int count=0; while(mystack.size()!=0) { i=mystack.top(); mystack.pop(); cout<<i<<' '; count++; for(p=adj[i].next;p!=NULL;p=p->next) { int k=p->adjvex; indegree[k]--; if(indegree[k]==0) mystack.push(k); } } cout<<endl; if(count<n)cout<<"有回路"<<endl; } int main() { int n; int m; cout<<"请输入顶点数及边数:"; cin>>n>>m; Create(adj,n,m); cout<<"输入的邻接表为:"<<endl; print(n); cout<<"拓扑排序结果为:"<<endl; topsort(adj,n); system("pause"); return 0; }</span>

根据上述事例按照一定的要求输入顶点和边数信息,在上例中是有6个顶点,8个边,其中第1条边对应的顶点是1和2个顶点,在这里输入顶点的边数要特别注意,我们一般是从箭头开始指向箭头结束方向,这样出来的结果就是对的了。另外,我要在这里强调的是,拓扑排序出来的结果是不唯一的,有很多种解法。

所以,总结以上,拓扑排序实质上就是一种偏序到全序的排序算法。

实例2:

主要是参考cplusplus.com网站上的代码,具体网址是:http://www.cplusplus.com/articles/iNwTURfi/

以下是经过验证了的代码:

  1. <span style=“font-family:Comic Sans MS;”>#include <iostream>  
  2. #include <vector>  
  3. #include <string>  
  4. #include <algorithm>  
  5.   
  6. using namespace std;  
  7.   
  8. class StringTopoSort  
  9. {  
  10.     public :  
  11.         StringTopoSort(vector<string> [],int);  
  12.         ~StringTopoSort();  
  13.           
  14.         vector<string> string_topo_sort();  
  15.     private :  
  16.         void visit(int index);  
  17.         int len;  
  18.         vector<string> * str_lists;  
  19.         vector<string> unsorted;  
  20.         vector<string> sorted;  
  21.         vector<int>   * digit_eq;  
  22.         vector<int> digit_sorted;  
  23.         vector<bool> is_visited;  
  24. };  
  25.   
  26.   
  27. StringTopoSort :: StringTopoSort(vector<string> * _str_lists,int _len)  
  28. {  
  29.     str_lists = _str_lists;  
  30.     len = _len;  
  31.     digit_eq = new vector<int>[len];  
  32.     for(int i =0; i<len;i++)  
  33.     {  
  34.         is_visited.push_back(false);  
  35.         unsorted.push_back(str_lists[i].at(0));  
  36.     }  
  37.     for(int i =0; i<len;i++)  
  38.     {  
  39.         for(vector<string>::iterator it = str_lists[i].begin(); it<str_lists[i].end();++it)  
  40.         {  
  41.             vector<string> :: iterator index = find(unsorted.begin(),unsorted.end(),*it);  
  42.             if(index != unsorted.end() )  
  43.                 digit_eq[i].push_back(index-unsorted.begin());  
  44.         }  
  45.     }  
  46. }  
  47.   
  48. StringTopoSort :: ~StringTopoSort() {}  
  49.   
  50. vector<string> StringTopoSort :: string_topo_sort()  
  51. {  
  52.     for(int i =0;i<len; i++)  
  53.     {  
  54.         if(is_visited[i] == false)  
  55.             visit(i);  
  56.     }  
  57.       
  58.     for(int i = 0; i<digit_sorted.size();i++)  
  59.         sorted.push_back(unsorted[digit_sorted[i]]);  
  60.       
  61.     return sorted;  
  62. }  
  63.   
  64. void StringTopoSort :: visit(int index)  
  65. {  
  66.     is_visited[index] = true;  
  67.       
  68.     for(vector<int>::iterator i = digit_eq[index].begin(); i<digit_eq[index].end(); i++)  
  69.     {  
  70.         if(!is_visited[*i])  
  71.             visit(*i);  
  72.     }  
  73.     digit_sorted.push_back(index);  
  74. }  
  75.   
  76. int main(int argc, char ** argv)  
  77. {  
  78.     vector<string> headers[] = {{“1”,”4”,”2”,”3”},{“2”},{“3”,”5”,”2”},\  
  79.     {“4”,”5”},{“5”},{“6”,”5”,”4”}}; //输入的数据为有向无环图的邻接表,出度的邻接表   
  80.     StringTopoSort sorter(headers,6);  
  81.     vector<string> sorted = sorter.string_topo_sort();  
  82. //  for(int i =0; i<sorted.size(); i++)  
  83. //      cout << sorted[i] << ” – ”;  
  84. //  for(vector <string>::iterator i=sorted.begin();i!=sorted.end();i++) //运用stl的迭代器进行顺序遍历   
  85. //      cout << *i << ”–”;   
  86. //  for(vector <string>::iterator i=sorted.end()-1;i!=sorted.begin();i–) //运用stl的迭代器进行反向遍历,会漏掉第1个元素   
  87. //      cout << *i << ”–”;   
  88.     vector <string>::iterator myi;   
  89.     myi=sorted.end();   
  90.     do   
  91.     {   
  92.         myi–;  
  93.         cout<<*myi<<“–”;   
  94.     }while(myi!=sorted.begin());//漏掉了sorted.begin()指向的元素  
  95.     cout << endl;  
  96.     return 0;  
  97. }</span>  
<span style="font-family:Comic Sans MS;">#include <iostream>




#include <vector> #include <string> #include <algorithm> using namespace std; class StringTopoSort { public : StringTopoSort(vector<string> [],int); ~StringTopoSort(); vector<string> string_topo_sort(); private : void visit(int index); int len; vector<string> * str_lists; vector<string> unsorted; vector<string> sorted; vector<int> * digit_eq; vector<int> digit_sorted; vector<bool> is_visited; }; StringTopoSort :: StringTopoSort(vector<string> * _str_lists,int _len) { str_lists = _str_lists; len = _len; digit_eq = new vector<int>[len]; for(int i =0; i<len;i++) { is_visited.push_back(false); unsorted.push_back(str_lists[i].at(0)); } for(int i =0; i<len;i++) { for(vector<string>::iterator it = str_lists[i].begin(); it<str_lists[i].end();++it) { vector<string> :: iterator index = find(unsorted.begin(),unsorted.end(),*it); if(index != unsorted.end() ) digit_eq[i].push_back(index-unsorted.begin()); } } } StringTopoSort :: ~StringTopoSort() {} vector<string> StringTopoSort :: string_topo_sort() { for(int i =0;i<len; i++) { if(is_visited[i] == false) visit(i); } for(int i = 0; i<digit_sorted.size();i++) sorted.push_back(unsorted[digit_sorted[i]]); return sorted; } void StringTopoSort :: visit(int index) { is_visited[index] = true; for(vector<int>::iterator i = digit_eq[index].begin(); i<digit_eq[index].end(); i++) { if(!is_visited[*i]) visit(*i); } digit_sorted.push_back(index); } int main(int argc, char ** argv) { vector<string> headers[] = {{"1","4","2","3"},{"2"},{"3","5","2"},\ {"4","5"},{"5"},{"6","5","4"}}; //输入的数据为有向无环图的邻接表,出度的邻接表 StringTopoSort sorter(headers,6); vector<string> sorted = sorter.string_topo_sort(); // for(int i =0; i<sorted.size(); i++) // cout << sorted[i] << " -- "; // for(vector <string>::iterator i=sorted.begin();i!=sorted.end();i++) //运用stl的迭代器进行顺序遍历 // cout << *i << "--"; // for(vector <string>::iterator i=sorted.end()-1;i!=sorted.begin();i--) //运用stl的迭代器进行反向遍历,会漏掉第1个元素 // cout << *i << "--"; vector <string>::iterator myi; myi=sorted.end(); do { myi--; cout<<*myi<<"--"; }while(myi!=sorted.begin());//漏掉了sorted.begin()指向的元素 cout << endl; return 0; }</span> 实例2就采用了基于C++stl标准模板库,没有直接构建邻接表,而是直接输入的邻接表。注意,邻接表是有方向的,一般是从入度到出度的箭头方向算起。
另外,此程序的拓扑排序原先输出了一个方向的顺序,然后我就直接逆向输出了。我暂时还不明白为什么会这样?先暂时留个疑问在这里。


  • 26
    点赞
  • 6
    评论
  • 47
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值