1. 程式人生 > >最小生成樹和圖的遍歷

最小生成樹和圖的遍歷

Prim演算法

1.概覽

普里姆演算法Prim演算法),圖論中的一種演算法,可在加權連通圖裡搜尋最小生成樹。意即由此演算法搜尋到的邊子集所構成的樹中,不但包括了連通圖裡的所有頂點英語Vertex (graph theory),且其所有邊的權值之和亦為最小。該演算法於1930年由捷克數學家沃伊捷赫·亞爾尼克英語Vojtěch Jarník發現;並在1957年由美國電腦科學家羅伯特·普里姆英語Robert C. Prim獨立發現;1959年,艾茲格·迪科斯徹再次發現了該演算法。因此,在某些場合,普里姆演算法又被稱為DJP演算法、亞爾尼克演算法或普里姆-亞爾尼克演算法。

2.演算法簡單描述

1).輸入:一個加權連通圖,其中頂點集合為V,邊集合為E;

2).初始化:Vnew = {x},其中x為集合V中的任一節點(起始點),Enew = {},為空;

3).重複下列操作,直到Vnew = V:

a.在集合E中選取權值最小的邊<u, v>,其中u為集合Vnew中的元素,而v不在Vnew集合當中,並且v∈V(如果存在有多條滿足前述條件即具有相同權值的邊,則可任意選取其中之一);

b.將v加入集合Vnew中,將<u, v>邊加入集合Enew中;

4).輸出:使用集合Vnew和Enew來描述所得到的最小生成樹。

下面對演算法的圖例描述

圖例 說明 不可選 可選 已選(Vnew
 

此為原始的加權連通圖。每條邊一側的數字代表其權值。 - - -

頂點D被任意選為起始點。頂點ABEF通過單條邊與D相連。A是距離D最近的頂點,因此將A及對應邊AD以高亮表示。 C, G A, B, E, F D
 

下一個頂點為距離DA最近的頂點。BD為9,距A為7,E為15,F為6。因此,FDA最近,因此將頂點F與相應邊DF以高亮表示。 C, G B, E, F A, D
演算法繼續重複上面的步驟。距離A為7的頂點B被高亮表示。 C B, E, G A, D, F
 

在當前情況下,可以在CEG間進行選擇。CB為8,EB為7,GF為11。E最近,因此將頂點E與相應邊BE高亮表示。 C, E, G A, D, F, B
 

這裡,可供選擇的頂點只有CGCE為5,GE為9,故選取C,並與邊EC一同高亮表示。 C, G A, D, F, B, E

頂點G是唯一剩下的頂點,它距F為11,距E為9,E最近,故高亮表示G及相應邊EG G A, D, F, B, E, C

現在,所有頂點均已被選取,圖中綠色部分即為連通圖的最小生成樹。在此例中,最小生成樹的權值之和為39。 A, D, F, B, E, C, G

3.簡單證明prim演算法

反證法:假設prim生成的不是最小生成樹

1).設prim生成的樹為G0

2).假設存在Gmin使得cost(Gmin)<cost(G0)   則在Gmin中存在<u,v>不屬於G0

3).將<u,v>加入G0中可得一個環,且<u,v>不是該環的最長邊(這是因為<u,v>∈Gmin)

4).這與prim每次生成最短邊矛盾

5).故假設不成立,命題得證.

 4.演算法程式碼實現(未檢驗)

複製程式碼
#define MAX  100000
#define VNUM  10+1                                             //這裡沒有ID為0的點,so id號範圍1~10

int edge[VNUM][VNUM]={/*輸入的鄰接矩陣*/};
int lowcost[VNUM]={0};                                         //記錄Vnew中每個點到V中鄰接點的最短邊
int addvnew[VNUM];                                             //標記某點是否加入Vnew
int adjecent[VNUM]={0};                                        //記錄V中與Vnew最鄰近的點


