拓扑排序的原理及实现

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

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

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

在图论中,由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(英语:Topological sorting)。
(1)每个顶点出现且只出现一次;
(2)若A在序列中排在B的前面,则在图中不存在从B到A的路径。

也可以定义为:拓扑排序是对有向无环图的顶点的一种排序,它使得如果存在一条从顶点A到顶点B的路径,那么在排序中B出现在A的后面。

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

拓扑排序的原理及实现_第1张图片

(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
#include
#include
#include
#include
#include
#include 

using namespace std;
#define MAX 9999

stackmystack;
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<<"请输入第"<>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<adjvex<<' ';
            p=p->next;
        }
        cout<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<next)
        {
            int k=p->adjvex;
            indegree[k]--;
            if(indegree[k]==0)
                mystack.push(k);
        }
    }
    cout<>n>>m;
    Create(adj,n,m);
    cout<<"输入的邻接表为:"<
拓扑排序的原理及实现_第2张图片

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

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

实例2:

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

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

#include 
#include 
#include 
#include 

using namespace std;

class StringTopoSort
{
	public :
		StringTopoSort(vector [],int);
		~StringTopoSort();
		
		vector string_topo_sort();
	private :
		void visit(int index);
		int len;
		vector * str_lists;
		vector unsorted;
		vector sorted;
		vector	* digit_eq;
		vector digit_sorted;
		vector is_visited;
};


StringTopoSort :: StringTopoSort(vector * _str_lists,int _len)
{
	str_lists = _str_lists;
	len = _len;
	digit_eq = new vector[len];
	for(int i =0; i::iterator it = str_lists[i].begin(); it :: iterator index = find(unsorted.begin(),unsorted.end(),*it);
			if(index != unsorted.end() )
				digit_eq[i].push_back(index-unsorted.begin());
		}
	}
}

StringTopoSort :: ~StringTopoSort() {}

vector StringTopoSort :: string_topo_sort()
{
	for(int i =0;i::iterator i = digit_eq[index].begin(); i headers[] = {{"1","4","2","3"},{"2"},{"3","5","2"},\
	{"4","5"},{"5"},{"6","5","4"}}; //输入的数据为有向无环图的邻接表,出度的邻接表 
	StringTopoSort sorter(headers,6);
	vector sorted = sorter.string_topo_sort();
//	for(int i =0; i::iterator i=sorted.begin();i!=sorted.end();i++) //运用stl的迭代器进行顺序遍历 
//		cout << *i << "--"; 
//	for(vector ::iterator i=sorted.end()-1;i!=sorted.begin();i--) //运用stl的迭代器进行反向遍历,会漏掉第1个元素 
//		cout << *i << "--"; 
	vector ::iterator myi; 
	myi=sorted.end(); 
	do 
	{ 
		myi--;
		cout<<*myi<<"--"; 
	}while(myi!=sorted.begin());//漏掉了sorted.begin()指向的元素
	cout << endl;
	return 0;
}
实例2就采用了基于C++stl标准模板库,没有直接构建邻接表,而是直接输入的邻接表。注意,邻接表是有方向的,一般是从入度到出度的箭头方向算起。
另外,此程序的拓扑排序原先输出了一个方向的顺序,然后我就直接逆向输出了。我暂时还不明白为什么会这样?先暂时留个疑问在这里。


你可能感兴趣的:(数据结构与算法)