1. 程式人生 > >拓撲排序(C語言實現)

拓撲排序(C語言實現)

拓撲排序可以將一個有向無環圖轉換為一個線性序列。它也是判定一個有向圖是否是無環的方法之一。如何進行拓撲排序,方法如下:

1)從有向圖中選取一個沒有前驅(入度為0)頂點,並輸出之; 2)從有向圖中刪去此頂點以及所有以它為尾的(弧頭頂點的入度減1) 重複上述兩步,直至圖空,或者圖不空但找不到無前驅的頂點為止。 下圖:

圖a為一個有向圖,入度為0的點有兩個,任選一個輸出,這裡輸出0,以0為弧尾的頂點入度減一,得圖b。繼續輸出入度為0的點,在此是1,輸出1之後,以1為弧尾的頂點入度減一,得圖c,以此類推,直到圖空為止。

這個理解起來是比較容易的,現在關鍵的是實現,如何得到入度為0的點?為避免每次都要搜尋入度為零的頂點,在演算法中

設定一個,以儲存入度為零的頂點。

在拓撲排序演算法中,需要設定一個包含n個元素的一維整形陣列,假定用d表示,用它來儲存這個有向無環圖中每個頂點的入度值。對於上圖,得到陣列d的初始之為:

0 0 2 2 1 3
在進行拓撲排序時,為了把所有入度為0的頂點都儲存起來,而且又便於插入、刪除以及節省空間,最好的方法是把它們連結成一個棧。另外,當一個頂點vi的入度為0時,陣列d中下標為i的元素d[i]的值為0(這句話的意思是我們沒有必要儲存入度為0的頂點的入度值,反而我們可以利用這個空間,用它來儲存下一個入度為0的頂點的下標(序號),這裡很重要,好好理解。這樣,就可以把所有入度為0的頂點通過陣列d中的對應元素(陣列元素的下標對應著頂點編號)靜態連結成一個棧。在這個鏈棧中,棧頂指標top指向第一個入度為0的頂點所對應的陣列d中的元素,該元素的值(陣列中的值)則指向第二個入度為0的頂點所對應的陣列d中的元素
,以此類推,最後一個入度為0的頂點所對應的陣列d中的元素儲存-1,表示為棧底。

例如,根據上圖,建立鄰接表,如圖:

,建立入度為0的初始棧的過程如下:

    (1)開始置鏈棧為空,即給鏈棧指標top賦初值為-1     top=-1;

   (2)將入度為0的元素d[0]進棧,即: d[0]=top;top=0        /*因為d[0]存放下一個入度為0的頂點下標,在此,由於它是第一個入度為0的頂點(沒有下一個入度為0的頂點),因此d[0]的值為-1,top=0*/

  (3)將入度為0的元素d[1]進棧,此時,d[1]裡應該保留下一個入度為0的頂點下標,而此時,下一個入度為0的頂點下標肯定是原top所指向的值,即:

d[1]=top;top=1;

(4)因d[2]至d[5]的值均不為0,所以它們均不進棧。至此,初始棧建立完畢,陣列d(多用途哦,即保留了頂點入度不為0的入度值,也作為鏈棧使用如下圖所示:

現在開始迴圈執行拓撲演算法中的第一步"選擇一個入度為0的頂點並輸出之",利用輸出棧頂指標top所代表的頂點序號來實現,所以,輸出頂點1(因為top=1).頂點1輸出後,修改以頂點1為弧尾的頂點的入度,此例中,頂點4的入度為0,so,d[4]元素入棧,d[4]=0(d[4]=top;top=4,如果理解不了,請模擬一下鏈棧的出棧和入棧)。


現在頂點4輸出,以此為弧尾的頂點入度減一,得圖入下:

現在輸出頂點0,以此為弧尾的頂點入度減一,此時,頂點2的入度為0,因此入棧。d[2]=-1(d[2]=top;top=2),如圖

以此類推,直到top的值為-1,表示棧空,演算法執行結束。如果得到的頂點數是n(圖的頂點數),則表明該圖是有向無環圖,否則不是。

具體c語言實現:

#include <stdio.h>
#include <stdlib.h>


void TopoSort(adjlist GL,int n)
{
      int i,j,k,top,m=0;     /*m用來統計拓撲序列中的頂點數*/
      struct edgenode *p;    /*單鏈表*/
      int *d=(int *)malloc(n*sizeof(int));/*定義儲存圖中每個頂點入度的一維整形陣列d*/
      for(i=0;i<n;i++)
        d[i]=0;      /*初始化陣列*/
      for(i=0;i<n;i++)     /*利用陣列d中的對應元素統計出圖中每個頂點的入度*/
      {
           p=GL[i];
           while(p!=NULL)
           {
               j=p->adjvex;
               d[j]++;
               p=p->next;
           }
      }
      top=-1;   /*初始化用於連結入度為0的元素的棧的棧頂指標為-1*/
      for(i=0;i<n;i++)  /*建立初始化棧*/
        if(d[i]==0)
         {
            d[i]=top;
            top=i;
          }
      while(top!=-1)   /*每迴圈一次刪除一個頂點及所有以它為弧尾的頂點入度減一*/
      {
          j=top   /*j的值為一個入度為0的頂點序號*/
          top=d[top];  /*得到下一個入度為0的頂點下標*/
          printf("%d",j);  /*輸出一個頂點*/
          m++;    /*輸出的頂點個數加1*/
          p=GL[j];  /*p指向vj頂點鄰接表的第一個節點,目的是開始把以它為弧尾的頂點入度減一*/
          while(p!=NULL)
          {
              k=p->adjvex;   /*vk是vj的一個鄰接點*/
              d[k]--;       /*vk入度減一*/
              if(d[k]==0)     /*把入度為0的元素進棧,對應著圖看更容易明白*/
              {
                  d[k]=top;
                  top=k;
              }
              p=p->next;
          }
      }
       printf("\n");
       if(m<n)
        printf("有迴路");
        free(d);    /*刪除動態分配的陣列d*/
}

時間複雜度O(n+e)。