void prim(int start)
{
     int sumweight=0;
     int i,j,k=0;

     for(i=1;i<VNUM;i++)                                      //頂點是從1開始
     {
        lowcost[i]=edge[start][i];
        addvnew[i]=-1;                                         //將所有點至於Vnew之外,V之內,這裡只要對應的為-1,就表示在Vnew之外
     }

     addvnew[start]=0;                                        //將起始點start加入Vnew
     adjecent[start]=start;
                                                 
     for(i=1;i<VNUM-1;i++)                                        
     {
        int min=MAX;
        int v=-1;
        for(j=1;j<VNUM;j++)                                      
        {
            if(addvnew[j]!=-1&&lowcost[j]<min)                 //在Vnew之外尋找最短路徑
            {
                min=lowcost[j];
                v=j;
            }
        }
        if(v!=-1)
        {
            printf("%d %d %d\n",adjecent[v],v,lowcost[v]);
            addvnew[v]=0;                                      //將v加Vnew

            sumweight+=lowcost[v];                             //計算路徑長度之和
            for(j=1;j<VNUM;j++)
            {
                if(addvnew[j]==-1&&edge[v][j]<lowcost[j])      
                {
                    lowcost[j]=edge[v][j];                     //此時v點加入Vnew 需要更新lowcost
                    adjecent[j]=v;                             
                }
            }
        }
    }
    printf("the minmum weight is %d",sumweight);
}
複製程式碼

5.時間複雜度

這裡記頂點數v,邊數e

鄰接矩陣:O(v2)                 鄰接表:O(elog2v)

Kruskal演算法

1.概覽

Kruskal演算法是一種用來尋找最小生成樹的演算法,由Joseph Kruskal在1956年發表。用來解決同樣問題的還有Prim演算法和Boruvka演算法等。三種演算法都是貪婪演算法的應用。和Boruvka演算法不同的地方是,Kruskal演算法在圖中存在相同權值的邊時也有效。

2.演算法簡單描述

1).記Graph中有v個頂點,e個邊

2).新建圖Graphnew,Graphnew中擁有原圖中相同的e個頂點,但沒有邊

3).將原圖Graph中所有e個邊按權值從小到大排序

4).迴圈:從權值最小的邊開始遍歷每條邊 直至圖Graph中所有的節點都在同一個連通分量中

                if 這條邊連線的兩個節點於圖Graphnew中不在同一個連通分量中

                                         新增這條邊到圖Graphnew

圖例描述:

首先第一步,我們有一張圖Graph,有若干點和邊 

將所有的邊的長度排序,用排序的結果作為我們選擇邊的依據。這裡再次體現了貪心演算法的思想。資源排序,對區域性最優的資源進行選擇,排序完成後,我們率先選擇了邊AD。這樣我們的圖就變成了右圖

在剩下的變中尋找。我們找到了CE。這裡邊的權重也是5

依次類推我們找到了6,7,7,即DF,AB,BE。

下面繼續選擇, BC或者EF儘管現在長度為8的邊是最小的未選擇的邊。但是現在他們已經連通了(對於BC可以通過CE,EB來連線,類似的EF可以通過EB,BA,AD,DF來接連)。所以不需要選擇他們。類似的BD也已經連通了(這裡上圖的連通線用紅色表示了)。

最後就剩下EG和FG了。當然我們選擇了EG。最後成功的圖就是右:

3.簡單證明Kruskal演算法

對圖的頂點數n做歸納,證明Kruskal演算法對任意n階圖適用。

歸納基礎:

n=1,顯然能夠找到最小生成樹。

歸納過程:

假設Kruskal演算法對n≤k階圖適用,那麼,在k+1階圖G中,我們把最短邊的兩個端點a和b做一個合併操作,即把u與v合為一個點v',把原來接在u和v的邊都接到v'上去,這樣就能夠得到一個k階圖G'(u,v的合併是k+1少一條邊),G'最小生成樹T'可以用Kruskal演算法得到。

我們證明T'+{<u,v>}是G的最小生成樹。

用反證法,如果T'+{<u,v>}不是最小生成樹,最小生成樹是T,即W(T)<W(T'+{<u,v>})。顯然T應該包含<u,v>,否則,可以用<u,v>加入到T中,形成一個環,刪除環上原有的任意一條邊,形成一棵更小權值的生成樹。而T-{<u,v>},是G'的生成樹。所以W(T-{<u,v>})<=W(T'),也就是W(T)<=W(T')+W(<u,v>)=W(T'+{<u,v>}),產生了矛盾。於是假設不成立,T'+{<u,v>}是G的最小生成樹,Kruskal演算法對k+1階圖也適用。

由數學歸納法,Kruskal演算法得證。

