1. 程式人生 > >克魯斯卡爾演算法+普里姆演算法 詳解

克魯斯卡爾演算法+普里姆演算法 詳解

克魯斯卡爾演算法:

【1】克魯斯卡爾演算法

普里姆演算法是以某頂點為起點,逐步找各頂點上最小權值的邊來構建最小生成樹。

克魯斯卡爾演算法是直接以邊為目標去構建。

因為權值是在邊上,直接去找最小權值的邊來構建生成樹也是很自然的想法,只不過構建時要考慮是否會形成環路而已。

此時我們用到了圖的儲存結構中的邊集陣列結構。

以下是邊集陣列結構的定義程式碼:

本演算法所用同普里姆演算法的例項,我們直接建立圖的邊集陣列。

並對邊的權值從小到大排序後如下圖:

【2】克魯斯卡爾演算法及詳解

克魯斯卡爾演算法及其詳解:

鑑於此演算法很簡單,當 i=0, i=1, i=2時執行結果可以眼觀,不再贅述。直接分析重點:

此演算法的Find函式由邊數e決定,時間複雜度為O(loge),而外面有一個for迴圈e次。

所以克魯斯卡爾演算法的時間複雜度為O(eloge)

對比兩個演算法:

克魯斯卡爾演算法主要針對邊展開,邊數少時效率會很高,所以對於稀疏圖有優勢

而普利姆演算法對於稠密圖,即邊數非常多的情況會好些。

【3】克魯斯卡爾演算法實現

實現程式碼如下:

/克魯斯卡爾演算法
//在連通網中求出最小生成樹

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

#define MAXEDGE 20
#define MAXVEX  20
#define INFINITY 65535

typedef struct
{
    int arc[MAXVEX][MAXVEX];
    int numVertexes, numEdges;//頂點數,邊數
}MGraph;

typedef struct
{
    int begin;
    int end;
    int weight;
}Edge;   //對邊集陣列Edge結構的定義

//建立圖的鄰接矩陣
void CreateMGraph(MGraph *G) {
    int i, j;

    G->numEdges=11;
    G->numVertexes=7;

    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] = INFINITY;
        }
    }
    G->arc[0][1]=7;
    G->arc[0][3]=5;
    G->arc[1][2]=8;
    G->arc[1][3]=9;
    G->arc[1][4]=7;
    G->arc[2][4]=5;
    G->arc[3][4]=15;
    G->arc[3][5]=6;
    G->arc[4][5]=8;
    G->arc[4][6]=9;
    G->arc[5][6]=11;

    for(i = 0; i < G->numVertexes; i++) {
        for(j = i; j < G->numVertexes; j++) {
            G->arc[j][i] =G->arc[i][j];
        }
    }

}

//快速排序的條件
int cmp(const void* a, const void* b) {
    return (*(Edge*)a).weight - (*(Edge*)b).weight;
}

//找到根節點
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,均為整型

    // 用來構建邊集陣列並排序(將鄰接矩陣的對角線右邊的部分存入邊集陣列中)
    for ( i = 0; i < G.numVertexes-1; i++) {
        for (j = i + 1; j < G.numVertexes; j++) {
            if (G.arc[i][j] < INFINITY) {
                edges[k].begin = i; //編號較小的結點為首
                edges[k].end = j;   //編號較大的結點為尾
                edges[k].weight = G.arc[i][j];
                k++;
            }
        }
    }

    //為邊集陣列Edge排序
    qsort(edges, G.numEdges, sizeof(Edge), cmp);

    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);//尋找邊edge[i]的“首節點”所在樹的樹根
        m = Find(parent, edges[i].end);//尋找邊edge[i]的“尾節點”所在樹的樹根

        //假如n與m不等,說明兩個頂點不在一棵樹內,因此這條邊的加入不會使已經選擇的邊集產生迴路
        if (n != m) {
            parent[n] = m;
            printf("(%d, %d) %d\n", edges[i].begin, edges[i].end, edges[i].weight);
        }
    }
}

int main(void)
{
    MGraph G;
    CreateMGraph(&G);
    MiniSpanTree_Kruskal(G);

    return 0;
}


普里姆演算法:

普里姆(Prim)演算法,和克魯斯卡爾演算法一樣,是用來求加權連通圖的最小生成樹的演算法。

基本思想 
對於圖G而言,V是所有頂點的集合;現在,設定兩個新的集合U和T,其中U用於存放G的最小生成樹中的頂點,T存放G的最小生成樹中的邊。 從所有uЄU,vЄ(V-U) (V-U表示出去U的所有頂點)的邊中選取權值最小的邊(u, v),將頂點v加入集合U中,將邊(u, v)加入集合T中,如此不斷重複,直到U=V為止,最小生成樹構造完畢,這時集合T中包含了最小生成樹中的所有邊。

普里姆演算法圖解

以上圖G4為例,來對普里姆進行演示(從第一個頂點A開始通過普里姆演算法生成最小生成樹)。

初始狀態:V是所有頂點的集合,即V={A,B,C,D,E,F,G};U和T都是空! 
第1步:將頂點A加入到U中。 
    此時,U={A}。 
第2步:將頂點B加入到U中。 
    上一步操作之後,U={A}, V-U={B,C,D,E,F,G};因此,邊(A,B)的權值最小。將頂點B新增到U中;此時,U={A,B}。 
第3步:將頂點F加入到U中。 
    上一步操作之後,U={A,B}, V-U={C,D,E,F,G};因此,邊(B,F)的權值最小。將頂點F新增到U中;此時,U={A,B,F}。 
第4步:將頂點E加入到U中。 
    上一步操作之後,U={A,B,F}, V-U={C,D,E,G};因此,邊(F,E)的權值最小。將頂點E新增到U中;此時,U={A,B,F,E}。 
第5步:將頂點D加入到U中。 
    上一步操作之後,U={A,B,F,E}, V-U={C,D,G};因此,邊(E,D)的權值最小。將頂點D新增到U中;此時,U={A,B,F,E,D}。 
第6步:將頂點C加入到U中。 
    上一步操作之後,U={A,B,F,E,D}, V-U={C,G};因此,邊(D,C)的權值最小。將頂點C新增到U中;此時,U={A,B,F,E,D,C}。 
第7步:將頂點G加入到U中。 
    上一步操作之後,U={A,B,F,E,D,C}, V-U={G};因此,邊(F,G)的權值最小。將頂點G新增到U中;此時,U=V。

此時,最小生成樹構造完成!它包括的頂點依次是:A B F E D C G

普里姆演算法的程式碼說明

以"鄰接矩陣"為例對普里姆演算法進行說明,對於"鄰接表"實現的圖在後面會給出相應的原始碼。

1. 基本定義

// 鄰接矩陣
typedef struct _graph
{
    char vexs[MAX];       // 頂點集合
    int vexnum;           // 頂點數
    int edgnum;           // 邊數
    int matrix[MAX][MAX]; // 鄰接矩陣
}Graph, *PGraph;

// 邊的結構體
typedef struct _EdgeData
{
    char start; // 邊的起點
    char end;   // 邊的終點
    int weight; // 邊的權重
}EData;


Graph是鄰接矩陣對應的結構體。 
vexs用於儲存頂點,vexnum是頂點數,edgnum是邊數;matrix則是用於儲存矩陣資訊的二維陣列。例如,matrix[i][j]=1,則表示"頂點i(即vexs[i])"和"頂點j(即vexs[j])"是鄰接點;matrix[i][j]=0,則表示它們不是鄰接點。 
EData是鄰接矩陣邊對應的結構體。

2. 普里姆演算法

#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<string.h>
#define MAX 100
#define INF (~(0x1<<31))
typedef struct Graph
{
    char vexs[MAX];
    int vexnum;
    int edgnum;
    int matrix[MAX][MAX];
} Graph,*PGraph;

typedef struct EdgeData
{
    char start;
    char end;
    int weight;
} EData;

static int get_position(Graph g,char ch)
{
    int i;
    for(i=0; i<g.vexnum; i++)
        if(g.vexs[i]==ch)
            return i;
    return -1;
}

Graph* create_graph()
{
    char vexs[]= {'A','B','C','D','E','F','G'};
    int matrix[][7]=
    {
        {0,12,INF,INF,INF,16,14},
        {12,0,10,INF,INF,7,INF},
        {INF,10,0,3,5,6,INF},
        {INF,INF,3,0,4,INF,INF},
        {INF,INF,5,4,0,INF,8},
        {16,7,6,INF,2,0,9},
        {14,INF,INF,INF,8,9,0}
    };
    int vlen=sizeof(vexs)/sizeof(vexs[0]);
    int i,j;
    Graph *pG;
    if((pG=(Graph*)malloc(sizeof(Graph)))==NULL)
        return NULL;
    memset(pG,0,sizeof(pG));
    pG->vexnum=vlen;
    for(i=0; i<pG->vexnum; i++)
        pG->vexs[i]=vexs[i];
    for(i=0; i<pG->vexnum; i++)
        for(j=0; j<pG->vexnum; j++)
            pG->matrix[i][j]=matrix[i][j];
    for(i=0; i<pG->vexnum; i++)
    {
        for(j=0; j<pG->vexnum; j++)
        {
            if(i!=j&&pG->matrix[i][j]!=INF)
                pG->edgnum++;
        }
    }
    pG->edgnum/=2;
    return pG;
}

void print_graph(Graph G)
{
    int i,j;
    printf("Matrix Graph: \n");
    for(i=0; i<G.vexnum; i++)
    {
        for(j=0; j<G.vexnum; j++)
            printf("%10d ",G.matrix[i][j]);
        printf("\n");
    }
}

EData* get_edges(Graph G)
{
    EData *edges;
    edges=(EData*)malloc(G.edgnum*sizeof(EData));
    int i,j;
    int index=0;
    for(i=0; i<G.vexnum; i++)
    {
        for(j=i+1; j<G.vexnum; j++)
        {
            if(G.matrix[i][j]!=INF)
            {
                edges[index].start=G.vexs[i];
                edges[index].end=G.vexs[j];
                edges[index].weight=G.matrix[i][j];
                index++;
            }
        }
    }
    return edges;
}

void prim(Graph G,int start)
{
    int min,i,j,k,m,n,sum;
    int index=0;
    char prim[MAX];
    int weight[MAX];

    prim[index++]=G.vexs[start];

    for(i=0; i<G.vexnum; i++)
        weight[i]=G.matrix[start][i];
    weight[start]=0;

    for(i=0; i<G.vexnum; i++)
    {
       //i用來控制迴圈的次數,每次加入一個結點,但是因為start已經加入,所以當i為start是跳過
        if(start==i)
            continue;
        j=0;
        k=0;
        min=INF;
        for(k=0; k<G.vexnum; k++)
        {
            if(weight[k]&&weight[k]<min)
            {
                min=weight[k];
                j=k;
            }
        }
        sum+=min;
        prim[index++]=G.vexs[j];
        weight[j]=0;
        for(k=0; k<G.vexnum; k++)
        {
            if(weight[k]&&G.matrix[j][k]<weight[k])
                weight[k]=G.matrix[j][k];
        }
    }
    // 計算最小生成樹的權值
    sum = 0;
    for (i = 1; i < index; i++)
    {
        min = INF;
        // 獲取prims[i]在G中的位置
        n = get_position(G, prim[i]);
        // 在vexs[0...i]中,找出到j的權值最小的頂點。
        for (j = 0; j < i; j++)
        {
            m = get_position(G, prim[j]);
            if (G.matrix[m][n]<min)
                min = G.matrix[m][n];
        }
        sum += min;
    }
    printf("PRIM(%c)=%d: ", G.vexs[start], sum);
    for (i = 0; i < index; i++)
        printf("%c ", prim[i]);
    printf("\n");
}

