拓扑排序(一)之 C语言详解

出自:http://www.cnblogs.com/skywang12345/p/3711489.html

本章介绍图的拓扑排序。和以往一样,本文会先对拓扑排序的理论知识进行介绍,然后给出C语言的实现。后续再分别给出C++和Java版本的实现。

目录 
1
拓扑排序介绍 
2拓扑排序的算法图解 
3拓扑排序的代码说明 
4拓扑排序的完整源码和测试程序

转载请注明出处:http://www.cnblogs.com/skywang12345/

更多内容:数据结构与算法系列 目录

拓扑排序介绍

拓扑排序(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. 基本定义

复制代码
// 邻接表中表对应的链表的顶点
typedef struct _ENode
{
    int ivex;                   // 该边所指向的顶点的位置
    struct _ENode *next_edge;   // 指向下一条弧的指针
}ENode, *PENode;

// 邻接表中表的顶点
typedef struct _VNode
{
    char data;              // 顶点信息
    ENode *first_edge;      // 指向第一条依附该顶点的弧
}VNode;

// 邻接表
typedef struct _LGraph
{
    int vexnum;             // 图的顶点的数目
    int edgnum;             // 图的边的数目
    VNode vexs[MAX];
}LGraph;
复制代码

(01) LGraph是邻接表对应的结构体。 vexnum是顶点数,edgnum是边数;vexs则是保存顶点信息的一维数组。 
(02) VNode是邻接表顶点对应的结构体。 data是顶点所包含的数据,而firstedge是该顶点所包含链表的表头指针。 
(03) ENode是邻接表顶点所包含的链表的节点对应的结构体。 ivex是该节点所对应的顶点在vexs中的索引,而next
edge是指向下一个节点的。

2. 拓扑排序

复制代码
/*
 * 拓扑排序
 *
 * 参数说明:
 *     G -- 邻接表表示的有向图
 * 返回值:
 *     -1 -- 失败(由于内存不足等原因导致)
 *      0 -- 成功排序,并输入结果
 *      1 -- 失败(该有向图是有环的)
 */
int topological_sort(LGraph G)
{
    int i,j;
    int index = 0;
    int head = 0;           // 辅助队列的头
    int rear = 0;           // 辅助队列的尾
    int *queue;             // 辅组队列
    int *ins;               // 入度数组
    char *tops;             // 拓扑排序结果数组,记录每个节点的排序后的序号。
    int num = G.vexnum;
    ENode *node;

    ins  = (int *)malloc(num*sizeof(int));  // 入度数组
    tops = (char *)malloc(num*sizeof(char));// 拓扑排序结果数组
    queue = (int *)malloc(num*sizeof(int)); // 辅助队列
    assert(ins!=NULL && tops!=NULL && queue!=NULL);
    memset(ins, 0, num*sizeof(int));
    memset(tops, 0, num*sizeof(char));
    memset(queue, 0, num*sizeof(int));

    // 统计每个顶点的入度数
    for(i = 0; i < num; i++)
    {
        node = G.vexs[i].first_edge;
        while (node != NULL)
        {
            ins[node->ivex]++;
            node = node->next_edge;
        }
    }

    // 将所有入度为0的顶点入队列
    for(i = 0; i < num; i ++)
        if(ins[i] == 0)
            queue[rear++] = i;          // 入队列

    while (head != rear)                // 队列非空
    {
        j = queue[head++];              // 出队列。j是顶点的序号
        tops[index++] = G.vexs[j].data; // 将该顶点添加到tops中,tops是排序结果
        node = G.vexs[j].first_edge;    // 获取以该顶点为起点的出边队列

        // 将与"node"关联的节点的入度减1;
        // 若减1之后,该节点的入度为0;则将该节点添加到队列中。
        while(node != NULL)
        {
            // 将节点(序号为node->ivex)的入度减1。
            ins[node->ivex]--;
            // 若节点的入度为0,则将其"入队列"
            if( ins[node->ivex] == 0)
                queue[rear++] = node->ivex;  // 入队列

            node = node->next_edge;
        }
    }

    if(index != G.vexnum)
    {
        printf("Graph has a cycle\n");
        free(queue);
        free(ins);
        free(tops);
        return 1;
    }

    // 打印拓扑排序结果
    printf("== TopSort: ");
    for(i = 0; i < num; i ++)
        printf("%c ", tops[i]);
    printf("\n");

    free(queue);
    free(ins);
    free(tops);
    return 0;
}
复制代码

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

拓扑排序的完整源码和测试程序

拓扑排序源码(list_dg.c)

 
 
/**
* C: 无回路有向图(Directed Acyclic Graph)的拓扑排序
* 该DAG图是通过邻接表实现的。
*
* @author skywang
* @date 2014/04/22
*/

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <assert.h>

#define isLetter(a) ((((a)>='a')&&((a)<='z')) || (((a)>='A')&&((a)<='Z')))
#define LENGTH(a) (sizeof(a)/sizeof(a[0]))

// 邻接表中表对应的链表的顶点
typedef struct _ENode
{
     int ivex ; // 该边所指向的顶点的位置
     struct _ENode * next_edge ; // 指向下一条弧的指针
} ENode , * PENode ;

// 邻接表中表的顶点
typedef struct _VNode
{
     char data ; // 顶点数据
     ENode * first_edge ; // 指向第一条依附该顶点的弧
} VNode ;

// 邻接表
typedef struct _LGraph
{
     int vexnum ; // 图的顶点的数目
     int edgnum ; // 图的边的数目
     VNode * vexs ; // 图的顶点数组
} LGraph ;

/*
* 返回ch在matrix矩阵中的位置
*/
static int get_position ( LGraph g , char ch )
{
     int i ;
     for ( i = 0 ; i < g . vexnum ; i ++ )
         if ( g . vexs [ i ]. data == ch )
             return i ;
     return - 1 ;
}

/*
* 读取一个输入字符
*/
static char read_char ()
{
     char ch ;

     do {
         ch = getchar ();
     } while ( ! isLetter ( ch ));

     return ch ;
}

/*
* 将node链接到list的末尾
*/
static void link_last ( ENode * list , ENode * node )
{
     ENode * p = list ;

     while ( p -> next_edge )
         p = p -> next_edge ;
     p -> next_edge = node ;
}

/*
* 创建邻接表对应的图(自己输入)
*/
LGraph * create_lgraph ()
{
     char c1 , c2 ;
     int v , e ;
     int i , p1 , p2 ;
     ENode * node1 , * node2 ;
     LGraph * pG ;

     // 输入"顶点数"和"边数"
     printf ( "input vertex number: " );
     scanf ( "%d" , & v );
     printf ( "input edge number: " );
     scanf ( "%d" , & e );
     if ( v < 1 || e < 1 || ( e > ( v * ( v - 1 ))))
     {
         printf ( "input error: invalid parameters! \n " );
         return NULL ;
     }
 
     pG = ( LGraph * ) malloc ( sizeof ( LGraph ));
     assert ( pG != NULL );

     // 初始化"顶点数"和"边数"
     pG -> vexnum = v ;
     pG -> edgnum = e ;
     pG -> vexs = ( VNode * ) malloc ( pG -> vexnum * sizeof ( VNode ));
     assert ( pG -> vexs != NULL );
     // 初始化"邻接表"的顶点
     for ( i = 0 ; i < pG -> vexnum ; i ++ )
     {
         printf ( "vertex(%d): " , i );
         pG -> vexs [ i ]. data = read_char ();
         pG -> vexs [ i ]. first_edge = NULL ;
     }

     // 初始化"邻接表"的边
     for ( i = 0 ; i < pG -> edgnum ; i ++ )
     {
         // 读取边的起始顶点和结束顶点
         printf ( "edge(%d): " , i );
         c1 = read_char ();
         c2 = read_char ();

         p1 = get_position ( * pG , c1 );
         p2 = get_position ( * pG , c2 );
         // 初始化node1
         node1 = ( ENode * ) malloc ( sizeof ( ENode ));
         node1 -> ivex = p2 ;
         // 将node1链接到"p1所在链表的末尾"
         if ( pG -> vexs [ p1 ]. first_edge == NULL )
           pG -> vexs [ p1 ]. first_edge = node1 ;
         else
             link_last ( pG -> vexs [ p1 ]. first_edge , node1 );
     }

     return pG ;
}

/*
* 创建邻接表对应的图(用已提供的数据)
*/
LGraph * create_example_lgraph ()
{
     char c1 , c2 ;
     char vexs [] = { 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' };
     char edges [][ 2 ] = {
         { 'A' , 'G' },
         { 'B' , 'A' },
         { 'B' , 'D' },
         { 'C' , 'F' },
         { 'C' , 'G' },
         { 'D' , 'E' },
         { 'D' , 'F' }};
     int vlen = LENGTH ( vexs );
     int elen = LENGTH ( edges );
     int i , p1 , p2 ;
     ENode * node1 , * node2 ;
     LGraph * pG ;

     if (( pG = ( LGraph * ) malloc ( sizeof ( LGraph ))) == NULL )
     assert ( pG != NULL );
     memset ( pG , 0 , sizeof ( LGraph ));

     // 初始化"顶点数"和"边数"
     pG -> vexnum = vlen ;
     pG -> edgnum = elen ;
     pG -> vexs = ( VNode * ) malloc ( pG -> vexnum * sizeof ( VNode ));
     assert ( pG -> vexs != NULL );
     // 初始化"邻接表"的顶点
     for ( i = 0 ; i < pG -> vexnum ; i ++ )
     {
         pG -> vexs [ i ]. data = vexs [ i ];
         pG -> vexs [ i ]. first_edge = NULL ;
     }

     // 初始化"邻接表"的边
     for ( i = 0 ; i < pG -> edgnum ; i ++ )
     {
         // 读取边的起始顶点和结束顶点
         c1 = edges [ i ][ 0 ];
         c2 = edges [ i ][ 1 ];

         p1 = get_position ( * pG , c1 );
         p2 = get_position ( * pG , c2 );
         // 初始化node1
         node1 = ( ENode * ) malloc ( sizeof ( ENode ));
         node1 -> ivex = p2 ;
         // 将node1链接到"p1所在链表的末尾"
         if ( pG -> vexs [ p1 ]. first_edge == NULL )
           pG -> vexs [ p1 ]. first_edge = node1 ;
         else
             link_last ( pG -> vexs [ p1 ]. first_edge , node1 );
     }

     return pG ;
}

/*
* 深度优先搜索遍历图的递归实现
*/
static void DFS ( LGraph G , int i , int * visited )
{
     int w ;
     ENode * node ;

     visited [ i ] = 1 ;
     printf ( "%c " , G . vexs [ i ]. data );
     node = G . vexs [ i ]. first_edge ;
     while ( node != NULL )
     {
         if ( ! visited [ node -> ivex ])
             DFS ( G , node -> ivex , visited );
         node = node -> next_edge ;
     }
}

/*
* 深度优先搜索遍历图
*/
void DFS_traverse ( LGraph G )
{
     int i ;
     int * visited ; // 顶点访问标记

     visited = ( int * ) malloc ( G . vexnum * sizeof ( int ));
     assert ( visited != NULL );

     // 初始化所有顶点都没有被访问
     memset ( visited , 0 , G . vexnum * sizeof ( int ));

     printf ( "== DFS: " );
     for ( i = 0 ; i < G . vexnum ; i ++ )
     {
         if ( ! visited [ i ])
             DFS ( G , i , visited );
     }
     printf ( " \n " );

     free ( visited );
}

/*
* 广度优先搜索(类似于树的层次遍历)
*/
void BFS ( LGraph G )
{
     int head = 0 ;
     int rear = 0 ;
     int * queue ; // 辅组队列
     int * visited ; // 顶点访问标记
     int i , j , k ;
     ENode * node ;


     queue = ( int * ) malloc ( G . vexnum * sizeof ( int ));
     visited = ( int * ) malloc ( G . vexnum * sizeof ( int ));
     assert ( queue != NULL && visited != NULL );

     memset ( queue , 0 , G . vexnum * sizeof ( int ));
     memset ( visited , 0 , G . vexnum * sizeof ( int ));

     printf ( "== BFS: " );
     for ( i = 0 ; i < G . vexnum ; i ++ )
     {
         if ( ! visited [ i ])
         {
             visited [ i ] = 1 ;
             printf ( "%c " , G . vexs [ i ]. data );
             queue [ rear ++ ] = i ; // 入队列
         }
         while ( head != rear )
         {
             j = queue [ head ++ ]; // 出队列
             node = G . vexs [ j ]. first_edge ;
             while ( node != NULL )
             {
                 k = node -> ivex ;
                 if ( ! visited [ k ])
                 {
                     visited [ k ] = 1 ;
                     printf ( "%c " , G . vexs [ k ]. data );
                     queue [ rear ++ ] = k ;
                 }
                 node = node -> next_edge ;
             }
         }
     }
     printf ( " \n " );

     free ( visited );
     free ( queue );
}

/*
* 拓扑排序
*
* 参数说明:
* G -- 邻接表表示的有向图
* 返回值:
* -1 -- 失败(由于内存不足等原因导致)
* 0 -- 成功排序,并输入结果
* 1 -- 失败(该有向图是有环的)
*/
int topological_sort ( LGraph G )
{
     int i , j ;
     int index = 0 ;
     int head = 0 ; // 辅助队列的头
     int rear = 0 ; // 辅助队列的尾
     int * queue ; // 辅组队列
     int * ins ; // 入度数组
     char * tops ; // 拓扑排序结果数组,记录每个节点的排序后的序号。
     int num = G . vexnum ;
     ENode * node ;

     ins = ( int * ) malloc ( num * sizeof ( int )); // 入度数组
     tops = ( char * ) malloc ( num * sizeof ( char )); // 拓扑排序结果数组
     queue = ( int * ) malloc ( num * sizeof ( int )); // 辅助队列
     assert ( ins != NULL && tops != NULL && queue != NULL );
     memset ( ins , 0 , num * sizeof ( int ));
     memset ( tops , 0 , num * sizeof ( char ));
     memset ( queue , 0 , num * sizeof ( int ));

     // 统计每个顶点的入度数
     for ( i = 0 ; i < num ; i ++ )
     {
         node = G . vexs [ i ]. first_edge ;
         while ( node != NULL )
         {
             ins [ node -> ivex ] ++ ;
             node = node -> next_edge ;
         }
     }

     // 将所有入度为0的顶点入队列
     for ( i = 0 ; i < num ; i ++ )
         if ( ins [ i ] == 0 )
             queue [ rear ++ ] = i ; // 入队列

     while ( head != rear ) // 队列非空
     {
         j = queue [ head ++ ]; // 出队列。j是顶点的序号
         tops [ index ++ ] = G . vexs [ j ]. data ; // 将该顶点添加到tops中,tops是排序结果
         node = G . vexs [ j ]. first_edge ; // 获取以该顶点为起点的出边队列

         // 将与"node"关联的节点的入度减1;
         // 若减1之后,该节点的入度为0;则将该节点添加到队列中。
         while ( node != NULL )
         {
             // 将节点(序号为node->ivex)的入度减1。
             ins [ node -> ivex ] -- ;
             // 若节点的入度为0,则将其"入队列"
             if ( ins [ node -> ivex ] == 0 )
                 queue [ rear ++ ] = node -> ivex ; // 入队列

             node = node -> next_edge ;
         }
     }

     if ( index != G . vexnum )
     {
         printf ( "Graph has a cycle \n " );
         free ( queue );
         free ( ins );
         free ( tops );
         return 1 ;
     }

     // 打印拓扑排序结果
     printf ( "== TopSort: " );
     for ( i = 0 ; i < num ; i ++ )
         printf ( "%c " , tops [ i ]);
     printf ( " \n " );

     free ( queue );
     free ( ins );
     free ( tops );
     return 0 ;
}

/*
* 打印邻接表图
*/
void print_lgraph ( LGraph G )
{
     int i ;
     ENode * node ;

     printf ( "== List Graph: \n " );
     for ( i = 0 ; i < G . vexnum ; i ++ )
     {
         printf ( "%d(%c): " , i , G . vexs [ i ]. data );
         node = G . vexs [ i ]. first_edge ;
         while ( node != NULL )
         {
             printf ( "%d(%c) " , node -> ivex , G . vexs [ node -> ivex ]. data );
             node = node -> next_edge ;
         }
         printf ( " \n " );
     }
}

void main ()
{
     LGraph * pG ;

     // 自定义"图"(自己输入数据)
     //pG = create_lgraph();
     // 采用已有的"图"
     pG = create_example_lgraph ();

     // 打印图
     print_lgraph ( * pG ); // 打印图
     //DFS_traverse(*pG); // 深度优先搜索
     //BFS(*pG); // 广度优先搜索
     topological_sort ( * pG ); // 拓扑排序
}

  • 1
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值