4.程式碼演算法實現

複製程式碼
typedef struct          
{        
    char vertex[VertexNum];                                //頂點表         
    int edges[VertexNum][VertexNum];                       //鄰接矩陣,可看做邊表         
    int n,e;                                               //圖中當前的頂點數和邊數         
}MGraph; 
 
typedef struct node  
{  
    int u;                                                 //邊的起始頂點   
    int v;                                                 //邊的終止頂點   
    int w;                                                 //邊的權值   
}Edge; 

void kruskal(MGraph G)  
{  
    int i,j,u1,v1,sn1,sn2,k;  
    int vset[VertexNum];                                    //輔助陣列,判定兩個頂點是否連通   
    int E[EdgeNum];                                         //存放所有的邊   
    k=0;                                                    //E陣列的下標從0開始   
    for (i=0;i<G.n;i++)  
    {  
        for (j=0;j<G.n;j++)  
        {  
            if (G.edges[i][j]!=0 && G.edges[i][j]!=INF)  
            {  
                E[k].u=i;  
                E[k].v=j;  
                E[k].w=G.edges[i][j];  
                k++;  
            }  
        }  
    }     
    heapsort(E,k,sizeof(E[0]));                            //堆排序,按權值從小到大排列       
    for (i=0;i<G.n;i++)                                    //初始化輔助陣列   
    {  
        vset[i]=i;  
    }  
    k=1;                                                   //生成的邊數,最後要剛好為總邊數   
    j=0;                                                   //E中的下標   
    while (k<G.n)  
    {   
        sn1=vset[E[j].u];  
        sn2=vset[E[j].v];                                  //得到兩頂點屬於的集合編號   
        if (sn1!=sn2)                                      //不在同一集合編號內的話,把邊加入最小生成樹   
        {
            printf("%d ---> %d, %d",E[j].u,E[j].v,E[j].w);       
            k++;  
            for (i=0;i<G.n;i++)  
            {  
                if (vset[i]==sn2)  
                {  
                    vset[i]=sn1;  
                }  
            }             
        }  
        j++;  
    }  
}  
複製程式碼


時間複雜度:elog2e  e為圖中的邊數

 ------------------------------------------------------------------------------------------------

圖的遍歷是樹的遍歷的推廣,是按照某種規則(或次序)訪問圖中各頂點依次且僅一次的操作,亦是將網路結構按某種規則線性化的過程。 由於圖存在迴路,為區別一頂點是否被訪問過和避免頂點被多次訪問,在遍歷過程中,應記下每個訪問過的頂點,即每個頂點對應有一個標誌位,初始為False,一旦該頂點被訪問,就將其置為True,以後若又碰到該頂點時,視其標誌的狀態,而決定是否對其訪問。 對圖的遍歷通常有"深度優先搜尋"和"廣度優先搜尋"方法,二者是人工智慧的一個基礎。 深度優先搜尋(Depth First Search,簡稱DFS) 演算法思路: 類似樹的先根遍歷。設初始化時,圖中各頂點均未被訪問,從圖中某個頂點(設為V0)出發,訪問V0,然後搜尋V0的一個鄰接點Vi,若Vi未被訪問,則訪問之,在 搜尋Vi的一個鄰接點(深度優先)...。若某頂點的鄰接點全部訪問完畢,則回溯(Backtracking)到它的上一頂點,然後再從此頂點又按深度優先的方法搜尋下去,...,直到能訪問的頂點都訪問完畢為止。 設圖G10如下圖所示: 通過深度優先如下: 廣度優先搜尋(Breadth First Search),簡稱BFS 演算法思路: 類似樹的按層次遍歷。初始時,圖中各頂點均未被訪問,從圖中某頂點(V0)出發,訪問V0,並依次訪問V0的各鄰接點(廣度優先)。然後,分別從這些被訪問過的頂點出發,扔仍按照廣度優先的策略搜尋其它頂點,....,直到能訪問的頂點都訪問完畢為止。 為控制廣度優先的正確搜尋,要用到佇列技術,即訪問完一個頂點後,讓該頂點的序號進隊。然後取相應隊頭(出隊),考察訪問過的頂點的各鄰接點,將未訪問過的鄰接點訪問 後再依次進隊,...,直到隊空為止。 通過廣度優先如下: 下面看一下實現程式碼:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #define MAX 20
  5. //訪問記錄
  6. int visit[MAX];
  7. //圖的結構設計
  8. typedef struct
  9. {
  10.     int vex[MAX];//記錄頂點
  11.     int adjmatrix[MAX][MAX];//鄰接矩陣
  12.     int n;//頂點的個數
  13. }GRAPH;
  14. //初始化圖
  15. int init_graph(GRAPH *pG)
  16. {
  17.     memset(pG,0,sizeof(GRAPH));
  18.     pG->= -1;
  19.     printf("input vex\n");
  20.     while(scanf("%d",&pG->vex[++pG->n]));
  21.     while(getchar() != '\n');
  22. #ifndef _DEBUG_
  23.     int i = 0;
  24.     for(= 0;< pG->;++)
  25.     {
  26.         printf("V%d ",pG->vex[i]);
  27.     }
  28.     printf("\n");    
  29. #endif
  30.     return 0;
  31. }
  32. //獲取頂點的位置
  33. int locatevex(GRAPH *pG,int vex)
  34. {
  35.     int i = 0;
  36.     for(= 0;< pG->n;++)
  37.     {
  38.         if(pG->vex[i] == vex )
  39.             return i;
  40.     }
  41.     return 0;
  42. }
  43. //輸入圖的頂點之間的邊
  44. int input_edge(GRAPH *pG)
  45. {
  46.     int vex1,vex2;
  47.     int i,j;
  48.     printf("input edge(i,j):\n");
  49.     //任意字母鍵結束
  50.     while(scanf("(%d,%d)"

    相關推薦

    小生成樹

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

    論--小生成樹短路1

    圖論的兩個經典問題。 1、先介紹樹的概念: 樹的概念挺簡單的,一個祖先,一個兒子只能有一個父親節點,不能形成環。n個節點只能有n-1條邊,要不然會形成環。(易得知) 2、再來講講我用來存圖的兩種方式:

    算法學習筆記(六) 二叉樹—深搜 DFS 與廣搜 BFS

    創建 mark preorder 第一個 高度 變量初始化 term link 文章 圖的深搜與廣搜 復習下二叉樹、圖的深搜與廣搜。從圖的遍歷說起。圖的遍歷方法有兩種:深度優先遍歷(Depth First Search),

    小生成樹短路徑的總結

    1.求最小生成樹有兩種方法: ①克魯斯卡爾演算法:這個演算法是以邊為單位(包括邊的所有的資訊:兩個端點+權值)進行儲存的,然後將邊按照權值的從小到大的順序進行排序,然後將第一條邊連線起來,第二條邊連線起來,就這樣一直迴圈,直到所有的邊都被連線起來為止,在這期間,你需要判斷

    小生成樹倍增法求lca(Uva11354Bond)

    #include<bits/stdc++.h> #define maxn 600000 #define inf (1124984) using namespace std; int head[maxn],book[maxn],deep[maxn],pre[maxn],fa[maxn],mxcos

    小生成樹切分定理

    本文提綱 最小生成樹 切分定理 證明 1.最小生成樹 最小生成樹問題,針對帶權無向圖,就是在一個V個結點的連通圖裡面尋找V-1條邊,使得這個圖連通,並且權值之和最小的問題。 2.切分定理(Cut Property) 定義一:把圖中的結點分

    資料結構--C語言--的深度優先,廣度優先,拓撲排序,用prime演算法實現小生成樹,用迪傑斯特拉演算法實現關鍵路徑關鍵活動的求解,短路徑

    實驗七  圖的深度優先遍歷(選做,驗證性實驗,4學時) 實驗目的 熟悉圖的陣列表示法和鄰接表儲存結構,掌握構造有向圖、無向圖的演算法 ,在掌握以上知識的基礎上,熟悉圖的深度優先遍歷演算法,並實現。 實驗內容 (1)圖的陣列表示法定義及

    資料結構作業15—小生成樹(選擇題)

    2-1給定有權無向圖如下。關於其最小生成樹,下列哪句是對的? (3分) A.邊(B, F)一定在樹中,樹的總權重為23 B.邊(H, G)一定在樹中,樹的總權重為20 C.最小生成樹唯一,其總權重為20 D.最小生成樹不唯一,其總權重為23

    (dfs、bfs、短路、小生成樹、拓撲排序)

    程式碼如下: #include <cstdio> #include <cmath> #include <vector> #include <cstring> #include <algorithm> using n

    的廣度、深度小生成樹書演算法(Prim、Kruskal)

    typedef struct { char vertex[VertexNum]; //頂點表 int edges[VertexNum][VertexNum];

    資料結構:——小生成樹短路徑演算法

    前言 在這裡,如果大家對圖或者資料結構還不太熟悉,想找一個動態的生成過程來參考,這是一個不錯的網站. 知識框架 圖的定義 線上性結構中,資料元素之間滿足唯一的線性關係,每個資料元素(除第一個和最後一個外)只有一個直接前趨和一個直接後繼; 在樹形結構中,資料元素之間有著明顯的層次關係,

    (有向,無向)的鄰接矩陣表示C++實現(,拓撲排序,短路徑,小生成樹) Implement of digraph and undigraph using adjacency matrix

    本文實現了有向圖,無向圖的鄰接矩陣表示,並且實現了從建立到銷燬圖的各種操作。 以及兩種圖的深度優先遍歷,廣度優先遍歷,Dijkstra最短路徑演算法,Prim最小生成樹演算法,有向圖的拓撲排序演算法。 通過一個全域性變數控制當前圖為有向圖還是無向圖。 若為無向圖,則生成的

    (c++)資料結構與演算法之:鄰接矩陣、深度廣度、構造小生成樹(prim、kruskal演算法)

    //圖的鄰接矩陣實現 //廣度遍歷bfs和深度遍歷dfs //構造最小生成樹的prim、kruskal演算法 #include <iostream> #include<stack> #include<queue> #define WEI

    小生成樹短路徑

    這一篇我們要總結的是圖(Graph),圖可能比我們之前學習的線性結構和樹形結構都要複雜,不過沒有關係,我們一點一點地來總結,那麼關於圖我想從以下幾點進行總結: 1,圖的定義? 2,圖相關的概念和術語? 3,圖的建立和遍歷? 4,最小生成樹和最短路徑? 5,演算法實現? 回到頂部一,圖的定義 什麼

    的生成樹小生成樹

    一、生成樹的概念                 在一個任意連通圖G中,如果取它的全部頂點和一部分邊構成一個子圖G',即:V(G')=V(G)和E

    論經典演算法(通俗易懂):短路徑小生成樹

    一、最短路問題 求圖的最短路問題,幾乎是圖論的必學內容,而且在演算法分析與設計中也會涉及。很多書上內容, 實在沒法看,我們的圖論教材,更是編的非常糟糕,吐槽,為啥要用自己學校編的破教材,不過據說 下一屆終於要換書了。 言歸正傳,開始說明最短路問題。

    小生成樹:Prim演算法Kruskal演算法

    1. 圖的最小生成樹 生成樹的定義:如果連通圖G的一個子圖是一棵包含G的所有頂點的樹,則該子圖稱為G的生成樹。 生成樹是連通圖的包含圖中的所有頂點的極小連通子圖。它並不唯一,從不同的頂點出發進行遍歷,可以得到不同的生成樹。 其中,權值最小的樹就是最小生成樹

    --生成樹小生成樹

    樹(自由樹)、無序樹和有根樹      自由樹就是一個無迴路的連通圖(沒有確定根)(在自由樹中選定一頂點做根,則成為一棵通常的樹)。      從根開始,為每個頂點(在樹中通常稱作結點)的孩子規定從左到右的次序,則它就成為一棵有序樹。      在圖的應用中,我們常常需要求

    第七章 小生成樹之prime演算法 kruskal演算法)

    最小生成樹 所謂最小生成樹,就是在一個具有N個頂點的帶權連通圖G中,如果存在某個子圖G’,其包含了圖G中的所有頂點和一部分邊,且不形成迴路,並且子圖G’的各邊權值之和最小,則稱G’為圖G的最小生成樹。 由定義我們可得知最小生成樹的

    深度廣度優先小生成樹

    怎麼用圖的深度和廣度優先遍歷來遍歷樹呢?我是這樣想的,把樹構造成圖就行了。 // 圖的遍歷.cpp : Defines the entry point for the console application. // #include "stdafx.h" #includ