Prime算法是解决最小生成树的一个算法。
不管是Kruskal算法还是Prime算法去,其核心都是切分定理。
切分定理:
切分
把图中的节点分为两部分,称为一个切分(Cut);
横切边
如果一个边的两个端点,属于切分(Cut)不同的两边,这个边称为横切边(Crossing Edge);
切分定理
给定任意切分,横切边中权值最小的边必然属于最小生成树;
切分定理的证明可使用反正法,这里不给出证明,证明不了只需要知道切分定理的正确性就能推出Prime算法的正确性。
Prime算法:
Prime算法是切分定理的正向应用。首先选定一个顶点0,当选择一个顶点后就把它看成一个切分,一部分为已选顶点集合{0},另一部分为未选顶点集合{1,2,3,4,5}。那么此时会出现横切边{{0,1},{0,2},{0,3}}。
按照切分定理选择最小的边,{0,3}则为最小生成树的一条边。此时将{0,3}这条边加入到存储最小生成树的数据结构中,将顶点3加入到已选集合中{0,3},未选集合为{2,4,5},这时横向边集合也发生了改变{{0,2},{0,1},{0,3},{3,7},{3,5},{3,4}}。
可以观察到{0,3}这条边刚刚已经加入到最小生成树中了,已经不再是横向边了。
此时可按照切分定理,选择最小的横向边,将边的另一点加入到已选集合中。
按照这个算法继续往下执行到如图所示:
可发现横向边从{0,2}从橙色变成了绿色。其实很好理解,已选集合{0,2,3,4}中的{0,2}那条边已经没有机会再被选择了,他们内部已经构成了局部最小生成树了。
按照这个算法,直到所有顶点都在已选集合中,算法就计算结束了。
总而言之,就是在找到的点和未找到的点的两个集合之间找到路劲最短的边,再将未找到的点放到已经找到的点的集合里
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#define N 100
#define max 1000;
//无向图邻接矩阵数据结构
typedef struct {
int vcount;//顶点数
char vexs[N]; // 顶点信息
int arcs[N][N]; //带权关系矩阵,就是边
} GraphMatrix;
int letter2num(char letter) {
return letter - 65;
}
char num2letter(int num) {
return num + 65;
}//数字转字母,字母转数字
//void printGraph(GraphMatrix* G)
//{//本函数输出图的邻接矩阵
// int i, j;
// for (i = 0; i < G->vcount; i++)
// {
// printf("顶点%c ", G->vexs[i]);
// for (j = 0; j < G->vcount; j++)
// printf("%d ", G->arcs[i][j]);
// printf("\n");
// }
//}
/*图初始化-邻接矩阵
*/
GraphMatrix* initGraphMatrix()
{
GraphMatrix* G;
G = (GraphMatrix*)malloc(sizeof(GraphMatrix));
int v, s;
printf("请输入定点数和边数: ");
scanf("%d %d",&v, &s);
G->vcount = v;
int i, j;
getchar(); //吸收回车
printf("请输入定点名称: ");
for (i = 0; i < v; i++) //创建矩阵
{
scanf("%c", &G->vexs[i]);
getchar(); //吸收回车
for (j = 0; j < v; j++)
{
G->arcs[i][j] = max;//初始化矩阵
}
}
char str_s, str_e;
int start,end,length;
for (i = 0; i < s; i++) {
printf("请输入边%d的顶点1,2和长度: ",i+1);
scanf("%c %c %d", &str_s, &str_e,&length);
getchar(); //吸收回车
start =letter2num(str_s);
end =letter2num(str_e);
G->arcs[start][end] = length;
G->arcs[end][start] = length;
}
return G;
}
void prime(GraphMatrix* G,char a) {
int v=letter2num(a); //起始点
int visit[N]={0}; //点是否访问过,未访问0,访问1
visit[v] = 1; //起始点已经访问过
int short_length[N]; //访问过的点的集合到未访问过点的集合的最小路径
for (int i = 0; i < G->vcount; i++)
short_length[i] = G->arcs[v][i]; //初始化,此时只有起始点
short_length[v] = 0; //起始点路径为0
printf("%c", a);
for (int i = 0; i < G->vcount-1; i++) //有n个点,那最短路径就是n-1个边组成
{
int min_length = max;
int k=0;
for (int j = 0; j < G->vcount; j++) { //在所有未访问过的边里找到最短路径
if (min_length > short_length[j] && visit[j] == 0) {
k = j;
min_length = short_length[j];
}
}
visit[k] = 1;//将这个点加入已访问过的集合
printf("-%c", num2letter(k));
for (int j = 0; j < G->vcount; j++) {
if (visit[j] == 0 && short_length[j] > G->arcs[k][j])
short_length[j] = G->arcs[k][j];
}//更新未访问过点的最短路径
}
return;
}
int main()
{
GraphMatrix* G = initGraphMatrix();
//printGraph(G);
char a;
//scanf("请输入起始点%c", &a);
a = 'A'; //假设起始点为A
prime(G,a);
}
案例:
6 10
A B C D E F
A B 5
A C 6
A D 4
B C 1
B D 2
C D 2
C E 5
C F 3
D F 4
F E 4