有向图的拓扑排序——基于邻接矩阵

http://blog.csdn.net/jinzhao1993/article/details/51778468

1. 拓扑排序的概念

定义:将有向图中的顶点以线性方式进行排序。即对于任何连接自顶点u到顶点v的有向边u->v,在最后的排序结果中,顶点u总是在顶点v的前面。

考虑一个非常非常经典的例子——选课。假设我非常想学习一门机器学习的课程,但是在修这么课程之前,我们必须要先学习一些基础课程,比如计算机科学概论,C语言程序设计,数据结构,算法等等。那么这个制定选修课程顺序的过程,实际上就是一个拓扑排序的过程。每门课程相当于有向图中的一个顶点,而连接顶点之间的有向边就是课程学习的先后关系。只不过这个过程不是那么复杂,从而很自然的在我们的大脑中完成了。将这个过程以算法的形式描述出来的结果,就是拓扑排序。

2. 拓扑排序的充要条件

是不是所有的有向图都能被拓扑排序呢?显然不是。 
继续考虑上面的例子,如果告诉你在选修《计算机科学概论》这门课之前需要你先学习《机器学习》,你是不是会被弄糊涂?在这种情况下,就无法进行拓扑排序,因为它中间存在互相依赖的关系,从而无法确定谁先谁后。在有向图中,这种情况被描述为存在环路。 
因此,一个有向图能被拓扑排序的充要条件就是它是一个有向无环图。

3. 偏序和全序的关系

还是以上面选课的例子来描述这两个概念。假设我们在学习完了《算法》这门课后,可以选修《机器学习》或者《计算机图形学》。这也就意味着,学习《机器学习》和《计算机图形学》这两门课之间没有特定的先后顺序。因此,在我们所有可以选择的课程中,任意两门课程之间的关系要么是确定的(即拥有先后关系),要么是不确定的(即没有先后关系),绝对不存在互相矛盾的关系(即环路)。 
以上就是偏序的意义,抽象而言,有向图中两个顶点之间不存在环路,至于连通与否,是无所谓的。所以,有向无环图必然是满足偏序关系的。

理解了偏序的概念,那么全序就好办了。所谓全序,就是在偏序的基础上,有向无环图中的任意一对顶点还需要有明确的关系(反映在图中,就是单向连通的关系,注意不能双向连通,那就成环了)。可见,全序就是偏序的一种特殊情况。 
回到我们的选课例子中,如果《机器学习》需要在学习了《计算机图形学》之后才能学习,那么它们之间也就存在了确定的先后顺序,原本的偏序关系就变成了全序关系。

实际上,很多地方都存在偏序和全序的概念。 
比如对若干互不相等的整数进行排序,最后总是能够得到唯一的排序结果。我们以偏序/全序的角度来考虑一下这个再自然不过的问题,可能就会有别的体会了。

如何用偏序/全序来解释排序结果的唯一性呢? 
我们知道不同整数之间的大小关系是确定的,比如1总是小于4的。这就是说,这个序列是满足全序关系的。而对于拥有全序关系的结构(如拥有不同整数的数组),在其线性化(排序)之后的结果必然是唯一的。 
对于排序的算法,我们评价指标之一是看该排序算法是否稳定,即值相同的元素的排序结果是否和出现的顺序一致。比如,我们说快速排序是不稳定的,因为最后的快排结果中,相同元素的出现顺序和排序前不一致了。如果用偏序的概念可以这样解释这一现象:相同值的元素之间的关系是无法确定的。因此它们在最终的结果中的出现顺序可以是任意的。而对于诸如插入排序这种稳定性排序,它们对于值相同的元素,还有一个潜在的比较方式,即比较它们的出现顺序,出现靠前的元素大于出现后出现的元素。因此通过这一潜在的比较,将偏序关系转换为了全序关系,从而保证了结果的唯一性。 
拓展到拓扑排序中,结果具有唯一性的条件也是其所有顶点之间都具有全序关系。如果没有这一层全序关系,那么拓扑排序的结果也就不是唯一的了。如果拓扑排序的结果唯一,那么该拓扑排序的结果同时也代表了一条哈密顿路径。

4. 入度和出度

入度和出度是图论算法中重要的概念之一。 
入度(in-degree)通常指有向图中某点作为图中边的终点的次数之和。
在图中,一个结点的前件个数称为该结点的入度。
出度 (out-degree) 是指以某顶点为弧尾,起始于该顶点的弧的数目。在图中,一个结点的后件个数称为该结点的出度。

5. 拓扑排序的实现

拓扑排序算法主要是循环执行以下两步,直到不存在入度为0的顶点为止。

  • 选择一个入度为0的顶点并输出之;
  • 从网中删除此顶点及所有出边(以它为尾的弧)。

直至图空,或者图不空但找不到无前驱的顶点为止。 
循环结束后,若输出的顶点数小于网中的顶点数(只存在有入度的点),则输出“有回路”信息,否则(图空)输出的顶点序列就是一种拓扑序列。

我们继续以题来进行进一步讲解:

Description 
有N个比赛队(1<=N<=500),编号依次为1,2,3,。。。。,N进行比赛,比赛结束后,裁判委员会要将所有参赛队伍从前往后依次排名,但现在裁判委员会不能直接获得每个队的比赛成绩,只知道每场比赛的结果,即P1赢P2,用P1,P2表示,排名时P1在P2之前。现在请你编程序确定排名。

Input 
输入有若干组,每组中的第一行为二个数N(1<=N<=500),M;其中N表示队伍的个数,M表示接着有M行的输入数据。接下来的M行数据中,每行也有两个整数P1,P2表示即P1队赢了P2队。

Output 
给出一个符合要求的排名。输出时队伍号之间有空格,最后一名后面没有空格。 
其他说明:符合条件的排名可能不是唯一的,此时要求输出时编号小的队伍在前;输入数据保证是正确的,即输入数据确保一定能有一个符合要求的排名。

Sample Input 
4 3 
1 2 
2 3 
4 3

Sample Output 
1 2 4 3

采用邻接矩阵实现,map[i][j]=0,表示节点i和j没有关联;map[i][j]=1,表示存在边<i,j>,并且j的入度加1。

#include<iostream>
#include<stdlib.h>
#include<stdio.h>
#define MAX 100
usingnamespace std;

void toposort(int map[MAX][MAX],int indegree[MAX],int n)
{
    int i,j,k;
    for(i=0;i<n;i++) //遍历n次
    {
        for(j=0;j<n;j++) //找出入度为0的节点
        {
            if(indegree[j]==0)
            {
                indegree[j]--;
                cout<<j<<endl;
                for(k=0;k<n;k++) //删除与该节点关联的边
                {
                    if(map[j][k]==1)
                    {
                        indegree[k]--;
                    }
                }
                break;
            }
        }
    }
}


int main(void)
{
    int n,m; //n:关联的边数,m:节点数
    while(scanf("%d %d",&n,&m)==2&&n!=0)
    {
        int i;
        int x,y;
        int map[MAX][MAX]; //邻接矩阵
        int indegree[MAX]; //入度
        memset(map,0,sizeof(map));
        memset(indegree,0,sizeof(indegree));
        for(i=0;i<n;i++)
        {
            scanf("%d %d",&x,&y);
            if(!map[x][y])
            {
                map[x][y]=1;
                indegree[y]++;
            }
        }
        toposort(map,indegree,m);
    }
    return0;
}

    原文作者:拓扑排序
    原文地址: https://blog.csdn.net/taotaoah/article/details/52745813
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