1. 程式人生 > >拓撲排序以及迪傑斯特拉演算法和弗洛伊德演算法的一些例子(天勤資料結構)

拓撲排序以及迪傑斯特拉演算法和弗洛伊德演算法的一些例子(天勤資料結構)

拓撲排序核心演算法
    在一個有向圖中找到一個拓撲排序的過程如下:
    1)從一個有向圖中選擇一個沒有前驅(入度為0)的頂點輸出;
    2)刪除1)中的頂點,並刪除從該頂點出發的全部邊;
    3)重複上述兩步,直到剩餘的圖中不存在沒有前驅頂點為止(此時圖全部頂點都已經輸出)
    拓撲排序存在的條件是圖中不存在環

    為使能從途中選取出入度為0的結點並將其進行輸出,要對鄰接表結構進行修改,可以增加一個統計結點入度的計數器

typedef struct
{
    int data;
    int count;    //此句為新增部分,count用來統計頂點當前的入度
    ArcNode *firststarc;
}VNode; //此句前面已經定義過了*/


    假設圖的鄰接表已經生成,並且各個頂點的入度都已經記錄在count中,在本演算法中要設計一個棧,記錄當前圖中入度為0的頂點,還需要設計一個計數器
    n,用來記錄已經輸出的頂點的個數
    演算法開始時置n為0,掃描所有的頂點,將入度為0的頂點入棧。然後在棧部位空的時候迴圈執行下面的操作:出棧,將棧頂元素進行輸出,執行++n,並且
    由此頂點引出的邊所指向的頂點的入度都減1,並且將入度變為0的頂點入棧;出棧......棧空時退出迴圈,排序結束。迴圈退出後判斷n是否等於圖中頂點
    個數,如果相等則返回1,拓撲排序成功;否則返回0,拓撲排序失敗。因此可以寫出以下的演算法程式碼:

int TopSort(AGraph *G)
{
    int i = 0, j = 0, n = 0;
    stack<int> topsortstack;    //定義並初始化棧
    ArcNode *p = NULL;
    //下面這個迴圈將圖中入度為0的頂點入棧
    for (i = 0; i < G->n; ++i)
        if (G->adjlist[i].count == 0)
            topsortstack.push(i);
    //關鍵操作開始
    while (!(topsortstack.empty()))
    {
        i = topsortstack.top();
        topsortstack.pop();    //頂點出棧
        ++n;    //計數器加一,統計當前頂點
        cout << i << " ";
        p = G->adjlist[i].firstarc;
        //上面這個迴圈實現了將所有由此頂點引出的邊所指向的頂點的入度都減少1。並將這個過程中入度為0的頂點入棧

        while (p != NULL)
        {
            j = p->adjvex;
            --(G->adjlist[j].count);
            if (G->adjlist[i].count == 0)
            {
                topsortstack.push(j);
            }
            p = p->nextarc;
        }
    }
    //關鍵操作結束
    if (n == G->n)
        return 1;
    else return 0;
}

拓撲排序的時間複雜度為O(n+e),n為圖的頂點數,e為圖的邊數
注意拓撲排序的結果可能不唯一。從演算法的過程就可以看出,在選擇入度為0的頂點進行輸出的時候,對其他頂點是沒有要求的
只需要入度為0即可。噹噹前步驟中有多個入度為0的頂點時,可以選擇任意一個進行輸出,這就造成了拓撲序列的不唯一
當有向圖圖中無環時,還可以採用深度優先搜尋進行拓撲排序。由於圖中無環,當由圖中某頂點出發進行DFS遍歷的時候,最先退出演算法
的頂點即為初度為0的頂點,它是拓撲有序序列中序列當中的最後一個頂點,因此按照DFS演算法的先後次序記錄下的頂點序列即為逆向的
拓撲有序序列

對這句的詳細理解:
    1、最先退出演算法的頂點即為出度為0的頂點,此處退出演算法是指所遍歷的頂點退出當前系統棧
    2、按照DFS演算法的先後次序記錄下的頂點序列。此處按照DFS演算法先後次序並不是指最終遍歷結果序列,而是頂點退出系統棧的順序

  拓展練習:在有向圖G中,若r到G的每個頂點都有路徑可達,則r為G的根結點。編寫一個演算法判斷有向圖中是否存在根,若有,打出根結點
//的值,假設圖已經存在鄰接表G中 
    分析:判斷r到G中每個頂點是否有路徑可達,則可以通過深度優先搜尋的方法,以r為為起點進行深度優先遍歷,若在函式DFS()推出前已
    經訪問過所有的頂點,則r為根。要打印出所有的根結點,可以對圖中的每個頂點都呼叫一次函式DFS,若是根則列印。

int DFS_Vedge_visit[MAXSIZE], sum;    
//以下是深度優先搜搜遍歷演算法
void DFS_Judge(AGraph *&G, int v)
{
    ArcNode *p = NULL;
    DFS_Vedge_visit[v] = 1;
    ++sum;        //此處為DFS演算法的修改部分,將函式Visit()換成了++sum,即每次訪問一個頂點時計數器加1

    p = G->adjlist[v].firstarc;
    while ( p != NULL)
    {
        if (DFS_Vedge_visit[p->adjvex] == 0)
        {
            DFS_Judge(G, p->adjvex);
        }
    }
}
void print_DFS_Edges(AGraph *G)
{
    int i = 0, j = 0;
    for (i = 0; i < G->n; ++i)
    {
        sum = 0;
        for (j = 0; j < G->n; ++j)
            DFS_Vedge_visit[j] = 0;
        DFS(G, i);
        if (sum == G->n)
        {
            cout << i << endl;
        }
    }
}


