最小生成树的算法这两种算法都是本着贪心的思想从局部出发,一步步扩展,完成整体部分。
一个网络包含了一系列由链路相连的结点(在离散数学里面,也叫做图,结点就叫做顶点,链路叫做边)。
与树不同,网络中并没有根节点。链路可以是无向(可以从任意一个方向通过它)的, 从你家到我家和从我家到你家就都走一条路,距离也是一样的,或者是有向(只能从一个方向通过它),有向图可以想象,单向高速车道,从西安到宝鸡和从宝鸡到西安走的路是不同的,具体的有向无向取决于这个网络包含哪种的链路。
有几个重要的概念是:
回路(环路):一条返回起点的路径。
出度:离开这个结点的链路个数。
入度:进入这个结点的链路个数。
连通:在无向网络中,如果结点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
*/
运行结果: