轉自:http://blog.sina.com.cn/s/blog_a00f56270101a7op.html
2.1,問題描述
設G=(V,E)是無向連通帶權圖,如果G的一個子圖G’是一棵包含G的所有頂點的樹,則稱G’爲G的生成樹。生成樹的各邊權的總和稱爲該生成樹的耗費,求在G的所有生成樹中耗費最小的最小生成樹。
2.2,算法思想
(1)將代價樹中權值非0的所有的邊進行小頂堆排序,依次存入到road[]數組中,然後將road[]進行倒置,注意在進行排序時,按照road[i]的權值進行排序,然後記錄這條邊的起始頂點也要相對應。
(2)從最小邊開始掃描各邊,並檢測當前所選邊的加入是否會構成迴路,如果不會構成迴路,則將該邊併入到最小生成樹中。
(3)不斷重複步驟2,直到所有的邊都檢測完爲止。
其中判斷當前檢測邊的加入是否會使原來生成樹構成迴路的算法思想是:
a.採用樹的雙親存儲結構,即數組v[i]的值表示編號爲i的頂點的父結點編號。並將數組v[]初始化爲v[i]=i;
b.若當前檢測的邊結點起點爲a,終點爲b,則判斷該邊是否能被加入的方法是:分別訪問a,b的根結點(a,b的父結點有可能還有父結點),若a,b的根結點相同,則不可以併入,否則可以將該邊併入。
c.若當前要併入的邊起點爲a,終點爲b,需要判斷起點a是否被修改過,即a!=v[a],若已被修改過,就要修改終點v[b]的值,使v[b]=a,即結點b的父結點爲a。
取得頂點a根結點的算法實現爲:
//頂點a所在的連通分支號
int GetRoot(int a){
while(a!=v[a]){
a=v[a];
}
return a;
}
2.3程序設計
(1)所用數據結構,圖的存儲結構模塊(nodetype.h)
#define maxSize 20
typedef struct{
int no;
}VertexType; //結點類型定義
typedef struct{
int n; //圖的頂點數
int e; //圖的所有邊數
VertexType vex[maxSize];//頂點信息
int edges[maxSize][maxSize];//各邊的權值
}MGraph; //圖的存儲結構
typedef struct{
int a;//邊的起點
int b;//邊的終點
int w;//邊的權值
}Road; //權值非0的邊的存儲結構
(2)主模塊(main_Kruskal.cpp)
#include
#include
#include"nodetype.h"
#include"initlize.h"
#include"roadinfor.h"
#include"heapSort.h"
#include"kruskal.h"
int main(){
int sum=0; //存儲最小生成樹的總代價
Initlize(g); //圖信息的初始化,錄入初始信息
Roadinfor(road,g.n); //錄入邊的信息
printf("最小生成樹邊的編號 起點 終點 權值\n");
Kruska(g,road,v,sum); //調用Kruskal算法,輸出最小生成樹
printf("\n最小代價生成樹爲: %d\n",sum);
return 0;
}
(3)讀取數據模塊,圖的初始化(initlize.h)
//初始化圖結構,錄入圖的信息
void Initlize(MGraph &g){
int i,j;
printf("請輸入圖的頂點數目:\n");
scanf("%d",&g.n); //讀取頂點數目
printf("請輸入各邊的權值:(不相臨接的兩頂點權值爲0)\n\n");
for(i=1;i<=g.n;i++){
g.vex[i].no=i;
for(j=1;j<=g.n;j++){
printf("邊[%d][%d]的權值爲:",i,j);
scanf("%d",&g.edges[i][j]);
printf("\n");
}
}
}
MGraph g; //圖
Road road[maxSize]; //邊的信息
int v[maxSize]; //頂點集
(4)讀取各條候選邊(待排序邊)信息模塊(roadinfor.h)
//讀取各邊的信息,並將之存入road[]中
void Roadinfor(Road road[],int e){
int i,j;
int k=1;
for(i=1;i<=e;i++){
for(j=i;j<=e;j++){
if(g.edges[i][j]!=0){
road[k].a=i;
road[k].b=j;
road[k].w=g.edges[i][j];
k++;
}
}
}
}
(5)將待檢測邊進行堆排序模塊(heapSort.h)
int Sizearray(Road road[]); //求邊的數目,即road[]數組元素的個數//R[low]到R[high]範圍內對位置low到high結點進行調整
void Sift(Road road[],int low,int high);
//將邊對象road2的信息賦值給邊對象road1
void Switch(Road &road1,Road road2);
void verse(Road road[]); //將road數組倒置
void HeapSort(Road road[],int e){ //對邊的權值進行堆排序,從小到大
int i;
Road temp;
for(i=(Sizearray(road))/2;i>=1;){
Sift(road,i,Sizearray(road));//建立初始小頂堆
i--;
}
for(i=(Sizearray(road));i>=2;i--){ //取得堆頂元素之後,不斷對堆進行調準
Switch(temp,road[1]);
Switch(road[1],road[i]);
Switch(road[i],temp);
//只需要從新的堆頂開始調準,只有堆頂可能不滿足小頂堆條件
Sift(road,1,i-1);
}
}
void Sift(Road road[],int low,int high){
int i,j;
i=low;
j=2*i;
Road temp;
Switch(temp,road[i]); //先將堆頂存入temp
while(j<=high){
if(jroad[j+1].w)//找到其最小的兒子
j++;
if(temp.w>road[j].w){ //若不滿足小頂堆條件,則需進行調準
Switch(road[i],road[j]);
i=j;
j=2*i;
}
else{
break;
}
}
Switch(road[i],temp); //最後確定road[i]的位置
}
int Sizearray(Road road[]){
int i,n=0;
for(i=1;road[i].w!=0;i++){
n++;
}
return n;
}
void Switch(Road &road1,Road road2){
road1.a=road2.a; //road1起點賦值
road1.b=road2.b; //road1終點賦值
road1.w=road2.w; //road1權值賦值
}
void verse(Road road[]){
int i,n;
Road temp;
n=Sizearray(road);
for(i=1;i<=n/2;){
Switch(temp,road[i]);
Switch(road[i],road[n-i+1]);
Switch(road[n-i+1],temp);
i++;
}
}
(6)運用貪心策略——Kruskal算法構造最小生成樹(kruskal.h)
int GetRoot(int a); //取得頂點的根節點,從而得到連通分支
//Kruskal算法,實現求最小生成樹
void Kruska(MGraph &g,Road road[],int v[],int &sum){
int i,x,y;
int a,b;
//頂點ver[i]初始時表示各在不同的連通分支v[i]中,父結點依次爲v[i]
for(i=1;i<=g.n;i++){
v[i]=i;
}
//堆排序,將邊按照從小到大的順序排序
HeapSort(road,g.e);
verse(road);
for(i=1;i<=Sizearray(road);i++){
//得到起點所在的連通分支號
x=GetRoot(road[i].a);
//得到終點所在的連通分支號
y=GetRoot(road[i].b);
//添加road[i]不會成環,將邊road[i]添加其中
if(x!=y){
printf(" %d %d %d %d\n",
i,road[i].a,road[i].b,road[i].w);
if(x!=road[i].a){
//road[i]的起點已經被修改過,這時需要修改邊road[i]的終點的連通分支號
v[road[i].b]=road[i].a;
sum+=road[i].w;
}else{
//road[i]的起點未被修改過,這時修改邊road[i]的起點的連通分支號
v[road[i].a]=road[i].b;
sum+=road[i].w;
}
}
}
}
//頂點a所在的連通分支號
int GetRoot(int a){
while(a!=v[a]){
a=v[a];
}
return a;
}
2.4算法分析
從Kruskal算法中可以看到,執行該算法時間主要花費在堆排序和單層循環上,而循環是線性級的,則可以認爲時間複雜性主要花費在堆排序上,由堆排序算法可知,Kruskal算法的時間複雜度爲O(eloge),其中e爲圖的邊數。
2.5 測試實例
最小生成樹——Kruskal算法
最小生成樹——Kruskal算法
2.6 實驗總結
通過實現求最小生成樹的兩種算法,Prim算法和Kruskal算法,一個從頂點集考慮,一個從邊集考慮,分別實現了構造最小生成樹,當然Prim從單個頂點出發,利用貪心策略,開始頂點不同,可能構造的最小生成樹可能不同,但最終的總耗費卻是唯一的。同時思考問題的角度不同,帶來執行算法效率的不同,Prim的時間複雜度爲O(n2)(n爲圖的頂點數),與圖的頂點數有關,邊數無關,故適合頂點數目稀少的圖,即密集圖,而Kruskal的時間複雜度爲O(eloge)(e爲圖中邊的數目),與圖的邊數有關,故適合邊數較少的稀疏圖。