一、圖的最小生成樹
由生成樹的定義可知,無向連通圖的最小生成樹不是唯一的,連通圖的一次遍歷所經過的邊的集合及圖中所有頂點的集合構成了該圖的一棵生成樹,對連通圖的不同遍歷,可能的到不同的生成樹。
如果無向連通圖是一個網,那麼,它所有生成樹中必有一棵邊的權值總和最小的生成樹,我們稱這棵生成樹爲最小生成樹,簡稱最小生成樹。
二、構造最小生成樹的Prim算法
假設G(V,E)爲一網圖,其中V爲所有頂點的集合,E爲網圖中所有邊的集合。設置兩個新的集合U和T,其中集合U用於存放G的最小生成樹中頂點,集合T存放G的最小生成樹的邊。Wuv爲頂點爲u與v的權值。
Prim算法可用下述過程描述:
1、U = {u1} , T = {} ;
2、while(U!=V)do
(u,v) = min{Wuv , u屬於U,v屬於(U-V)}
T = T + {(u , v)}
U = U+{v} ;
3、結束
爲實現Prim算法,需設置兩個輔助一維數組lowcost和closevertex,其中lowcost用來保存集合V-U中各項頂點與集合U中各項頂點構成的邊中具有最小權值的邊的權值;數組closevertex用來保存依附於該邊的在集合U中的頂點。
算法:
/*
*Prim算法求最小生成樹
*從序號爲0的頂點出發,建立的最小生成樹存於數組closevertex中
*/
void Prim(int gm[][MaxVertexNum] , int n , int closevertex[])
{
int lowcost[MaxVertexNum] , mincost ;
int i , j , k ;
for(i = 1 ; i < n ; i++)
{
lowcost[i] = gm[0][i] ;
closevertex[i] = 0 ;
}
lowcost[0] = 0 ;
closevertex[0] = 0 ;
//尋找當前最小權值的邊的頂點
for(i = 1 ; i < n ; i++)
{
mincost = MAX ;
j = 1 ;
k = 1 ;
while(j < n)
{
if(lowcost[j] < mincost && lowcost[j] != 0)
{
mincost = lowcost[j] ;
k = j ;
}
j++ ;
}
printf("頂點的序號 = %d 邊的權值 = %d \n" , k , mincost) ;
lowcost[k] = 0 ;
for(j = 1 ; j < n ; j++)
{
//當存在距離更小時,將lowcost改變,並改變closevertex的值
if(gm[k][j] < lowcost[j])
{
lowcost[j] = gm[k][j] ;
closevertex[j] = k ;
}
}
}
}
三、構造最小生成樹的Kruskal算法
Kruskal算法是一種按照網中邊的權值遞增的順序構造最小生成樹的方法。
基本思想:
設無向連通網G = (V , E),令G的最小生成樹爲T,其初態爲T = (V , {}),即開始時,最小生成樹T由圖G中的n個頂點構成,頂點之間設有一條邊,這樣T中各項頂點各自構成一個連通分量。然後按照邊的權值從小到大的順序,考察G的邊集E中的各條邊。若被考察的邊的兩個頂點屬於T的兩個不同的連通分量,則將此邊作爲最小生生成樹的邊加入到T中,同時把兩個連通分量連接爲一個連通分量;若被考察的兩個頂點屬於同一個連通分量,則捨去此邊,以免造成迴路,如此下去,當T中的連通分量個數爲1時,此時連通分量便爲G的一棵最小生成樹。
如下圖爲構造過程:
下面介紹算法的實現:
設置一個結構數組Edges存儲網中所有的邊,邊的結構類型包括構成頂點信息和邊的權值。
定義如下:
/*
*爲kruskal定義邊表
*/
typedef struct
{
int v1 , v2 ; //邊的兩個頂點
int cost ; //邊上的權值
} SEdgeType ;
SEdgeType S[MaxVertexNum] ;
爲了方便選取當前權值最小的邊,事先將數組S中的各元素按照其cost域值從大到小順序排列。
對於有n個頂點的網,設置一個數組father[n],其初值爲father[i]=-1{i = 0,1…,n-1},表示各個頂點在不同的連通分量上面,然後依次取出edges數組中的每條邊的兩個頂點,查找他們所屬的連通分量,假設v1與v2爲兩個頂點所在的樹的根節點在father數組中的序號,若v1不等於v2,表明這兩條邊的兩個頂點不屬於同一分量,則將這條邊作爲最小生成樹的邊輸出,併合並他們所屬的連通分量。
算法:
/*
*根據Kruskal算法求得最小生成樹
*/
int find(int father[] , int v)
{
int t ;
t = v ;
while(father[t] >= 0)
{
t = father[t] ;
}
return t ;
}
void Kruskal(SEdgeType edges[] , int n , int e)
{
int father[MaxVertexNum] ;
int i , j , v1 , v2 ;
for(i = 0 ; i < e ; i++)
{
father[i] = -1 ;
}
i = 0 ;
j = 0 ;
while((i < e) && (j < n - 1))
{
v1 = find(father , edges[i].v1) ;
v2 = find(father , edges[i].v2) ;
if(v1 != v2)
{
father[v2] = v1 ;
j++ ;
printf("%3d %3d \n" , edges[i].v1 , edges[i].v2) ;
}
i++ ;
}
}
四、測試代碼
/*
*構建最小生成樹Prim算法,通過鄰接矩陣表示圖
*/
#include<stdio.h>
#include<malloc.h>
#define MAX 9999
#define MaxVertexNum 100 //最大定點數設爲100
typedef char VertexType ; //頂點字符設爲字符型
typedef int EdgeType ; //邊上權值設爲整型
/*
*定義圖儲存的數據結構
*/
typedef struct
{
VertexType vexs[MaxVertexNum][3] ; //頂點表
EdgeType edges[MaxVertexNum][MaxVertexNum] ; //邊表
int n , e ; //頂點數與邊數
} MGraph ;
/*
*爲kruskal定義邊表
*/
typedef struct
{
int v1 , v2 ; //邊的兩個頂點
int cost ; //邊上的權值
} SEdgeType ;
/*
*建立一個無向圖的鄰接矩陣存儲
*/
void CreateMGraph(MGraph *G , SEdgeType S[])
{
int i , j , k , w ;
printf("請輸入定點數與邊數(輸入格式爲:頂點數,邊數):") ;
scanf("%d,%d" , &G->n , &G->e) ;
printf("請輸入頂點信息(輸入格式爲:頂點號<CR>):\n") ;
for(i = 0 ; i < G->n ; i++)
{
scanf("%s" , G->vexs[i]) ;
}
//初始化鄰接矩陣
for(i = 0 ; i < G->n ; i++)
{
for(j = 0 ; j < G->n ; j++)
{
G->edges[i][j] = MAX ;
}
}
printf("請輸入每條邊對應的兩個頂點序號以及權值(輸入格式爲:i,j,w):\n") ;
for(k = 0 ; k < G->e ; k++)
{
scanf("%d,%d,%d" , &i , &j , &w) ;
S[k].v1 = i ;
S[k].v2 = j ;
S[k].cost = w ;
G->edges[i][j] = w ;
G->edges[j][i] = w ;
}
}
/*
*Prim算法求最小生成樹
*從序號爲0的頂點出發,建立的最小生成樹存於數組closevertex中
*/
void Prim(int gm[][MaxVertexNum] , int n , int closevertex[])
{
int lowcost[MaxVertexNum] , mincost ;
int i , j , k ;
for(i = 1 ; i < n ; i++)
{
lowcost[i] = gm[0][i] ;
closevertex[i] = 0 ;
}
lowcost[0] = 0 ;
closevertex[0] = 0 ;
//尋找當前最小權值的邊的頂點
for(i = 1 ; i < n ; i++)
{
mincost = MAX ;
j = 1 ;
k = 1 ;
while(j < n)
{
if(lowcost[j] < mincost && lowcost[j] != 0)
{
mincost = lowcost[j] ;
k = j ;
}
j++ ;
}
printf("頂點的序號 = %d 邊的權值 = %d \n" , k , mincost) ;
lowcost[k] = 0 ;
for(j = 1 ; j < n ; j++)
{
//當存在距離更小時,將lowcost改變,並改變closevertex的值
if(gm[k][j] < lowcost[j])
{
lowcost[j] = gm[k][j] ;
closevertex[j] = k ;
}
}
}
printf("最小生成樹爲:\n") ;
for(i = 0 ; i < n ; i++)
{
printf("%d " , closevertex[i]) ;
}
printf("\n") ;
}
/*
*根據Kruskal算法求得最小生成樹
*/
int find(int father[] , int v)
{
int t ;
t = v ;
while(father[t] >= 0)
{
t = father[t] ;
}
return t ;
}
void Kruskal(SEdgeType edges[] , int n , int e)
{
int father[MaxVertexNum] ;
int i , j , v1 , v2 ;
for(i = 0 ; i < e ; i++)
{
father[i] = -1 ;
}
i = 0 ;
j = 0 ;
while((i < e) && (j < n - 1))
{
v1 = find(father , edges[i].v1) ;
v2 = find(father , edges[i].v2) ;
if(v1 != v2)
{
father[v2] = v1 ;
j++ ;
printf("%3d %3d \n" , edges[i].v1 , edges[i].v2) ;
}
i++ ;
}
}
void out(MGraph G)
{
int i , j ;
printf("圖G的頂點數與邊數爲 %d , %d \n" , G.n , G.e) ;
printf("圖G的頂點爲:");
for(i = 0 ; i < G.n ; i++)
{
printf("%s " , G.vexs[i]) ;
}
printf("\n") ;
printf("圖G的鄰接矩陣爲:\n") ;
for(i = 0 ; i < G.n ; i++)
{
for(j = 0 ; j < G.n ; j++)
{
printf("%04d " , G.edges[i][j]) ;
}
printf("\n") ;
}
}
void sort(SEdgeType edges[] , int e)
{
int i , j ;
SEdgeType temp ;
for(i = 0 ; i < e - 1 ; i++)
{
for(j = i + 1 ; j < e ; j++)
{
if(edges[i].cost > edges[j].cost)
{
temp = edges[j] ;
edges[j] = edges[i] ;
edges[i] = temp ;
}
}
}
}
/*
*進行測試
*/
void main()
{
MGraph *G ;
SEdgeType S[MaxVertexNum] ;
int closevertex[MaxVertexNum] ;
G = (MGraph*)malloc(sizeof(MGraph)) ;
CreateMGraph(G , S) ;
//輸出該圖信息
out(*G) ;
//運用Prim算法求最小生成樹
printf("Prim: \n") ;
Prim(G->edges , G->n , closevertex) ;
//運用Kruskal算法求最小生成樹
sort(S , G->e) ;
Kruskal(S , G->n , G->e) ;
free(G) ; //回收內存
}