51nod 1212 無向圖最小生成樹

N個點M條邊的無向連通圖,每條邊有一個權值,求該圖的最小生成樹。
Input
第1行:2個數N,M中間用空格分隔,N爲點的數量,M爲邊的數量。(2 <= N <= 1000, 1 <= M <= 50000)
第2 - M + 1行:每行3個數S E W,分別表示M條邊的2個頂點及權值。(1 <= S, E <= N,1 <= W <= 10000)
Output
輸出最小生成樹的所有邊的權值之和。
Input示例
9 14
1 2 4
2 3 8
3 4 7
4 5 9
5 6 10
6 7 2
7 8 1
8 9 7
2 8 11
3 9 2
7 9 6
3 6 4
4 6 14
1 8 8
Output示例
37
prim算法實現過程
#include<iostream>
#include<cstdio>
#include<map>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
#include<vector>
#include<stack>
#include<cstdlib>
#include<cctype>
#include<cstring>
#include<cmath>

using namespace std;
const int inf =0x3f3f3f3f;
int G[1001][1001];
int vis[1001],lowc[1001]; //lowc數組臨時儲存待選變量,vis數組用於給點做標記是否選中
int prim(int G[][1001],int n){
  int i,j,p,minc,res=0;
  memset(vis,0,sizeof(vis));
  vis[1]=1;             //從第一個頂點開始遍歷
  for(i = 2; i <= n; i++){
    lowc[i]=G[1][i];      //臨時儲存找最短邊的一些變量
  }
    for(i = 2; i <= n ;i++){
        minc=inf;  //用於標記最短點
        p=-1;      //用於標記點的座標
        for(j = 1; j <= n;j++){
            if(vis[j]==0&&lowc[j]<minc){
                minc=lowc[j];
                p=j;
            }
        }
        if(inf==minc)return -1;  //原圖不連通
        res+=minc;
        vis[p]=1;                //點選擇過了就標記爲1
        for(j = 1; j<=n ;j++){   //按照上面找到的最短邊進行接下來的尋找
            if(vis[j]==0&&lowc[j]>G[p][j]){  //如果點未被選擇過,且lowc裏面的比矩陣裏面的大
                lowc[j]=G[p][j];
            }
        }
    }
  return res;
}

int main()
{
  int n,m;
  while(cin>>n>>m)// n點的個數,m邊的個數
  {
      int x,y,w;
      memset(G,inf,sizeof(G));
      for(int i = 0;i < m;i++){
        cin>>x>>y>>w;
        G[x][y]=G[y][x]=w;   //無向圖權值對稱
      }
      cout<<prim(G,n)<<endl;
  }
}

下面給出了最小生成樹的兩種算法的原理:

給定一個帶權的無向連通圖,如何選取一棵生成樹,使樹上所有邊上權的總和爲最小,這叫最小生成樹.

求最小生成樹的算法
(1) 克魯斯卡爾算法
圖的存貯結構採用邊集數組,且權值相等的邊在數組中排列次序可以是任意的.該方法對於邊相對比較多的不是很實用,浪費時間.
(2) 普里姆算法
圖的存貯結構採用鄰接矩陣.此方法是按各個頂點連通的步驟進行,需要用一個頂點集合,開始爲空集,以後將以連通的頂點陸續加入到集合中,全部頂點加入集合後就得到所需的最小生成樹 .


下面來具體講下:
克魯斯卡爾算法
方法:將圖中邊按其權值由小到大的次序順序選取,若選邊後不形成迴路,則保留作爲一條邊,若形成迴路則除去.依次選夠(n-1)條邊,即得最小生成樹.(n爲頂點數)

第一步:由邊集數組選第一條邊

第二步:選第二條邊,即權值爲2的邊

第三步:選第三條邊,即權值爲3的邊

第四步:選第四條邊,即權值爲4的邊

第五步:選第五條邊

 


普里姆算法
方法:從指定頂點開始將它加入集合中,然後將集合內的頂點與集合外的頂點所構成的所有邊中選取權值最小的一條邊作爲生成樹的邊,並將集合外的那個頂點加入到集合中,表示該頂點已連通.再用集合內的頂點與集合外的頂點構成的邊中找最小的邊,並將相應的頂點加入集合中,如此下去直到全部頂點都加入到集合中,即得最小生成樹.
例在下圖中從1點出發求出此圖的最小生成樹,並按生成樹的邊的順序將頂點與權值填入表中.

———————>先寫出其鄰接矩陣

第一步:從①開始,①進集合,用與集合外所有頂點能構成的邊中找最小權值的一條邊
①——②權6
①——③權1 -> 取①——③邊
①——④權5

 

第二步:③進集合,①,③與②,④,⑤,⑥構成的最小邊爲
①——④權5
③——⑥權4 -> 取③——⑥邊

第三步:⑥進集合,①,③,⑥與②,④,⑤構成的各最小邊
①——②權6
③——②權5
⑥——④權2 -> 取⑥——④邊

第四步:④進集合,①,③,⑥,④與②,⑤構成的各最小邊
①——②權6
③——②權5 -> 取③——②邊
⑥——⑤權6

第四步:②進集合,①,③,⑥,②,④與⑤構成的各最小邊
②——⑤權3 -> 取②——⑤邊


這也是在網上找到的一個Kruskal和Prim構造過程圖,貼出來:

 

Kruskal算法
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++;  
    }  
}  


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章