【數(shù)據(jù)結(jié)構(gòu)】最小生成樹之普里姆(Prim)算法和克魯斯卡爾(Kruskal)算法

最小生成樹

列子引入


如圖假設(shè)v0v8表示9個(gè)村莊,現(xiàn)在需要在這9個(gè)村莊假設(shè)通信網(wǎng)絡(luò)。村莊之間的數(shù)字代表村莊之間的直線距離痹换,求用最小成本完成這9個(gè)村莊的通信網(wǎng)絡(luò)建設(shè)。

分析

  • 這幅圖只一個(gè)帶權(quán)值的圖都弹,即網(wǎng)結(jié)構(gòu)娇豫。
  • 所謂最小成本,就是n個(gè)頂點(diǎn)畅厢,用n-1條邊把一個(gè)連通圖連接起來冯痢,并且使權(quán)值的和最小。

最小生成樹

如果無向連通圖是一個(gè)網(wǎng)圖框杜,那么它的所有生成樹中必有一顆是邊的權(quán)值總和最小的生成樹浦楣,即最小生成樹
找到連通圖的最小生成樹咪辱,有兩種經(jīng)典的算法:普里姆(Prim)算法和克魯斯卡爾(Kruskal)算法


一振劳、普里姆(Prim)算法

圖的鄰接矩陣

普利姆算法步驟

  • 從圖中某一個(gè)頂點(diǎn)出發(fā)(這里選V0),尋找它相連的所有結(jié)點(diǎn)油狂,比較這些結(jié)點(diǎn)的權(quán)值大小历恐,然后連接權(quán)值最小的那個(gè)結(jié)點(diǎn)。(這里是V1
  • 然后將尋找這兩個(gè)結(jié)點(diǎn)相連的所有結(jié)點(diǎn)专筷,找到權(quán)值最小的連接弱贼。(這里是V5).
  • 重復(fù)上一步,知道所有結(jié)點(diǎn)都連接上仁堪。


    最小生成樹

實(shí)現(xiàn)代碼

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

#define MAXEDGE 20
#define MAXVEX 20
#define INIFINTY 65535

typedef struct {
    
    int arc[MAXVEX][MAXVEX];
    int numVertexes, numEdges;
    
}MGraph;

/**
 * 構(gòu)建圖
 */
void CreateMGraph(MGraph * G){
    
    int i, j;
    
    G->numVertexes = 9;  // 9個(gè)頂點(diǎn)
    G->numEdges = 15;  // 15條邊
    
    for (i = 0; i < G->numVertexes; i++) {  // 初始化圖
        for (j = 0; j < G->numVertexes; j++) {
            if (i == j)
                G->arc[i][j] = 0;
            else
                G->arc[i][j] = G->arc[j][i] = INIFINTY;
        }
    }
    
    G->arc[0][1] = 10;
    G->arc[0][5] = 11;
    
    G->arc[1][2] = 18;
    G->arc[1][8] = 12;
    G->arc[1][6] = 16;
    
    G->arc[2][3] = 22;
    G->arc[2][8] = 8;
    
    G->arc[3][4] = 20;
    G->arc[3][7] = 16;
    G->arc[3][6] = 24;
    G->arc[3][8] = 21;
    
    G->arc[4][5] = 26;
    G->arc[4][7] = 7;
    
    G->arc[5][6] = 17;
    
    G->arc[6][7] = 19;
    
    // 利用鄰接矩陣的對(duì)稱性
    for (i = 0; i < G->numVertexes; i++)
        for (j = 0; j < G->numVertexes; j++)
            G->arc[j][i] = G->arc[i][j];
}


/**
 * Prime算法生成最小生成樹
 */
void MiniSpanTree_Prim(MGraph G){
    
    int min,i,j,k;
    
    int adjvex[MAXVEX]; // 保存相關(guān)頂點(diǎn)的下標(biāo)
    int lowcost[MAXVEX]; // 保存相關(guān)頂點(diǎn)間邊的權(quán)值
    
    lowcost[0] = 0;  // 初始化第一個(gè)權(quán)值為0,即v0加入生成樹
    adjvex[0] = 0; // 初始化第一個(gè)頂點(diǎn)下標(biāo)為0
    
    for (i = 1; i < G.numVertexes; i++) {  // 循環(huán)除下標(biāo)為0外的全部頂點(diǎn)
        lowcost[i] = G.arc[0][i];  // 將v0頂點(diǎn)與之右邊的權(quán)值存入數(shù)組
        adjvex[i] = 0; // 初始化都為v0的下標(biāo)
    }
    
    for (i = 1; i < G.numVertexes; i++) {
        
        min = INIFINTY; //初始化最小權(quán)值
        j = 1;
        k = 0;
        
        while (j < G.numVertexes) { // 循環(huán)全部頂點(diǎn)
            if (lowcost[j] != 0 && lowcost[j] < min) {
                min = lowcost[j];  // 讓當(dāng)前權(quán)值變?yōu)樽钚≈?                k = j;  // 將當(dāng)前最小值的下標(biāo)存入k
            }
            j++;
        }
        
        printf("(%d, %d)\n", adjvex[k], k);  // 打印當(dāng)前頂點(diǎn)中權(quán)值最小的邊
        lowcost[k] = 0;             // 將當(dāng)前頂點(diǎn)的權(quán)值設(shè)置為0填渠,表示此頂點(diǎn)已經(jīng)完成任務(wù)
        
        for (j = 1; j < G.numVertexes; j++) {  // 循環(huán)所有頂點(diǎn)
            if (lowcost[j]!= 0 && G.arc[k][j] < lowcost[j]) {  // 如果下標(biāo)為k頂點(diǎn)各邊權(quán)值小于當(dāng)前這些頂點(diǎn)未被加入生成樹權(quán)值
                lowcost[j] = G.arc[k][j]; // 將較小的權(quán)值存入lowcost相應(yīng)的位置
                adjvex[j] = k;   // 將下標(biāo)為k的頂點(diǎn)存入adjvex
            }
        }
    }
}

int main(int argc, const char * argv[]) {
    
    MGraph G;
    CreateMGraph(&G);
    MiniSpanTree_Prim(G);
    
    return 0;
}

代碼解釋

  • 創(chuàng)建了兩個(gè)數(shù)組adjvexlowcost弦聂。adjvex[0] = 0意思就是從V0開始鸟辅,lowcost[0] = 0表示V0已經(jīng)被納入到最小生成樹中。之后凡是lowcost數(shù)組中的值被設(shè)置為0就是表示此下標(biāo)的頂點(diǎn)被納入最小生成樹莺葫。
  • 普里姆算法的時(shí)間復(fù)雜度為O(n^2)匪凉,因?yàn)槭莾蓪友h(huán)嵌套。

代碼運(yùn)行結(jié)果

普里姆算法運(yùn)行結(jié)果

二捺檬、克魯斯卡爾(Kruskal)算法

普里姆算法是從某一頂點(diǎn)為起點(diǎn)再层,逐步找各個(gè)頂點(diǎn)最小權(quán)值的邊來構(gòu)成最小生成樹。那我們也可以直接從邊出發(fā)堡纬,尋找權(quán)值最小的邊來構(gòu)建最小生成樹聂受。不過在構(gòu)建的過程中要考慮是否會(huì)形成環(huán)的情況

邊集數(shù)組存儲(chǔ)圖

邊集數(shù)組

在直接用邊來構(gòu)建最小生成樹的時(shí)候,需要用到邊集數(shù)組結(jié)構(gòu)烤镐,代碼為:

typedef struct {  // 邊集數(shù)組
    int begin;
    int end;
    int weight;
}Edge;

代碼實(shí)現(xiàn)

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

#define MAXEDGE 20
#define MAXVEX 20
#define INIFINTY 65535

typedef struct {
    
    int arc[MAXVEX][MAXVEX];
    int numVertexes, numEdges;
    
}MGraph;

typedef struct {  // 邊集數(shù)組
    int begin;
    int end;
    int weight;
}Edge;

/**
 * 構(gòu)建圖
 */
void CreateMGraph(MGraph * G){
    
    int i, j;
    
    G->numVertexes = 9;  // 9個(gè)頂點(diǎn)
    G->numEdges = 15;  // 15條邊
    
    for (i = 0; i < G->numVertexes; i++) {  // 初始化圖
        for (j = 0; j < G->numVertexes; j++) {
            if (i == j)
                G->arc[i][j] = 0;
            else
                G->arc[i][j] = G->arc[j][i] = INIFINTY;
        }
    }
    
    G->arc[0][1] = 10;
    G->arc[0][5] = 11;
    
    G->arc[1][2] = 18;
    G->arc[1][8] = 12;
    G->arc[1][6] = 16;
    
    G->arc[2][3] = 22;
    G->arc[2][8] = 8;
    
    G->arc[3][4] = 20;
    G->arc[3][7] = 16;
    G->arc[3][6] = 24;
    G->arc[3][8] = 21;
    
    G->arc[4][5] = 26;
    G->arc[4][7] = 7;
    
    G->arc[5][6] = 17;
    
    G->arc[6][7] = 19;
    
    // 利用鄰接矩陣的對(duì)稱性
    for (i = 0; i < G->numVertexes; i++)
        for (j = 0; j < G->numVertexes; j++)
            G->arc[j][i] = G->arc[i][j];
}


/**
 * 交換權(quán)值蛋济、頭、尾
 */
void Swapn(Edge * edges, int i, int j){
    
    int temp;
    temp = edges[i].begin;
    edges[i].begin = edges[j].begin;
    edges[j].begin = temp;
    
    temp = edges[i].end;
    edges[i].end = edges[j].end;
    edges[j].end = temp;
    
    temp = edges[i].weight;
    edges[i].weight = edges[j].weight;
    edges[j].weight = temp;
}

/**
 * 對(duì)權(quán)值進(jìn)行排序
 */
void sort(Edge edges[], MGraph *G){

    int i,j;
    
    for (i = 0;  i < G->numEdges; i++) {
        for (j = i+1; j < G->numEdges; j++) {
            if (edges[i].weight > edges[j].weight)
                Swapn(edges, i, j);
        }
    }
    
    printf("權(quán)值排序之后為:\n");
    
    for (i = 0;  i < G->numEdges; i++) {
        printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight);
    }
}

/**
 * 查找連線頂點(diǎn)的尾部下標(biāo)
 */
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;
    
    Edge edges[MAXEDGE]; // 定義邊集數(shù)組
    int parent[MAXVEX]; // 定義一維數(shù)組來判斷邊與邊是否形成回路
    
    //構(gòu)建邊集數(shù)組并排序
    for (i = 0; i < G.numVertexes - 1; i++) {
        for (j = i+1; j < G.numVertexes; j++) {
            if (G.arc[i][j] < INIFINTY) {
                edges[k].begin = i;
                edges[k].end = j;
                edges[k].weight = G.arc[i][j];
                k++;
            }
        }
    }
    sort(edges, &G);
    
    
    for (i = 0; i < G.numVertexes; i++) {
        parent[i] = 0;
    }
    
    printf("打印最小生成樹:\n");
    for (i = 0;  i < G.numEdges; i++) {
        n = Find(parent, edges[i].begin);
        m = Find(parent, edges[i].end);
        
        if (n != m) {
            parent[n] = m;
            printf("(%d, %d) %d\n",edges[i].begin, edges[i].end
                   , edges[i].weight);
        }
    }
}

int main(int argc, const char * argv[]) {
    
    MGraph G;
    CreateMGraph(&G);
    MiniSpanTree_Kruskal(G);
    
    return 0;
}

代碼解釋

  • 先構(gòu)建邊集數(shù)組炮叶,并排序碗旅,所以前面有對(duì)權(quán)值進(jìn)行排序的方法sort
  • 克魯斯卡爾(Kruskal)算法的時(shí)間復(fù)雜度為O(eloge)镜悉。

運(yùn)行結(jié)果


對(duì)比普里姆(Prim)算法和克魯斯卡爾(Kruskal)算法

  • 克魯斯卡爾(Kruskal)算法主要針對(duì)邊來展開祟辟,邊數(shù)較少時(shí)效率非常高,所以對(duì)于稀疏圖有很大的優(yōu)勢(shì)侣肄;
  • 普里姆(Prim)算法對(duì)于稠密圖旧困,邊數(shù)非常多的情況更好一些。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末茫孔,一起剝皮案震驚了整個(gè)濱河市叮喳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缰贝,老刑警劉巖馍悟,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異剩晴,居然都是意外死亡锣咒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門赞弥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毅整,“玉大人,你說我怎么就攤上這事绽左〉考担” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵拼窥,是天一觀的道長(zhǎng)戏蔑。 經(jīng)常有香客問我蹋凝,道長(zhǎng),這世上最難降的妖魔是什么总棵? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任鳍寂,我火速辦了婚禮,結(jié)果婚禮上情龄,老公的妹妹穿的比我還像新娘迄汛。我一直安慰自己,他們只是感情好骤视,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布鞍爱。 她就那樣靜靜地躺著,像睡著了一般尚胞。 火紅的嫁衣襯著肌膚如雪硬霍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天笼裳,我揣著相機(jī)與錄音唯卖,去河邊找鬼。 笑死躬柬,一個(gè)胖子當(dāng)著我的面吹牛拜轨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播允青,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼橄碾,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了颠锉?” 一聲冷哼從身側(cè)響起法牲,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎琼掠,沒想到半個(gè)月后拒垃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓷蛙,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年悼瓮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片艰猬。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡横堡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出冠桃,到底是詐尸還是另有隱情命贴,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站胸蛛,受9級(jí)特大地震影響培己,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胚泌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肃弟。 院中可真熱鬧玷室,春花似錦、人聲如沸笤受。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)箩兽。三九已至津肛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汗贫,已是汗流浹背身坐。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留落包,地道東北人部蛇。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像咐蝇,于是被迫代替她去往敵國(guó)和親涯鲁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容