最小生成树定义
我们把构造连通网的最小代价生成树称为最小生成树;也就是将一个带权值的连通图将各个点连通,使得所有连通的边的权值之和最小;
找连通网的最小生成树有两种经典算法,分别为普里姆算法和克鲁斯卡尔算法;这一篇总结普里姆算法;
Prim算法定义
假设N=(V,{E})是连通网,TE是N上最小生成树中边的集合;算法从U={u0}(u0∈V),TE={}开始;重复执行下述操作:在所有u∈U,v∈V-U的边(u,v)∈E中找一条代价最小的边(u0,v0)并入集合TE,同时v0并入U,直到U=V为止;此时TE中必有n-1条边,则T=(V,{TE})为N的最小生成树;该算法的时间复杂度为O(n2);
ps:看不懂可以忽略,我觉得正常人都很难看懂这样的定义,还不如直接看代码~
Prim代码思路及实现
主要思路是利用了广度优先遍历的性质,通过一个节点去找到与之相关联的其他顶点,从中找到一个权值最小的边,在利用最小边的另一个顶点,去关联更多顶点,生成更多的边,在做一个比较,选出最小的边,直到所有顶点都被加入最小生成树;
有点不好理解,而且本人表达能力有限,又懒得画图解释,所以emmmm。。。代码里我加了很多注释;
void MiniSpanTreeByPrim(Graph &graph) {
int adjVex[MAXVEX]; //保存相关顶点下标
int lowCost[MAXVEX];//保存相关顶点间边的权值
lowCost[0] = 0; //初始化第一个权值为0,代表顶点0加入生成树;
//哪一个顶点对应的索引为0,表示该顶点已加入生成树
adjVex[0] = 0; //初始化第一个顶点的下标为0;
//循环除第0个顶点之外的所有顶点
for(int i = 0; i <graph.numVertexes; ++i) {
lowCost[i] = graph.arc[0][i]; //将0号顶点对应的邻接矩阵中的哪一行拷入数组lowCost
adjVex[i] = 0; //将所有顶点对应的下标都初始化为0号顶点的下标;
}
int min, j, k;
for(int i = 1; i < graph.numVertexes; ++i) {
min = INFINITY;
j = 1;
k = 0;
while(j < graph.numVertexes) {
//判断j号顶点未加入生成树,且j与0号顶点的边的权值最小,然后将j的序号给k
if(lowCost[j] != 0 && lowCost[j] < min) {
min = lowCost[j];
k = j;
}
++j;
}
//打印当前顶点边中的最小权值边adjVex是已经被顶点更新的数组,
//只要有与该顶点相关连的顶点,其相关顶点再邻接表中的位置就会被更新称该顶点
//很难解释清楚,建议对照着邻接表,来掩饰代码的每一步,再列出对应的adjVex数组和lowCost数组
cout << adjVex[k] << "--" << k << "\t";
lowCost[k] = 0;//序号为k的顶点已经完成任务加入生成树,所以它在lowCost中的位置被置为0
//这个循环用来更新序号k的相关顶点对应的lowCost数组
for(int i = 1; i < graph.numVertexes; ++i) {
if(lowCost[i] != 0 && graph.arc[k][i] < lowCost[i]) {
lowCost[i] = graph.arc[k][i];
adjVex[i] = k;//将于k顶点相关的顶点的序号都置为k,做占有标志,在下一次循环中使用
}
}
}
}
测试
#include<iostream>
#include<queue>
using namespace std;
#define MAXVEX 20
#define INFINITY 7777 //用于初始化时填充邻接矩阵
typedef struct Graph {
char vexs[MAXVEX];
int arc[MAXVEX][MAXVEX];
int numVertexes, numEdges;
}*pGraph;
void CreateGraph(Graph &graph) {
cout << "输入顶点数和边数:";
cin >> graph.numVertexes >> graph.numEdges;
//建立顶点表
for(int i = 0; i < graph.numVertexes; ++i) {
cout << "请输入第" << i + 1 << "个顶点名:";
cin >> graph.vexs[i];
}
//初始化邻接矩阵
for(int i = 0; i < graph.numVertexes; ++i) {
for(int j = 0; j < graph.numVertexes; ++j) {
graph.arc[i][j] = INFINITY;
}
}
//建立邻接矩阵
int x, y, w;
for(int i = 0; i < graph.numEdges; ++i) {
cout << "输入边的下标x,y和权值w:";
cin >> x >> y >> w;
graph.arc[x][y] = w;
graph.arc[y][x] = w;
}
}
void MiniSpanTreeByPrim(Graph &graph) {
int adjVex[MAXVEX];
int lowCost[MAXVEX];
lowCost[0] = 0;
adjVex[0] = 0;
for(int i = 0; i <graph.numVertexes; ++i) {
lowCost[i] = graph.arc[0][i];
adjVex[i] = 0;
}
int min, j, k;
for(int i = 1; i < graph.numVertexes; ++i) {
min = INFINITY;
j = 1;
k = 0;
while(j < graph.numVertexes) {
if(lowCost[j] != 0 && lowCost[j] < min) {
min = lowCost[j];
k = j;
}
++j;
}
cout << adjVex[k] << "--" << k << "\t";
lowCost[k] = 0;
for(int i = 1; i < graph.numVertexes; ++i) {
if(lowCost[i] != 0 && graph.arc[k][i] < lowCost[i]) {
lowCost[i] = graph.arc[k][i];
adjVex[i] = k;
}
}
}
}
int main() {
Graph graph;
CreateGraph(graph);
for(int i = 0; i < graph.numVertexes; ++i) {
cout << "\t" << graph.vexs[i];
}
cout << "\n\n";
for(int i = 0; i < graph.numVertexes; ++i) {
cout << graph.vexs[i] << "\t";
for(int j = 0; j < graph.numVertexes; ++j) {
cout << graph.arc[i][j] << "\t";
}
cout << "\n\n";
}
MiniSpanTreeByPrim(graph);
getchar();
return 0;
}
总结
这个算法比较重要的就是要搞懂adjVex[]和lowCost[]这两个数组的作用,建议跟着代码一步一步将这两个数组写出来,并且与邻接矩阵做一个比对,如果搞明白了这两个数组的作用,已经他们的工作流程和原理,那么在这个算法也差不多就理解了~
其实想搞懂这个算法要对邻接矩阵和广度优先遍历有深刻的理解才行,如果实在理解不了可以先看一下我之前总结的关于邻接矩阵和广度优先遍历的文章~
数据结构复习 ---- 邻接矩阵
数据结构复习 ---- 广度优先遍历(BFS)