算法:圖解最小生成樹之克魯斯卡爾(Kruskal)算法

算法基本思想:設無向連通網爲G=(V, E),令G的最小生成樹爲T=(U, TE),其初態爲U=V,TE={ },然後,按照邊的權值由小到大的順序,考察G的邊集E中的各條邊。若被考察的邊的兩個頂點屬於T的兩個不同的連通分量,則將此邊作爲最小生成樹的邊加入到T中,同時把兩個連通分量連接爲一個連通分量;若被考察邊的兩個頂點屬於同一個連通分量,則捨去此邊,以免造成迴路,如此下去,當T中的連通分量個數爲1時,此連通分量便爲G的一棵最小生成樹。

我們在前面講過的《克里姆算法》是以某個頂點爲起點,逐步找各頂點上最小權值的邊來構建最小生成樹的。同樣的思路,我們也可以直接就以邊爲目標去構建,因爲權值爲邊上,直接找最小權值的邊來構建生成樹也是很自然的想法,只不過構建時要考慮是否會形成環而已,此時我們就用到了圖的存儲結構中的邊集數組結構,如圖7-6-7
這裏寫圖片描述
假設現在我們已經通過鄰接矩陣得到了邊集數組edges並按權值從小到大排列如上圖。
下面我們對着程序和每一步循環的圖示來看:
算法代碼:(改編自《大話數據結構》)

typedef struct
{
    int begin;
    int end;
    int weight;
} Edge;

/* 查找連線頂點的尾部下標 */
int Find(int *parent, int f)
{
    while (parent[f] > 0)
        f = parent[f];

    return f;
}
/* 生成最小生成樹 */
void MiniSpanTree_Kruskal(MGraph G)
{
    int i, j, n , m;
    int k = 0;
    int parent[MAXVEX];/* 定義一數組用來判斷邊與邊是否形成環路 */

    Edge edges[MAXEDGE];/* 定義邊集數組,edge的結構爲begin,end,weight,均爲整型 */

    /* 此處省略將鄰接矩陣G轉換爲邊集數組edges並按權由小到大排列的代碼*/
    for (i = 0; i < G.numVertexes; i++)
        parent[i] = 0;

    cout << "打印最小生成樹:" << endl;
    for (i = 0; i < G.numEdges; i++)/* 循環每一條邊 */
    {
        n = Find(parent, edges[i].begin);
        m = Find(parent, edges[i].end);
        if (n != m)/* 假如n與m不等,說明此邊沒有與現有的生成樹形成環路 */
        {
            parent[n] = m;/* 將此邊的結尾頂點放入下標爲起點的parent中。 */
            /* 表示此頂點已經在生成樹集合中 */

            cout << "(" << edges[i].begin << ", " << edges[i].end << ") "
                 << edges[i].weight << endl;
        }
    }

}

1、程序 第17~28行是初始化操作,中間省略了一些存儲結構轉換代碼。
2、第30~42行,i = 0 第一次循環,n = Find( parent, 4) = 4; 同理 m = 7; 因爲 n != m 所以parent[4] = 7, 並且打印 “ (4, 7) 7 ” 。此時我們已經將邊(v4, v7)納入到最小生成樹中,如下圖的第一個小圖。
3、繼續循環,當i從1 至 6 時,分別把(v2, v8), (v0, v1), (v0, v5), (v1, v8), (v3, v7), (v1, v6)納入到最小生成樹中,如下圖所示,此時parent數組爲
{ 1, 5, 8, 7, 7, 8, 0, 0, 6 },如何解讀現在這些數字的意義呢?從圖i = 6來看,其實是有兩個連通的邊集合A與B 納入到最小生成樹找中的,如圖7-6-12所示。parent[0] = 1表示v0 和v1 已經在生成樹的邊集合A中,將parent[0] = 1中的 1 改成下標,由parent[1] = 5 ,表示v1 和v5 已經在生成樹的邊集合A中,parent[5] = 8 ,表示v5 和v8 已經在生成樹的邊集合A中,parent[8] = 6 ,表示v8 和v6 已經在生成樹的邊集合A中,parent[6] = 0 表示集合A暫時到頭,此時邊集合A有 v0, v1, v5, v6, v8。查看parent中沒有查看的值,parent[2] = 8,表明v2 和 v8在一個集合中,即A中。再由parent[3] = 7, parent[4] = 7 和 parent[7] = 0 可知v3, v4, v7 在一個邊集合B中。
4、當i = 7時, 調用Find函數,n = m = 6,不再打印,繼續下一循環,即告訴我們,因爲(v5, v6) 使得邊集合A形成了迴路,因此不能將其納入生成樹中,如圖7-6-12所示。
5、當i = 8時與上面相同,由於邊(v1, v2) 使得邊集合A形成了迴路,因此不能將其納入到生成樹中,如圖7-6-12所示。
6、當i = 9時,n = 6, m = 7, 即parent[6] = 7,打印“(6, 7)19” ,此時parent數組爲{ 1, 5, 8, 7, 7, 8, 7, 0, 6 } ,如圖7-6-13所示。
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
最後,我們來總結一下克魯斯卡爾算法的定義:
假設 N = (V, {E} )是連通網,則令最小生成樹的初始狀態爲只有n個頂點而無邊的非連通圖T { V, {} },圖中每個頂點自成一個連通分量。在E中選擇代價最小的邊,若該邊依附的頂點落在T中不同的連通分量上,則將其加入到 T 中,否則捨去此邊而選擇下一條代價最小的邊。依次類推,直至T中所有頂點都在同一連通分量上爲止。
此算法的Find函數由邊數e決定,時間複雜度爲O(loge),而外面有一個for循環e次,所以克魯斯卡爾算法的時間複雜度爲O(eloge)。

對比普里姆和克魯斯卡爾算法,克魯斯卡爾算法主要針對邊來展開,邊數少時效率比較高,所以對於稀疏圖有較大的優勢;而普里姆算法對於稠密圖,即邊數非常多的情況下更好一些。

轉載自:http://blog.csdn.net/jnu_simba/article/details/8870481

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