最小生成樹的兩種算法


最小生成樹prim算法實現

所謂生成樹,就是n個點之間連成n-1條邊的圖形。而最小生成樹,就是權值(兩點間直線的值)之和的最小值。

 

   

  

  首先,要用二維數組記錄點和權值。如上圖所示無向圖:

int map[7][7];
       map[1][2]=map[2][1]=4;
       map[1][3]=map[3][1]=2;
       ......

      然后再求最小生成樹。具體方法是:

1.先選取一個點作起始點,然后選擇它鄰近的權值最小的點(如果有多個與其相連的相同最小權值的點,隨便選取一個)。如1作為起點。

visited[1]=1;

pos=1;

//用low[]數組不斷刷新最小權值,low[i](0<i<=點數)的值為:i點到鄰近點(未被標記)的最小距離。

low[1]=0;  //起始點i到鄰近點的最小距離為0

low[2]=map[pos][2]=4;

low[3]=map[pos][3]=2;

low[4]==map[pos][4]=3;

low[5]=map[pos][5]=MaxInt;  //無法直達

low[6]=map[pos][6]=MaxInt;

 

  2.再在伸延的點找與它鄰近的兩者權值最小的點。

//low[]以3作當前位置進行更新

visited[3]=1;

pos=3;

low[1]=0;   //已標記,不更新

low[2]=map[1][2]=4;  //比5小,不更新

low[3]=2;  //已標記,不更新

low[4]=map[1][4]=3;   //比1大,更新后為:low[4]=map[3][4]=1;

low[5]=map[1][5]=MaxInt;//無法直達,不更新

low[6]=map[1][6]=MaxInt;//比2大,更新后為:low[6]=map[3][6]=2;

 

    3.如此類推...

 
 
     當所有點都連同后,結果最生成樹如上圖所示。

     所有權值相加就是最小生成樹,其值為2+1+2+4+3=12。

     至於具體代碼如何實現,現在結合POJ1258例題解釋。代碼如下:

 1 //poj-1258 
 2 #include <stdio.h> 
 3 #include <string.h> 
 4 #define MaxInt 0x3f3f3f3f 
 5 #define N 110 
 6 //創建map二維數組儲存圖表,low數組記錄每2個點間最小權值,visited數組標記某點是否已訪問 
 7 int map[N][N],low[N],visited[N]; 
 8 int n;   
 9 int prim() 
10 { 
11     int i,j,pos,min,result=0; 
12     memset(visited,0,sizeof(visited)); 
13     visited[1]=1;pos=1; //從某點開始,分別標記和記錄該點 
14     for(i=1;i<=n;i++)   //第一次給low數組賦值 
15         if(i!=pos) low[i]=map[pos][i]; 
16     for(i=1;i<n;i++)    //再運行n-1次 
17     {         
18         min=MaxInt; //找出最小權值並記錄位置 
19         for(j=1;j<=n;j++) 
20          if(visited[j]==0&&min>low[j]) 
21          { 
22              min=low[j];pos=j; 
23          } 
24         result+=min;    //最小權值累加 
25         visited[pos]=1;     //標記該點 
26         for(j=1;j<=n;j++)   //更新權值 
27         if(visited[j]==0&&low[j]>map[pos][j]) 
28             low[j]=map[pos][j]; 
29     } 
30     return result; 
31 } 
32   
33 int main() 
34 { 
35     int i,v,j,ans; 
36     while(scanf("%d",&n)!=EOF) 
37     { 
38         memset(map,MaxInt,sizeof(map)); //所有權值初始化為最大 
39         for(i=1;i<=n;i++) 
40             for(j=1;j<=n;j++) 
41             { 
42                 scanf("%d",&v); 
43                 map[i][j]=map[i][j]=v; 
44             } 
45             ans=prim(); 
46             printf("%d\n",ans); 
47     } 
48     return 0; 
49 }

 

http://acm.nyist.net/JudgeOnline/problem.php?pid=38

nyoj—38代碼:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 int map[550][550],vis[550],low[550];
 7 int n;
 8 int prim()
 9 {
10     int i,j,pos,min,sum=0;
11     memset(low,0,sizeof(low));
12     memset(vis,0,sizeof(vis));
13     vis[1]=1;pos=1;
14     for(i=1;i<=n;i++)
15     if(i!=pos)
16     low[i]=map[pos][i];
17     for(i=1;i<n;i++)
18     {
19         min=200;
20         for(j=1;j<=n;j++)
21         {
22             if(!vis[j]&&low[j]<min)
23             {
24                 min=low[j];
25                 pos=j;
26             }
27         }
28         sum+=min;
29         vis[pos]=1;
30         for(j=1;j<=n;j++)
31         {
32             if(vis[j]==0&&low[j]>map[pos][j])
33             low[j]=map[pos][j];
34         }
35     }
36     return sum;
37 }
38 int main()
39 {
40     int T;
41     scanf("%d",&T);
42     while(T--)
43     {
44         int i,j,m,v,e,c;
45         int s[550];
46         memset(map,0,sizeof(map));
47         memset(s,0,sizeof(s));
48         scanf("%d %d",&n,&m);
49         //if(n==0&&m==0)
50         //{
51         //    printf("0\n");
52         //    continue;
53         //}
54         for(i=0;i<=n;i++)
55         for(j=0;j<=n;j++)
56         map[i][j]=map[j][i]=200;
57         for(i=0;i<m;i++)
58         {
59             scanf("%d %d %d",&v,&e,&c);
60             map[v][e]=map[e][v]=c;
61         }
62         for(i=0;i<n;i++)
63         scanf("%d",&s[i]);
64         sort(s,s+n); 
65         printf("%d\n",prim()+s[0]);
66     }
67     return 0;
68 }
69 //prim算法 
70 //初始化時沒有把 vis數組清零導致 wa 
View Code

 