int main()
{
    Graph *pG;
    pG=create_graph();
    print_graph(*pG);
    prim(*pG,0);
}


執行結果:


相關推薦

最小生成樹演算法演算法演算法

普里姆演算法—Prim演算法 演算法思路:  從已選頂點所關聯的未選邊中找出權重最小的邊,並且生成樹不存在環。 其中,已選頂點是構成最小生成樹的結點,未選邊是不屬於生成樹中的邊。 (普里姆演算法與求最短路徑的迪傑斯塔拉演算法思想很類似)   下面我們對下面這幅

【最小生成樹】Prim 演算法,Kruskal 演算法生成 最小生成樹

1.Analyse 來自兩位科學家。 生成最小生成樹,從0頂點出發,最小生成樹包含所以頂點>_<,這個作業難道好像就是似乎改個矩陣? 最好還是把最小生成樹變成一條路線,這樣就不用去,自己找路線了。 題目圖如圖 2.源自老師的Code Print 1Prim

圖->連通性->最小生成樹(演算法) 最小生成樹(演算法)

文字描述   上一篇部落格介紹了最小生成樹(普里姆演算法),知道了普里姆演算法求最小生成樹的時間複雜度為n^2, 就是說複雜度與頂點數無關,而與弧的數量沒有關係;   而用克魯斯卡爾(Kruskal)演算法求最小生成樹則恰恰相反。它的時間複雜度為eloge (e為網中邊的數目),因此它相對於普里姆演算法而

演算法演算法

最近資料結構老師講了好幾個演算法,今晚上正好有空,所以就來整理一下 一:Kruskal演算法思想:直接以邊為目標去構建最小生成樹,注意只找出n-1條邊即可,並且不能形成迴路。圖的儲存結構採用的是邊集陣列,且權值相等的邊在陣列中的排練次序是任意的,如果圖中的邊數較多則此演算法會很浪費時間! 二

演算法+演算法

克魯斯卡爾演算法: 【1】克魯斯卡爾演算法 普里姆演算法是以某頂點為起點,逐步找各頂點上最小權值的邊來構建最小生成樹。 克魯斯卡爾演算法是直接以邊為目標去構建。 因為權值是在邊上,直接去找最小權值的邊來構建生成樹也是很自然的想法,只不過構建時要考慮是否會形成環路

[Sicily 1090 Highways] 求最小生成樹的兩種演算法演算法/演算法

(1)問題描述: 政府建公路把所有城市聯絡起來,使得公路最長的邊最短,輸出這個最長的邊。 (2)基本思路: 使得公路最長的邊最短其實就是要求最小生成樹。 (3)程式碼實現: 普里姆演算法: #

最小生成樹---演算法(Prim演算法)和演算法(Kruskal演算法

      **最小生成樹的性質:MST性質(假設N=(V,{E})是一個連通網,U是頂點集V的一個非空子集,如果(u,v)是一條具有最小權值的邊,其中u屬於U,v屬於V-U,則必定存在一顆包含邊(u,v)的最小生成樹)** # 普里姆演算法(Pri

最小生成樹(演算法演算法

設G = (V,E)是無向連通帶權圖,即一個網路。E中的每一條邊(v,w)的權為c[v][w]。如果G的子圖G’是一棵包含G的所有頂點的樹,則稱G’為G的生成樹。生成樹上各邊權的總和稱為生成樹的耗費。在G的所有生成樹中,耗費最小的生成樹稱為G的最小生成樹。構造最小生成樹的兩種方

最小生成樹演算法演算法演算法實現

最小生成樹演算法: 普里姆演算法:頂點集合N,輔助頂點集合S,初始化中,將出發點vi加入S,並從N中刪除 1.從頂點集合N中找到一條到集合S最近的邊(vi,vj),儲存該邊,並將vj從N移到S中 2.重複1步驟直至所有頂點加入S集合 普里姆演算法:與邊的多少關係不大,適合計算邊稠密的圖

Prim(演算法+Kruskal(演算法

Prim(普利姆)演算法 1.概覽 普里姆演算法(Prim演算法),圖論中的一種演算法,可在加權連通圖裡搜尋最小生成樹。意即由此演算法搜尋到的邊子集所構成的樹中,不但包括了連通圖裡的所有頂點(英語:Vertex (graph theory)),且其所有邊的權值之和亦

最小生成樹算法(算法和算法)

algo 貪心 size cin out visit cast 聯通 兩個 一般最小生成樹算法分成兩種算法: 一個是克魯斯卡爾算法:這個算法的思想是利用貪心的思想,對每條邊的權值先排個序,然後每次選取當前最小的邊,判斷一下這條邊的點是否已經被選過了,也就是已經在樹內了,一般

luogu P3366 【模板】最小生成樹(演算法)

題目描述 如題,給出一個無向圖,求出最小生成樹,如果該圖不連通,則輸出orz 輸入輸出格式 輸入格式: 第一行包含兩個整數N、M,表示該圖共有N個結點和M條無向邊。(N<=5000,M<=200000) 接下來M行每行包含三個整數Xi、Yi、Zi,表示有一條長度為Zi的無向邊連線結點Xi

最小生成樹-HDU1233 演算法 模板

題意就是給你一個圖,然後算這個圖衍生的最小生成樹 簡直就是模板題 程式碼: #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using

JS實現最小生成樹之(Kruskal)演算法

  克魯斯卡爾演算法列印最小生成樹:   構造出所有邊的集合 edges,從小到大,依次選出篩選邊列印,遇到閉環(形成迴路)時跳過。 JS程式碼: 1 //定義鄰接矩陣 2 let Arr2 = [ 3 [0, 10, 65535, 65535, 65535,

第十三週專案1最小生成樹的演算法

/*Copyright (c) 2015, 煙臺大學計算機與控制工程學院 * All rights reserved. * 檔名稱:H1.cpp * 作者:辛志勐 * 完成日期:2015年11月30日 * 版本號:VC6.0 * 問題描述:最小生成樹的克魯斯卡爾演算法 * 輸入描述:無 * 程式輸

最小生成樹----演算法----java版

  踉踉蹌蹌寫出來了,原理我基本懂了,但是感覺有點講不出來,這裡只貼一下程式碼: 圖如上: package cn.nrsc.graph; /** * * @author 孫川 最小生成樹-克魯斯卡爾演算法 * */ public class Graph_Kru

最小生成樹之kruskal(演算法(C++實現)

參考部落格:https://blog.csdn.net/YF_Li123/article/details/75195549   最小生成樹之kruskal(克魯斯卡爾)演算法 kruskal演算法:同樣解決最小生成樹的問題,和prim演算法不同,kruskal演算法採用了邊貪心

(Kruskal)演算法求最小生成樹

                1、基本思想:設無向連通網為G=(V, E),令G的最小生成樹為T=(U, TE),其初態為U=V,TE={ },然後,按照邊的權值由小到大的順序,考察G的邊集E中的各條邊。若被考察的邊的兩個頂點屬於T的兩個不同的連通分量,則將此邊作為最小生成樹的邊加入到T中,同時把兩個連通分

一步一步寫演算法(之演算法 上)

                【 宣告:版權所有,歡迎轉載,請勿用於商業用途。  聯絡信箱:feixiaoxing @163.com】    克魯斯卡爾演算法是計算最小生成樹的一種演算法。和prim演算法(上,中,下)按照節點進行查詢的方法不一樣,克魯斯卡爾演算法是按照具體的線段進行的。現在我們假設一個圖有

筆記:演算法

//用到了並查集和堆排序解決最小生成樹,思路就是權值邊排序(堆排序),然後從最小權值邊取起,邊取邊判定是否有迴路(用並查集),有迴路則放棄該邊。思路相對簡單,直接看程式碼。 #define MaxSize 100 typedef struct{ int a,b;//邊