最小生成樹的算法這兩種算法都是本着貪心的思想從局部出發,一步步擴展,完成整體部分。
一個網絡包含了一系列由鏈路相連的結點(在離散數學裏面,也叫做圖,結點就叫做頂點,鏈路叫做邊)。
與樹不同,網絡中並沒有根節點。鏈路可以是無向(可以從任意一個方向通過它)的, 從你家到我家和從我家到你家就都走一條路,距離也是一樣的,或者是有向(只能從一個方向通過它),有向圖可以想象,單向高速車道,從西安到寶雞和從寶雞到西安走的路是不同的,具體的有向無向取決於這個網絡包含哪種的鏈路。
有幾個重要的概念是:
迴路(環路):一條返回起點的路徑。
出度:離開這個結點的鏈路個數。
入度:進入這個結點的鏈路個數。
連通:在無向網絡中,如果結點B對於結點A是可達的,那麼結點A和結點B就是連通的,反之亦然。對於整個無向網絡而言,如果能從一個結點出發,可以達到網絡中的所有其他節點,那麼這個無向網絡就是連通的。
就以無向圖爲例來討論這兩張最小生成樹算法。
現在有很多個結點,各結點信息和鏈路信息如下圖,選擇找出來一條路徑,使得各節點之間是連通的。
更爲實際的情景是這樣的情況,在某地分佈着N
個村莊,現在需要在N
個村莊之間修路,每個村莊之前的距離不同,問怎麼修最短的路,將各個村莊連接起來。
Prim算法整體的思想是,將所有的結點分爲兩類,一類是已加入的結點集合A,一類是待加入的結點集合B。
每次從集合A裏面找可以達到的所有結點中最小鏈路的結點。然後將該結點加入到A中去。繼續重複這樣的操作,直到所有的結點都加入進來。prim算法也叫加點法,
下圖的過程是prim算法最小生成樹的一個過程,任選一點A出發
而Kruskal算法則是從邊出發的思想,把所有的鏈路分爲兩類,一類是已經加入的鏈路,另一類就是剩下的鏈路,每次從剩下的邊中選擇不會產生迴路並且具有最小權值的邊加入到生成樹中。直到生成樹中包含了所有的點。
對比兩種最小生成樹的算法,可見kruskal算法主要是基於邊的排序,以及合併連通分量完成的,依賴邊的個數,適合稀疏圖(頂點多邊少)。而Prim算法依賴於頂點的個數,更適合稠密圖(頂點少邊多)。
兩種算法實現起來,Kruskal算法的核心是:找當前剩下邊的最小,保證添加邊不會造成迴路。
Prim算法的核心是:注意更新當前生成樹裏面可以達到的各點經過的邊的信息,一旦發現有更小的邊,就更新數據,添加進來。方便下一次的尋找最小邊。
大概寫了個Prim算法的過程,結果是添加結點的順序
核心的算法在這裏
typedef struct CLOSE_EDG {
int adjvex; //從哪個結點過來
int lowcost; //權值 也是是否就加入的biaozh
}CLOSE_EDG;
char *Prim(const GRAPH *graph, CLOSE_EDG *closedge) {
const int start = 0;
int i;
int j;
int k;
int min_index;
int min;
closedge[start].lowcost = HaveJoined; //標記起點start已經加入生成樹中
char *JoinOrder = NULL; //記錄各頂點添加到生成樹的先後順序
int beginIndex = 0;
JoinOrder = (char *)calloc(sizeof(char), graph->vexnum + 1);
JoinOrder[beginIndex++] = graph->vec_infor[start]; //將起始點加進去結果
// 然後初始化除起始點之外的剩下的節點
for(i = 0; i < graph->vexnum; i++) {
if(i != start) {
closedge[i].adjvex = start;
// 分別是起始點也就是A到其他各點的距離
closedge[i].lowcost = graph->arc[start][i];
}
}
// 這裏只需要再加入n-1個頂點即可 已經加入了起始點了
for(i = 0; i < graph->vexnum - 1; i++) {
min = INFINITY;
// 找一個最小權重的邊 以及這條邊所到達的頂點
for(k = 0; k < graph->vexnum; k++) {
if(closedge[k].lowcost != HaveJoined && closedge[k].lowcost < min) {
min_index = k;
min = closedge[k].lowcost;
}
}
// printf("%d ", min_index);
JoinOrder[beginIndex++] = graph->vec_infor[min_index]; //加入找到的最小權重所達到的點
closedge[min_index].lowcost = HaveJoined; //標記該頂點已經加入了生成樹中去了
// 在鄰接矩陣中比較 新加入的點所能達到的各點 經過的路徑是否有小於現在closedge裏面記錄的路徑
for(j = 0; j < graph->vexnum; j++) {
if(j != min_index && graph->arc[min_index][j] < closedge[j].lowcost) {
closedge[j].lowcost = graph->arc[min_index][j]; //更新權重值
closedge[j].adjvex = min_index;
}
}
}
JoinOrder[beginIndex] = '\0';
return JoinOrder;
}
全部代碼:
#include<stdio.h>
#include<malloc.h>
#define MAXVEXNUM 100
#define INFINITY 99999
#define HaveJoined 0
typedef struct GRAPH{
int vexnum; //頂點個數
int edgenum; //邊的個數
int arc[MAXVEXNUM][MAXVEXNUM]; //圖的鄰接矩陣
char *vec_infor; //記錄各個頂點的信息 長度和vexnum保持一致
}GRAPH;
typedef struct CLOSE_EDG {
int adjvex;
int lowcost;
}CLOSE_EDG;
void createGraph(GRAPH **graph, int vexnum, int edgenum);
void destoryGraph(GRAPH **graph);
void showMatrix_1(int array[][MAXVEXNUM], int n);
char *Prim(const GRAPH *graph, CLOSE_EDG *closedge);
char *Prim(const GRAPH *graph, CLOSE_EDG *closedge) {
const int start = 0;
int i;
int j;
int k;
int min_index;
int min;
closedge[start].lowcost = HaveJoined; //標記起點start已經加入生成樹中
char *JoinOrder = NULL; //記錄各頂點添加到生成樹的先後順序
int beginIndex = 0;
JoinOrder = (char *)calloc(sizeof(char), graph->vexnum + 1);
JoinOrder[beginIndex++] = graph->vec_infor[start]; //將起始點加進去結果
// 然後初始化除起始點之外的剩下的節點
for(i = 0; i < graph->vexnum; i++) {
if(i != start) {
closedge[i].adjvex = start;
// 分別是起始點也就是A到其他各點的距離
closedge[i].lowcost = graph->arc[start][i];
}
}
// 這裏只需要再加入n-1個頂點即可 已經加入了起始點了
for(i = 0; i < graph->vexnum - 1; i++) {
min = INFINITY;
// 找一個最小權重的邊 以及這條邊所到達的頂點
for(k = 0; k < graph->vexnum; k++) {
if(closedge[k].lowcost != HaveJoined && closedge[k].lowcost < min) {
min_index = k;
min = closedge[k].lowcost;
}
}
// printf("%d ", min_index);
JoinOrder[beginIndex++] = graph->vec_infor[min_index]; //加入找到的最小權重所達到的點
closedge[min_index].lowcost = HaveJoined; //標記該頂點已經加入了生成樹中去了
// 在鄰接矩陣中比較 新加入的點所能達到的各點 經過的路徑是否有小於現在closedge裏面記錄的路徑
for(j = 0; j < graph->vexnum; j++) {
if(j != min_index && graph->arc[min_index][j] < closedge[j].lowcost) {
closedge[j].lowcost = graph->arc[min_index][j]; //更新權重值
closedge[j].adjvex = min_index;
}
}
}
JoinOrder[beginIndex] = '\0';
return JoinOrder;
}
void destoryGraph(GRAPH **graph) {
if((*graph) == NULL) {
return;
}
free((*graph)->vec_infor);
free(*graph);
*graph = NULL;
}
void createGraph(GRAPH **graph, int vexnum, int edgenum) {
int i;
int data;
int from;
int to;
int j;
*graph = (GRAPH *)calloc(sizeof(GRAPH), 1);
(*graph)->vexnum = vexnum;
(*graph)->edgenum = edgenum;
(*graph)->vec_infor = (char *)calloc(sizeof(char), vexnum + 1);
for(i = 0; i < vexnum; i++) {
printf("input the %d/%d vex infor: ", i+1, vexnum);
setbuf(stdin, NULL);
(*graph)->vec_infor[i] = getchar();
}
for(i = 0; i < vexnum; i++) {
for(j = 0; j < vexnum; j++) {
(*graph)->arc[i][j] = INFINITY;
}
}
for(i = 0; i < vexnum; i++) {
(*graph)->arc[i][i] = 0;
}
for(i = 0; i < edgenum; i++) {
printf("input the %d/%d ede infor(from to power):", i+1, edgenum);
setbuf(stdin, NULL);
scanf("%d%d%d", &from, &to, &data);
// printf("from = %d, to = %d, data = %d\n", from, to, data);
(*graph)->arc[from][to] = (*graph)->arc[to][from]= data;
}
}
void showCLoseEdge(CLOSE_EDG *closedge, const int vexnum) {
int i;
for(i = 0; i < vexnum; i++) {
printf("%d ", closedge[i].adjvex);
}
printf("\n");
for(i = 0; i < vexnum; i++) {
printf("%d ", closedge[i].lowcost);
}
printf("\n");
}
int main(void) {
GRAPH *graph = NULL;
int vexnum = 6; //頂點個數
int edgenum = 10;
CLOSE_EDG *closedge = NULL;
char *JoinOrder;
printf("input vexnum and edgenum: ");
scanf("%d%d", &vexnum, &edgenum);
closedge = (CLOSE_EDG *)calloc(sizeof(CLOSE_EDG), vexnum);
createGraph(&graph, vexnum, edgenum);
showMatrix_1(graph->arc, vexnum);
JoinOrder = Prim(graph, closedge);
printf("JoinOrder : %s\n", JoinOrder);
// showCLoseEdge(closedge, vexnum);
destoryGraph(&graph);
free(closedge);
free(JoinOrder);
JoinOrder = NULL;
closedge = NULL;
return 0;
}
void showMatrix_1(int array[][MAXVEXNUM], int n) {
int i;
int j;
for(i = 0; i < n; i++) {
for(j = 0; j < n; j++) {
// printf("%d ", array[i][j]);
printf("%6d ", *(*(array + i)+j));
}
printf("\n");
}
printf("\n");
}
/*
6 10
A
B
C
D
E
F
0 1 6
0 2 1
0 3 5
1 2 5
1 4 3
2 3 5
2 4 6
2 5 4
3 5 2
4 5 6
*/
運行結果: