最小生成樹之prim算法

這是我說的那個博客,爲了防止做者刪除,我直接複製過來,網頁是最小生成樹html

邊賦以權值的圖稱爲網或帶權圖,帶權圖的生成樹也是帶權的,生成樹T各邊的權值總和稱爲該樹的權。算法

   最小生成樹(MST):權值最小的生成樹。數組

   生成樹和最小生成樹的應用:要連通n個城市須要n-1條邊線路。能夠把邊上的權值解釋爲線路的造價。則最小生成樹表示使其造價最小的生成樹。數據結構

   構造網的最小生成樹必須解決下面兩個問題:測試

    一、儘量選取權值小的邊,但不能構成迴路;ui

    二、選取n-1條恰當的邊以連通n個頂點;spa

    MST性質:假設G=(V,E)是一個連通網,U是頂點V的一個非空子集。若(u,v)是一條具備最小權值的邊,其中u∈U,v∈V-U,則必存在一棵包含邊(u,v)的最小生成樹。 .net

 

1.prim算法設計

  基本思想:假設G=(V,E)是連通的,TE是G上最小生成樹中邊的集合。算法從U={u0}(u0∈V)、TE={}開始。重複執行下列操做:unix

   在全部u∈U,v∈V-U的邊(u,v)∈E中找一條權值最小的邊(u0,v0)併入集合TE中,同時v0併入U,直到V=U爲止。

   此時,TE中必有n-1條邊,T=(V,TE)爲G的最小生成樹。

   Prim算法的核心:始終保持TE中的邊集構成一棵生成樹。

注意:prim算法適合稠密圖,其時間複雜度爲O(n^2),其時間複雜度與邊得數目無關,而kruskal算法的時間複雜度爲O(eloge)跟邊的數目有關,適合稀疏圖。

看了上面一大段文字是否是感受有點暈啊,爲了更好理解我在這裏舉一個例子,示例以下:

 

 

 

 

(1)圖中有6個頂點v1-v6,每條邊的邊權值都在圖上;在進行prim算法時,我先隨意選擇一個頂點做爲起始點,固然咱們通常選擇v1做爲起始點,好,如今咱們設U集合爲當前所找到最小生成樹裏面的頂點,TE集合爲所找到的邊,如今狀態以下:

U={v1}; TE={};

(2)如今查找一個頂點在U集合中,另外一個頂點在V-U集合中的最小權值,以下圖,在紅線相交的線上找最小值。

 

 

 

 

 

經過圖中咱們能夠看到邊v1-v3的權值最小爲1,那麼將v3加入到U集合,(v1,v3)加入到TE,狀態以下:

U={v1,v3}; TE={(v1,v3)};

(3)繼續尋找,如今狀態爲U={v1,v3}; TE={(v1,v3)};在與紅線相交的邊上查找最小值。

咱們能夠找到最小的權值爲(v3,v6)=4,那麼咱們將v6加入到U集合,並將最小邊加入到TE集合,那麼加入後狀態以下:

U={v1,v3,v6}; TE={(v1,v3),(v3,v6)}; 如此循環一下直到找到全部頂點爲止。

(4)下圖像咱們展現了所有的查找過程:

2.prim算法程序設計

 

(1)因爲最小生成樹包含每一個頂點,那麼頂點的選中與否就能夠直接用一個數組來標記used[max_vertexes];(咱們這裏直接使用程序代碼中的變量定義,這樣也易於理解);當選中一個數組的時候那麼就標記,如今就有一個問題,怎麼來選擇最小權值邊,注意這裏最小權值邊是有限制的,邊的一個頂點必定在已選頂點中,另外一個頂點固然就是在未選頂點集合中了。我最初的一個想法就是窮搜了,就是在一個集合中選擇一個頂點,來查找到另外一個集合中的最小值,這樣雖然很易於理解,可是很明顯效率不是很高,在嚴蔚敏的《數據結構》上提供了一種比較好的方法來解決:設置兩個輔助數組lowcost[max_vertexes]和closeset[max_vertexes],lowcost[max_vertexes]數組記錄從U到V-U具備最小代價的邊。對於每一個頂點v∈V-U,closedge[v], closeset[max_vertexes]記錄了該邊依附的在U中的頂點。

注意:咱們在考慮兩個頂點無關聯的時候設爲一個infinity 1000000最大值。

說了這麼多,感受有點羅嗦,仍是發揚原來的風格舉一個例子來講明,示例以下:

過程以下表:頂點標號都比圖中的小1,好比v10v21這裏首先選擇v1

 

Lowcost[0]

Lowcost[1]

Lowcost[2]

Lowcost[3]

Lowcost[4]

Lowcost[5]

U

V-U

closeset

v1,infinity 

v1,6

v1,1

v1,5

v1,infinity 

v1,infinity 

v1

v1,v2,v3,v4,v5,v6

從這個表格能夠看到依附到v1頂點的v3Lowcost最小爲1,那麼選擇v3,選擇了以後咱們必需要更新Lowcost數組的值,由於記錄從U到V-U具備最小代價的邊,加入以後就會改變。這裏更新Lowcost和更新closeset數組可能有點難理解,

 for (k=1;k<vcount;k++)
            if (!used[k]&&(G[j][k]<lowcost[k]))
                { lowcost[k]=G[j][k];
                closeset[k]=j; }
        }
j爲咱們已經選出來的頂點,若是G[j][k]<lowcost[k],則意味着最小權值邊發生變化,更新該頂點的最小lowcost權值,依附的頂點確定就是剛剛選出的頂點j,closeset[k]=j。

 

Lowcost[0]

Lowcost[1]

Lowcost[2]

Lowcost[3]

Lowcost[4]

Lowcost[5]

U

V-U

closeset

v1,infinity 

v1,6

v1,1

v1,5

v3,6

v3,4

v1v3

v1,v2,v4,v5,v6

 

這樣一直選擇下去直到選出全部的頂點。

(2)上面把查找最小權值的邊結束了,可是這裏有一個問題,就是咱們沒有存儲找到的邊,若是要求你輸出找到的邊那麼這個程序就須要改進了,咱們剛開始的時候選取的是v1做爲第一個選擇的頂點,那咱們設置一個father[]數組來記錄每一個節點的父節點,固然v1的父節點確定沒有,那麼咱們設置一個結束標誌爲-1,每次找到一個新的節點就將它的父節點設置爲他依附的節點,這樣就能夠準確的記錄邊得存儲了。

 

語法:prim(Graph G,int vcount,int father[]);

參數:

G:

圖,用鄰接矩陣表示

vcount:

表示圖的頂點個數

father[]:

用來記錄每一個節點的父節點

返回值:

null

注意:

 

 

常數max_vertexes爲圖最大節點數

 

常數infinity爲無窮大

 

數組存儲從0開始

 

若是下面的源程序有錯請參照測試程序。

源程序:

 

 

#define infinity 1000000
#define max_vertexes 5 

typedef int Graph[max_vertexes][max_vertexes];

void prim(Graph G,int vcount,int father[])
{
    int i,j,k;
    int lowcost[max_vertexes];

int closeset[max_vertexes],used[max_vertexes];

int min;
    for (i=0;i<vcount;i++)
        {

/* 最短距離初始化爲其餘節點到1號節點的距離 */
        lowcost[i]=G[0][i];

    /* 標記全部節點的依附點皆爲默認的1號節點 */


        closeset[i]=0; 
        used[i]=0;
        father[i]=-1; 
        }
    used[0]=1;  /*第一個節點是在U集合裏的*/

/* vcount個節點至少須要vcount-1條邊構成最小生成樹 */
    for (i=1;i<=vcount-1;i++)
        {
        j=0;

    min = infinity;

       /* 找知足條件的最小權值邊的節點k */
        for (k=1;k<vcount;k++)

         /* 邊權值較小且不在生成樹中 */
            if ((!used[k])&&(lowcost[k]<min)) 

          {

              min =  lowcost[k];

              j=k;

           }
        father[j]=closeset[j]; 
        used[j]=1;;//把第j個頂點併入了U中
        for (k=1;k<vcount;k++)

         /* 發現更小的權值 */
            if (!used[k]&&(G[j][k]<lowcost[k]))
                { 

                  lowcost[k]=G[j][k];/*更新最小權值*/
                  closeset[k]=j;;/*記錄新的依附點*/

                 }
        }
}

 

測試程序:

 

測試用例:

1 2 6

1 3 1

1 4 5

2 3 5

2 5 3

3 4 5

3 5 6

3 6 4

5 6 6

4 6 2

 

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

 

#define infinity 1000000

#define max_vertexes 6 

typedef int Graph[max_vertexes][max_vertexes];

void prim(Graph G,int vcount,int father[])

{    

int i,j,k; 

    int lowcost[max_vertexes];

int closeset[max_vertexes],used[max_vertexes];

int min;  

for (i=0;i<vcount;i++)     

  {

/* 最短距離初始化爲其餘節點到1號節點的距離 */   

    lowcost[i]=G[0][i];

    /* 標記全部節點的依附點皆爲默認的1號節點 */

     closeset[i]=0;      

  used[i]=0;    

    father[i]=-1;      

}    

used[0]=1; /*第一個節點是在s集合裏的*/

/* vcount個節點至少須要vcount-1條邊構成最小生成樹 */  

  for (i=1;i<=vcount-1;i++)      

   {       

 j=0;

     min = infinity;

       /* 找知足條件的最小權值邊的節點k */      

     for (k=1;k<vcount;k++)

         /* 邊權值較小且不在生成樹中 */     

 if ((!used[k])&&(lowcost[k]<min)) 

    {

              min =  lowcost[k];

              j=k;

            }       

    father[j]=closeset[j];   

printf("%d %d\n",j+1,closeset[j]+1);//打印邊   

used[j]=1;;//把第j個頂點併入了U中     

for (k=1;k<vcount;k++)

         /* 發現更小的權值 */       

   if (!used[k]&&(G[j][k]<lowcost[k]))       

                  lowcost[k]=G[j][k];/*更新最小權值*/       

      closeset[k]=j;;/*記錄新的依附點*/

    }      

   }

}

                 

int main()

{

FILE *fr;

int i,j,weight;

Graph G;

int fatheer[max_vertexes];

for(i=0; i<max_vertexes; i++)

for(j=0; j<max_vertexes; j++)

G[i][j] = infinity;

fr = fopen("prim.txt","r");

if(!fr)

{

printf("fopen failed\n");

exit(1); 

}

while(fscanf(fr,"%d%d%d", &i, &j, &weight) != EOF)

{

G[i-1][j-1] = weight;

G[j-1][i-1] = weight;

}

 

prim(G,max_vertexes,fatheer);

return 0;

 

}

 

程序結果:

3 1

6 3

4 6

2 3

5 2

請按任意鍵繼續. . .

相關文章
相關標籤/搜索