設有向圖採用鄰接表表示,圖中有n個頂點,表示為n。試寫一個演算法求頂點k的入度
分析:要求頂點k的入度,必須檢查表中的所有的邊結點。看看邊結點所指頂點是否為k,若是則證明k有一個入度,做一次統計。因此需要設計一個技術其來統計k的入度
程式碼如下:

int count(AGraph *G, int k)
{
    ArcNode *p;//p用來掃描每個頂點所發出的邊
    int i = 0, sum = 0;    //sum為頂點k的計數器
    for (int i = 0; i < G->n; ++i)
    {
        p = G->adjlist[i].firstarc;
        while ( p != NULL)
        {
            if (k == p->adjvex)
            {
                ++sum;
                break;
            }
            p = p->nextarc;
        }
    }
    return sum;    //返回k的入度
}

試寫出一鄰接表為儲存結構的的深度優先搜尋演算法的非遞迴版本
    遞迴的本質就是呼叫了系統已有的棧來幫助我們解決問題。因此,對於演算法的非遞迴版本的關鍵在於需要手寫一個
    棧來代替系統棧。
    當問題執行到一個狀態,一現有的條件無法完全解決是,必須先要先記下當前狀態,然後繼續往下進行,等條件成熟之後
    在返回來解決。這一類的問題可以用棧解決
根據上述的分析可以寫出下面的程式碼:

void DFS_Non_Recursive(AGraph *&G, int v)
{
    ArcNode *p = NULL;
    stack<int> dfs_stack;    //頂一個棧來記錄訪問過程中的頂點
    int j = 0, k = 0;
    int visit[MAXSIZE] = { 0 };//頂點訪問標記陣列
    for (i = 0;i <G->n;++i)
        visit[i] = 0;
    //訪問頂點v的操作
    visit[i] = 1;    //標記其實頂點v已經被訪問過
    dfs_stack.push(v);    //其實頂點入棧

    //以下是本演算法的關鍵步驟
    while (!(dfs_stack.empty()))
    {
        k = dfs_stack.top();            //取棧頂元素
        p = G->adjlist[k].firstarc;        //p指向該頂點的第一條邊

        //下面這個迴圈是p沿著邊行走並將圖中經過的頂點入棧的過程
        while ( p != NULL && 1 == visit[p->adjvex])
        {
            p = p->nextarc;        //找到當前頂點第一個沒有訪問過的鄰接頂點或者p走到當前連結串列尾部時,while迴圈停止
        }
        if (NULL == p)
            dfs_stack.pop();    //如果p到達當前連結串列尾部,則說明當前頂點的所有點都訪問完畢,當前頂點出棧
        else                    //否則訪問當前鄰接點併入棧
        {
            //Visit(p->adjvex);    //即訪問頂點p->adjvex的函式
            visit[p->adjvex] = 1;
            dfs_stack.push(p->adjvex);
        }
    }
}


    省政府“暢通工程”的目標是使全省任何兩個村莊間都可以實現公路交通(但不一定有直接的公路相連,只要能間接通過公路到達即可)。
    現在得到城鎮道路統計表,表中列出了任意兩個城鎮間修建道路的費用,以及該道路是否修通的狀態,全省一共N個村莊,編寫程式,計算
    出全省需要的最低成本。道路資訊儲存在Road_Array[]陣列中,Road_Array[]陣列定義如下:
    typedef struct
    {
    int a, b;    //a、b道路兩端的兩個村莊
    int cost;//如果a、b間需要修路,則cost為修路費用
    int is;     //is為0代表a、b間還未修路,is等於1代表a、b間已經修好了路
    }Road_Array;        //道路結構體型別
    Road road[ M + 1];//村莊間的道路資訊已經儲存在於road[]中,M為已經定義的常量,假設已修以及待修的道路一共有M條

#define  M 1000    //定義所有道路(待修的+已經修的)
typedef struct
{
    int a, b;    //a、b道路兩端的兩個村莊
    int cost;//如果a、b間需要修路,則cost為修路費用
    int is;     //is為0代表a、b間還未修路,is等於1代表a、b間已經修好了路
}Road_Array;        //道路結構體型別
Road_Array road[ M + 1];//村莊間的道路資訊已經儲存在於road[]中,M為已經定義的常量,假設已修以及待修的道路一共有M條

int GetRoot(int a, int set[])
{
    while (a != set[a] )
    {
        a = set[a];
    }
    return a;
}
#define N 1000 //定義村莊數目
int LowCost(Road_Array  road[])    //本演算法返回最小花費
{
    int i = 0;
    int a = 0, b = 0,min = 0;
    int set[100];        //定義並查集
    for (i = 0; i < N; ++i)
        set[i] = i;        //初始化並查集,各村莊是孤立的,因此自己就是根結點
    for (i = 0; i < M; ++i)
    {
        //下面這個if語句將已經有道路相連的各個村莊合併為一個集合
        if (road[i].is == 1)
        {
            a = GetRoot(road[i].a, set);
            b = GetRoot(road[i].b, set);
            set[a] = b;
        }
    }
        /*C++中的快排函式,即對road[]中的M條道路按照花費進行遞增排序*/
        qsort(road, M, sizeof(Road), [](void const  *t1,void const * t2)->int
            {
                return ((Road_Array *)t1)->cost < ((Road_Array *)t2)->cost ? 1 : 0;
            }
        );
        //下面這個迴圈從road[]陣列挑出逐個應該修的道路
        for (i = 0; i < M; ++i)
        {
            if (0 == road[i].is && (a = GetRoot(road[i].a, set)) != (b = GetRoot(road[i].b, set)))
            {//當a、b不屬於一個集合的時候,並且a、b間沒有道路的時候,將a、b併入同一集合,並記錄下修建a、b間道路所需要的花費
                set[a] = b;
                min += road[i].cost;
            }
        }
        return min;
}