貪心算法——拓撲排序

關於貪心算法介紹:
http://blog.csdn.net/mind_v/article/details/72956707

拓撲排序


問題描述

一個複雜的工程,經常可以分解成一組簡單一些的任務,這些任務完成了,整個工程就完成了。例如汽車組裝問題可以分解成:底盤安裝、車軸安裝、車輪安裝、座位安裝、噴漆、剎車安裝、車門安裝等。但是這些任務之間有個先後順序,例如車軸安裝之前先要進行底盤安裝。

類似的問題,可以將任務和人物的先後順序用有向圖表示——稱爲頂點活動網絡,頂點代表一個任務,有向邊(i,j)代表任務i要在任務j之前完成。下圖就是一個工程,它有6個任務,邊(1,3)表示任務1要在任務3開始前完成,邊(4,6)表示任務4要在任務6之前完成。

任務有向圖

這樣就形成了一個任務序列,對有向圖的任意一條邊(i,j),在這個序列中,任務i一定出現在任務j之前,具有這種性質的序列叫做拓撲序列(topological order),根據有向圖建立拓撲序列的過程稱爲拓撲排序(topological sorting)。

對於這樣一個有向圖有若干個拓撲序列,如1-2-3-4-5-6,1-3-2-4-5-6,2-1-3-4-5-6等。

貪心求解

我們可以通過從左到右分步構造拓撲序列,每一步選擇一個新頂點加入到序列中。選擇新頂點的貪心準則:從剩餘的頂點中選擇一個頂點w,所有鄰接於它的頂點都在序列中。

算法步驟:

令n表示有向圖的頂點數
令theOrder表示空虛列
while(true)
{
  令w是任意一個沒有入邊(v,w)的頂點,其中v不在theOrder中
  如果沒有這樣的頂點w,算法終止
  把w加入到theOrder的尾部
}
if(theOrder的頂點數小於n)
  算法失敗
else
  theOrder是一個拓撲序列

針對上圖,使用這樣一個算法:
從theOrder是一個空序列開始,第一步選擇插入theOrder序列的第一個頂點,頂點1和2都滿足貪心準則。若選擇1,則theOrder{1};第二步插入第二個頂點,這時有兩個個候選頂點2,3。若選擇3,則theOrder{1,3};第三步選擇第三個頂點,候選頂點只有2,則theOrder{1,3,2};第四步候選頂點爲4和5,如果選擇4,則theOrder{1,3,2,4};第五步只有一個候選頂點5,第六步最後一個頂點6,則theOrder{1,3,2,4,5,6}。

數據結構選擇:
用一個一維數組表示theOrder,存儲拓撲序列;用一個棧存儲可以加入theOrder的候選頂點; 用一個一維數組inDegree存儲每個頂點的實時入度,inDegree[j]表示不在theOrder中但鄰接於頂點j的數目。每次向theOrder中添加,一個頂點時,所有鄰接於此頂點的頂點j,theDegree[j]減1,當inDegree變成0時,表示頂點j成爲候選頂點。

C++實現

函數是圖的graph抽象類的成員函數,使用到的接口包括:
directed():確定圖是否是有向圖
numberOfVertices():返回圖的頂點數
vertexIterator類:創造一個頂點的迭代器,其next()成員返回鄰接於改頂點的下一個頂點。

arrayStack是用數組描述的棧,接口:
push(i):將i壓入棧
pop(),刪除棧頂元素

bool topologicalOrder(int *theOrder)
{//返回false,當且僅當有向圖沒有拓撲序列
 //如果存在一個拓撲序列,則將頂點賦給theOrder[0,n-1]

    //確定是有向圖
    if (!directed())
        return;

    int n = numberOfVertices();

    //計算入度
    int *inDegree = new int[n + 1];
    fill(inDegree + 1, inDegree + n + 1, 0);
    for (int i = 1; i <= n; ++i)
    {
        vertexIterator<int> *ii = iterator(i);
        int u;
        if ((u = ii->next()) != 0)
            inDegree[u]++;
    }

    //把入邊數加入到棧中
    arrayStack<int> stack;
    for (int i = 1; i <= n; ++i)
        if (inDegree[i] == 0)
            stack.push(i);

    //生成拓撲序列
    int j = 0;
    while (!stack.empty())
    {
        int theVertex = stack.top();
        stack.pop();
        theOrder[j++] = theVertex;
        //更新入邊數
        vertexIterator<int> *iTheVertex = iterator(theVertex);
        int u;
        while ((u = iTheVertex->next()) != 0)
        {
            inDegree[u]--;
            if (inDegree[u] == 0)
                stack.push(u);
        }
    }
    return (j == n);
}

複雜度分析:
計算入度的for循環的時間複雜度爲O(n2);生成拓撲序列的外部while循環的時間複雜度爲O(n),內層嵌套的while循環的時間複雜度爲O(n),所以總的時間複雜度爲O(n2)。故此算法的時間複雜度爲O(n2)。

發佈了30 篇原創文章 · 獲贊 22 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章