一個連通圖的生成樹是一個極小的連通子圖,它包含圖中全部的頂點(n個頂點),但只有n-1條邊。
最小生成樹:構造連通網的最小代價(最小權值)生成樹。
prim算法在嚴蔚敏樹上有解釋,但是都是數學語言,很深奧。
最小生成樹MST性質:假設N=(V,{E})是一個連通網,U是頂點集V的一個非空子集。若(u,v)是一條具有最小權值(代價)的邊,
其中u∈U,v∈V-U,則必存在一顆包含邊(u,v)的最小生成樹。
prim算法過程為:
假設N=(V,{E})是連通圖,TE是N上最小生成樹中邊的集合。算法從U={u0}(u0∈V),TE={}開始,
重復執行下述操作:
在所有u∈U,v∈V-U的邊(u,v)∈E中找一條代價最小的邊(u0,v0)並入集合TE,同時v0 並入U,直至U=V為止。
此時TE中必有n-1條邊,則T=(V,{TE})為N的最小生成樹。
我以圖為例,看看算法過程。
上面基本就把prim算法思想給表達出來。
代碼部分:
這里我使用的是鄰接矩陣來表示圖,其中邊的值就是權值。
#include<stdio.h>
#include<stdlib.h> typedef int bool; typedef char VertexType; typedef int EdgeType; #define false 0 #define true 1 #define MAXVEX 100 #define IFY 65535 VertexType g_init_vexs[MAXVEX] = {'A','B','C','D','E','F','G','H','I'}; EdgeType g_init_edges[MAXVEX][MAXVEX] = { {0,11,IFY,IFY,IFY,9,IFY,IFY,6}, //'A' {11,0,10,IFY,IFY,IFY,IFY,IFY,8}, //'B' {IFY,10,0,17,IFY,IFY,IFY,IFY,IFY},//'C' {IFY,IFY,17,0,9,IFY,IFY,IFY,IFY},//'D' {IFY,IFY,IFY,9,0,7,5,8,IFY}, //'E' {9,IFY,IFY,IFY,7,0,3,IFY,IFY}, //'F' {IFY,IFY,IFY,IFY,5,3,0,IFY,IFY}, //'G' {IFY,IFY,IFY,IFY,8,IFY,IFY,0,IFY}, //'H' {6,8,IFY,IFY,IFY,IFY,IFY,IFY,0}, //'I' }; typedef struct { VertexType vexs[MAXVEX]; EdgeType Mat[MAXVEX][MAXVEX]; int numVexs,numEdges; }MGraph; //打印矩陣,使用二維指針 void prt_maxtix(EdgeType (*p)[MAXVEX],int vexs) { int i,j; for(i=0;i<vexs;i++) { printf("\t"); for(j=0;j<vexs;j++) { if( *(*p + j) == IFY) { printf(" $ "); } else { printf(" %2d ", *(*p + j)); } } p++; printf("\n"); } } //check the number of vextex int getVexNum(VertexType *vexs) { VertexType *pos = vexs; int cnt=0; while(*pos <= 'Z' && *pos >= 'A') { cnt++; pos++; } return cnt; } //檢查矩陣是否對稱 bool checkMat(EdgeType *p,VertexType numvex) { int i,j; for (i=0;i<numvex;i++) { for(j=i+1;j<numvex;j++) { //printf("[%d][%d] = %d\t",i,j,*(p + MAXVEX*i + j)); //printf("[%d][%d] = %d\n",j,i,*(p + MAXVEX*j + i)); if (*(p + MAXVEX*i + j) != *(p + MAXVEX*j +i) ) { printf("ERROR:Mat[%d][%d] or Mat[%d][%d] not equal!\n",i,j,j,i); return false; } } } return true; } //用已知的一維數組和二維數組分別初始化頂點和邊 void init_Grp(MGraph *g,VertexType *v,EdgeType *p) { int i,j; // init vex num (*g).numVexs = getVexNum(v); //init vexter for (i=0;i<(*g).numVexs;i++) { (*g).vexs[i]=*v; v++; } //init Mat for (i=0;i<(*g).numVexs;i++) { for (j=0;j<(*g).numVexs;j++) { (*g).Mat[i][j] = *(p + MAXVEX*i + j); } } if(checkMat(&((*g).Mat[0][0]),(*g).numVexs) == false) { printf("init error!\n"); exit(0); } } /*void prim(MGraph G,int num) { int sum=0; int min,i,j,k; int adjvex[MAXVEX]; int lowcost[MAXVEX]; lowcost[num] = 0; adjvex[num] = 0; for (i = 0; i < G.numVexs;i++ ) { if (num == i) { continue; } lowcost[i]=G.Mat[num][i]; //存放起始頂點到各個頂點的權值。 adjvex[i] = num; } for (i=0;i<G.numVexs;i++) { //1.找權最短路徑 //2.把權最短路徑的頂點納入已找到的頂點集合中,重新查看新集合中最短路徑 if(num == i) { continue; } min = IFY; j=0;k=0; while (j<G.numVexs) { if (lowcost[j] != 0 && lowcost[j] < min) { min = lowcost[j]; k = j; } j++; } printf(" (%d,%d) --> ",adjvex[k],k); sum += G.Mat[adjvex[k]][k]; lowcost[k]=0; for (j=0;j<G.numVexs;j++) { if (lowcost[j] != 0 && G.Mat[k][j] < lowcost[j]) { lowcost[j] = G.Mat[k][j]; adjvex[j]=k; } } } printf("total:sum=%d\n",sum); }*/ void Prim(MGraph G,int num) { int sum,i,j,min,k; int adjvex[MAXVEX]; int lowcost[MAXVEX]; sum=0; adjvex[num]=0; lowcost[num]=0; for(i=0;i<G.numVexs;i++) { if(i==num) continue; adjvex[i]=num; lowcost[i]=G.Mat[num][i]; } for(i=0;i<G.numVexs;i++) { if(i==num) continue; min=IFY; k=0; //求出代價最小的點 for(j=0;j<G.numVexs;j++) { if(lowcost[j]!=0&&lowcost[j]<min) { min=lowcost[j]; k=j; } } printf("(%d,%d)-->",adjvex[k],k); sum+=G.Mat[adjvex[k]][k]; lowcost[k]=0; for(j=0;j<G.numVexs;j++) { if(lowcost[j]!=0&&G.Mat[k][j]<lowcost[j]) { lowcost[j]=G.Mat[k][j]; adjvex[j]=k; } } } printf("total:sum=%d\n",sum); } int main(int argc, char* argv[]) { MGraph grp; //init init_Grp(&grp,g_init_vexs,&g_init_edges[0][0]); //print Matix prt_maxtix(grp.Mat,grp.numVexs); //prim(grp,4); int i; for (i=0;i<grp.numVexs;i++) { Prim(grp,i); } //prim(grp,3); getchar(); return 0; }
運行結果如下:
Jungle Roads