最小生成樹Kruskal算法+並查集實現

 

今天剛掌握Kruskal算法,寫下隨筆。

對於稀疏圖來說,用Kruskal寫最小生成樹效率更好,加上並查集,可對其進行優化。

Kruskal算法的步驟:

1.對所有邊進行從小到大的排序。

2.每次選一條邊(最小的邊),如果如果形成環,就不加入(u,v)中,否則加入。那么加入的(u,v)一定是最佳的。

並查集:
我們可以把每個連通分量看成一個集合,該集合包含了連通分量的所有點。而具體的連通方式無關緊要,好比集合中的元素沒有先后順序之分,只有“屬於”與“不屬於”的區別。圖的所有連通分量可以用若干個不相交集合來表示。

而並查集的精妙之處在於用數來表示集合。如果把x的父結點保存在p[x]中(如果沒有父親,p[x]=x),則不難寫出結點x所在樹的遞歸程序:

find(int x) {return p[x]==x?x:p[x]=find(p[x]);}

意思是,如果p[x]=x,說明x本身就是樹根,因此返回x;否則返回x的父親p[x]所在樹的根結點。

既然每棵樹表示的只是一個集合,因此樹的形態是無關緊要的,並不需要在“查找”操作之后保持樹的形態不變,只要順便把遍歷過的結點都改成樹根的兒子,下次查找就會快很多了。如下圖所示:

 
設第i條邊的端點序號和權值分別保存在u[i],v[i],w[i]中,而排序后第i小的邊保存在r[i]中。(間接排序是指排序的關鍵字是對象的代號,而不是對象本身。)
結合hdoj1863代碼如下:
 1 //hdu_1863
 2 #include <stdio.h> 
 3 #include <stdlib.h> 
 4 #include <algorithm> 
 5 #define N 150 
 6 using namespace std; 
 7 int m,n,u[N],v[N],w[N],p[N],r[N]; 
 8 int cmp(const int i,const int j) {return w[i]<w[j];} 
 9 int find(int x) {return p[x]==x?x:p[x]=find(p[x]);} 
10 int kruskal() 
11 { 
12     int cou=0,x,y,i,ans=0; 
13     for(i=0;i<n;i++) p[i]=i; 
14     for(i=0;i<m;i++) r[i]=i; 
15     sort(r,r+m,cmp); 
16     for(i=0;i<m;i++) 
17     { 
18         int e=r[i];x=find(u[e]);y=find(v[e]); 
19         if(x!=y) {ans += w[e];p[x]=y;cou++;} 
20     } 
21     if(cou<n-1) ans=0; 
22     return ans; 
23 } 
24   
25 int main() 
26 { 
27     int i,ans; 
28     while(scanf("%d%d",&m,&n)!=EOF&&m) 
29     { 
30         for(i=0;i<m;i++) 
31         { 
32             scanf("%d%d%d",&u[i],&v[i],&w[i]); 
33         } 
34         ans=kruskal(); 
35         if(ans) printf("%d\n",ans); 
36         else printf("?\n",ans); 
37     } 
38     return 0; 
39 }
View Code

我的hdu1863代碼:

題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=1863

 1 #include <cstdio>
 2 #include <iostream>
 3 #include <cstring>
 4 //#include <algotithm>
 5 #include <stdlib.h>
 6 using namespace std;
 7 typedef struct IN
 8 {
 9     int a;
10     int b;
11     int c;
12 }IN;
13 IN s[5000];
14 int N,M;
15 int pre[110];
16 int cmp(const void *a,const void *b)
17 {
18     return (*(IN *)a).c - (*(IN *)b).c;
19 }
20 int find(int x)
21 {
22     int i,r,t;
23     r=x;
24     while(r!=pre[r])
25     r=pre[r];
26     while(x!=r)
27     {
28         i=pre[x];
29         pre[x]=r;
30         x=i;
31     }
32     return r;
33 }
34 int kruskal()
35 {
36     int i,j,pa,pb,num=0,sum=0;
37     for(i=0;i<=M;i++)
38     pre[i]=i;
39     for(i=0;i<N;i++)
40     {
41         pa=find(s[i].a);
42         pb=find(s[i].b);
43         if(pa!=pb)
44         {
45             pre[pa]=pb;
46             sum+=s[i].c;
47             num++;
48         }
49     }
50     if(num==M-1)
51     return sum;
52     else
53     return 0;
54 }
55 int main()
56 {
57     while(scanf("%d %d",&N,&M),N)
58     {
59         int i,j,t;
60         memset(s,0,sizeof(s));
61         for(i=0;i<N;i++)
62         scanf("%d %d %d",&s[i].a,&s[i].b,&s[i].c);
63         qsort(s,N,sizeof(s[0]),cmp);
64         //for(i=0;i<N;i++)
65         //printf("%d\n",s[i].c);
66         t=kruskal();
67         if(t)
68         printf("%d\n",t);
69         else
70         printf("?\n");
71     }
72     return 0;
73 }

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM