一,最小生成樹
圖1 搭建網絡圖
在圖1中,在A到I的9個村莊中間搭建通信網絡,上面的數值就是搭建網路的成本,這個時候要求實現最低的成本從而保證這9個村莊的通信。
圖2 方案一的設計原理圖
圖3 方案二的設計原理圖
圖4 方案四的設計原理圖
從圖1到圖9依次有三個方案,這個時候可以看到方案三的成本最低。但是如果結點數量很多的時候就不能使用窮舉法來一一對應設計,因此這個時候怎麼設計出一個快速生成最小生成樹的方法是一個需要解決的問題。
二,普利姆算法
圖5 普里姆算法的鄰接矩陣表
圖5中的原理圖中,ABCDEFGH對應着v0、v2、v3、v4、v5、v6、v7、v8。
代碼實現如下:
//Prim算法生成最小生成樹
void MiniSpanTree_Prim(MGraph G)
{
int min, i, j, k;
int adjvex[MAXVEX]; //保存相關頂點下標[0,0,1,0,0,0,1,0,1]
int lowcost[MAXVEX]; //保存相關頂點間邊的權值
lowcost[0] = 0; //v0作爲最小生成樹的根開始遍歷,權值爲0
adjvex[0] = 0; //v0第一個加入
//初始化操作
for(i=1;i<G.numVertexes; i++)
{
lowcost[i] = G.arc[0][i]; //將鄰接矩陣第0行所有權值先加入數組
adjvex[i] = 0; //初始化全部先爲v0的下標
}
//真正構造最小生成樹的過程
for(i=1;i<G.numVertexes;i++)
{
min = INFINITY; //初始化最小權值爲65535等不可能數值
j = 1;
k = 0;
//遍歷全部頂點
while(j<G.numVertexes)
{
//找出lowcost數組已存儲的最小權值
if(lowcost[j]!=0 && lowcost[j]<min)
{
min = lowcost[j];
k = j; //將發現的最小權值的下標存入k,以待使用
}
j++;
}
//打印當前頂點邊中權值最小的邊
printf("%d,%d",adjvex[k],k);
lowcost[k] = 0; //將當期頂點的權值設置爲0,表示此頂點已經完成任務,進入到下一個頂點的遍歷
//鄰接矩陣k行逐個遍歷全部頂點
for(j=1;j<G.numVertexes;j++)
{
if(lowcost[j]!=0 && G.arc[k][j]<lowcost[j])
{
lowcost[j] = G.arc[k][j];
adjvex[j] = k;
}
}
}
}
三,克魯斯卡爾算法
無論是普里姆算法還是克魯斯卡爾算法,他們考慮問題的出發點都是:爲了使得生成樹上邊的權值之和達到最小,則應使生成樹中每一條邊的權值儘可能的小。普里姆算法是以某個頂點爲起點,逐步找各個頂點上最小權值的邊來構建最小生成樹的。現在我們換一種思考方式,我們從邊出發,因爲權值是在邊上,直接去找最小權值的邊來構建生成樹是自然的想法,這也是克魯斯卡爾算法的精髓。
圖6 克魯斯卡爾算法原理圖
在圖6中使用的是邊集數組。
//Kruskal(克魯斯卡爾算法)生成最小生成樹
int Find(int *parent, int f)
{
while(parent[f]>0)
{
f = parent[f];
}
return f;
}
void MiniSpanTree_Kruskal(MGraph, G)
{
int i, n, m;
Edge edges[MAGEDGE]; //定義邊集數據
int parent[MAXVEX]; //定義parent數組用來判斷邊與邊是否形成環路
for(i=0;i<G.numVerteses;i++)
{
parent[i] = 0;
}
for(i=0;i<G.numVerteses;i++)
{
n = Find(parent,edges[i].begin);
m = Find(parent,edges[i].end);
if(n!=m) //如果n=m,則形成環路,不滿足
{
parent[n] = m; //將此邊的結尾頂點放入下標爲起點的parent數組中,表示此頂點已經在生成樹集合中
printf("(%d,%d) %d ", edges[i].begin, edges[i].end, edges[i].weight);
}
}
}