The Head Elder of the tropical island of Lagrishan has a problem. A burst of foreign aid money was spent on extra roads between villages some years ago. But the jungle overtakes roads relentlessly, so the large road network is too expensive to maintain. The Council of Elders must choose to stop maintaining some roads. The map above on the left shows all the roads in use now and the cost in aacms per month to maintain them. Of course there needs to be some way to get between all the villages on maintained roads, even if the route is not as short as before. The Chief Elder would like to tell the Council of Elders what would be the smallest amount they could spend in aacms per month to maintain roads that would connect all the villages. The villages are labeled A through I in the maps above. The map on the right shows the roads that could be maintained most cheaply, for 216 aacms per month. Your task is to write a program that will solve such problems.
The input consists of one to 100 data sets, followed by a final line containing only 0. Each data set starts with a line containing only a number n, which is the number of villages, 1 < n < 27, and the villages are labeled with the first n letters of the alphabet, capitalized. Each data set is completed with n-1 lines that start with village labels in alphabetical order. There is no line for the last village. Each line for a village starts with the village label followed by a number, k, of roads from this village to villages with labels later in the alphabet. If k is greater than 0, the line continues with data for each of the k roads. The data for each road is the village label for the other end of the road followed by the monthly maintenance cost in aacms for the road. Maintenance costs will be positive integers less than 100. All data fields in the row are separated by single blanks. The road network will always allow travel between all the villages. The network will never have more than 75 roads. No village will have more than 15 roads going to other villages (before or after in the alphabet). In the sample input below, the first data set goes with the map above.
The output is one integer per line for each data set: the minimum cost in aacms per month to maintain a road system that connect all the villages. Caution: A brute force solution that examines every possible set of roads will not finish within the one minute time limit.
//HDOJ1301 #include<iostream>
#include<cstring> using namespace std; #define MAX 99999 #define LEN 30 int dist[LEN];//某點的權值 起始點到目標點的權值 int map[LEN][LEN];//某點到某點兩點之間的權值 bool isvisitd[LEN];//表示某點是否訪問過 //初始化map數組 設置為無窮大 void init(int n){ for(int i = 0;i<=n;i++) { for(int j = 0;j<=n;j++) map[i][j] = MAX; } } //prim最小生成樹的算法 int prim(int n){ int i,j,min,pos,sum; sum = 0; //最小生成樹的權值 //初始化,表示沒有一點走過 memset(isvisitd,false,sizeof(isvisitd)); //初始化給dist數組賦值 for(i = 1;i<=n;i++){ dist[i] = map[1][i]; } isvisitd[1] = true;//標記1已被訪問 從1開始 //找到權值最小點並記下位置 for(i = 1;i<n;i++){ min = MAX; for(j = 1;j<=n;j++){ if(!isvisitd[j]&&dist[j]<min){ min = dist[j]; pos = j;//記錄下該位置 } } sum+=min; isvisitd[pos] = true; //更新權值 for(j = 1;j<=n;j++) { if(!isvisitd[j]&&dist[j]>map[pos][j]){ dist[j] = map[pos][j]; } } } return sum; } int main(){ int i,j,n,m,len; char start,end; while(scanf("%d",&n)!=EOF){ if(n==0){ break; } init(n);//初始化 for(i=0;i<n-1;i++){ cin>>start>>m; for( j = 0;j<m;j++){ cin>>end>>len; map[i+1][end-'A'+1] = len; map[end-'A'+1][i+1] = len; } } cout<<prim(n)<<endl; } return 0; }
運行結果如下:
最小生成樹 Prim算法的實現及應用
關於prim算法
先把有的點放於一個集合(或者數組)里,這個集合里存放的是所有走過的點。初始值為0或者false表示還沒有點
聲明一個一維數組用於記錄各點的權值[可理解為起始點到目標點的距離],
聲明一個二維數組用於記錄某點到某一點的權值,如果這兩點不可達到,則設置為無窮大
具體執行過程:
先從某一點開始,把這一個開始的點放於聲明的一個數組或者集合里,表明這一點已經被訪問過。然后再從余下的n-1個點里去找那個權值最小的點並記錄該點的位置然后再加上這一點的權值,同時將該點放於集合里表明該點已初訪問。再更新權值
再看下圖,從下圖分析:
1、
先選取一個點作起始點,然后選擇它鄰近的權值最小的點(如果有多個與其相連的相同最小權值的點,隨便選取一個)。如1作為起點。
isvisited[1]=1; //表明把1加進來說明是已經訪問過
pos=1; //記錄該位置
//用dist[]數組不斷刷新最小權值,dist[i](0<i<=點數)的值為:i點到鄰近點(未被標記)的最小距離。
dist[1]=0; //起始點i到鄰近點的最小距離為0
dist[2]=map[pos][2]=4;
dist[3]=map[pos][3]=2;
dist[4]==map[pos][4]=3;
dist[5]=map[pos][5]=MaxInt; //無法直達
dist[6]=map[pos][6]=MaxInt;
2、
再在伸延的點找與它鄰近的兩者權值最小的點。
//dist[]以3作當前位置進行更新
isvisited[3]=1;
pos=3;
dist[1]=0; //已標記,不更新
dist[2]=map[pos][2]=4; //比5小,不更新
dist[3]=2; //已標記,不更新
dist[4]=map[pos][4]=3; //比1大,更新
dist[5]=map[pos][5]=MaxInt;
dist[6]=map[pos][6]=MaxInt;
3、最后的結果:
當所有點都連同后,結果最生成樹如上圖所示。
所有權值相加就是最小生成樹,其值為2+1+2+4+3=12。
prim算法的實現:
- //prim算法
- int prim(int n){
- int i,j,min,pos;
- int sum=0;
- memset(isvisited,false,sizeof(isvisited));
- //初始化
- for(i=1;i<=n;i++){
- dist[i]=map[1][i];
- }
- //從1開始
- isvisited[1]=true;
- dist[1]=MAX;
- //找到權值最小點並記錄下位置
- for(i=1;i<n;i++){
- min=MAX;
- //pos=-1;
- for(j=1;j<=n;j++){
- if(!isvisited[j] && dist[j]<min){
- min=dist[j];
- pos=j;
- }
- }
- sum+=dist[pos];//加上權值
- isvisited[pos]=true;
- //更新權值
- for(j=1;j<=n;j++){
- if(!isvisited[j] && dist[j]>map[pos][j]){
- dist[j]=map[pos][j];
- }
- }
- }
- return sum;
- }
算法的應用:
應用上面的這個模板基本上能解決一些常見的最小生成樹的算法,例如像杭電上的ACM題目:
題目鏈接地址:
HDOJ1863:http://acm.hdu.edu.cn/showproblem.php?pid=1863
HDOJ1875:http://acm.hdu.edu.cn/showproblem.php?pid=1875
HDOJ1879:http://acm.hdu.edu.cn/showproblem.php?pid=1879
HDOJ1233:http://acm.hdu.edu.cn/showproblem.php?pid=1233
HDOJ1162:http://acm.hdu.edu.cn/showproblem.php?pid=1162
HDOJ1301:http://acm.hdu.edu.cn/showproblem.php?pid=1301