概况
最小生成树针对的是无向图!它是原图的极小连通子图,是最小权重生成树的简称。其特点是:图中包含原图中的所有 n 个结点,并且有保持图连通的最少的边。
求最小生成树有两种算法,分别是:Kruskal(克鲁斯卡尔)算法、Prim(普里姆)算法。
本文将介绍笔者对Prim算法的理解。Kruskal算法将在后续文章介绍。
Prim算法
该算法需要用邻接矩阵存储图(使用edge数组记录边的权值,vertex数组存储顶点数据)。
class MGraph
{
public:
MGraph(DataType a[ ], int n, int e); //构造函数——建图(建立n个顶点e条边的图)
~MGraph( ){ }; //析构函数
void Prim(int v);
private:
DataType vertex[MaxSize]; //存放图中顶点的数组
int edge[MaxSize][MaxSize]; //存放图中边的数组
int vertexNum, edgeNum; //图的顶点数和边数
int MinEdge(int r[ ], int n);
};
图的构造
存储图的基本信息(边、点、代价)
MGraph :: MGraph(char a[ ], int n, int e)
{
int i, j, k, w;
vertexNum = n; edgeNum = e;
for (i = 0; i < vertexNum; i++) //存储顶点
vertex[i] = a[i];
for (i = 0; i < vertexNum; i++)
//初始化邻接矩阵:初始化权值(赋很大的数值即可)
for (j = 0; j < vertexNum; j++)
if (i == j)
edge[i][j] = 0; //顶点自己到自己不需要代价
else
edge[i][j] = 100;
for (k = 0; k < edgeNum; k++) //依次输入每一条边
{
cout << "请输入边依附的两个顶点的编号,以及边上的权值:";
cin >> i >> j >> w; //输入边依附的两个顶点的编号
edge[i][j] = w; edge[j][i] = w; //置有边标志
}
}
核心代码
prim算法的思想是加点法,即将图中顶点(V)分两部分,最小生成树的点集为U,其余顶点在集合(V-U)中。加点过程中,需要使用数组adjvex记录上一轮找到的最小值的位置(也就是连接当前最小值点的权值最小边另一端顶点),使用cost数组记录到各顶点的代价。
简言之,每轮迭代都可以找到到达新加入U中顶点代价最短的边及该边另一端的顶点,输出内容均为当前最短边两端的顶点及这条最短边的权值!
基本思路
1.初始化
将起点的邻接矩阵录入cost中,将所有加点位置(adjvex)置为起点。
2.从起点开始对剩余的(n-1)个点迭代
每轮迭代过程中,首先需要在cost数组内寻找最小值,并输出本轮最小值到上一轮最小值之间的代价,同时将本轮最小值位置的数据清0,以防止之后的遍历重复。【从起点出发花最少代价可到的点加入U】
之后再次找最小权值,并更新cost数组,同时在adj数组中记录上一轮迭代得到最小值的位置【记录新加入U中的点来源】
代码实现
找最小值的位置
int FindMin(int r[],int n){
int index;
int min=100; //图中所有权值最大不超过100
for(int i=0;i<n;i++)
if(r[i]!=0 &&r[i]<min){
min=r[i];
index=i;
}
return index; //返回最小值在数组中的位置
}
Prim核心代码
void Prim(int v){
int adjvex[MaxSize],cost[MaxSize];
int i,j,k;
//通过起点对adj、cost数组初始化
for(i=0;i<vertxNum;i++){
cost[i]=edge[v][i];
adjvex[i]=v;
//将起点的邻接矩阵录入cost中,将所有加点位置(adjvex)置为起点。
}
cost[v] = 0; //将顶点加入U中
for(k=1;k<vertxNum;k++) {
j=FindMin(cost,vertxNum); //在cost数组内寻找最小值
cout<<"("<<vertx[j]<<","<<vertx[ adjvex[j] ]<<")" <<cost[j]<<endl; //输出本轮最小值到上一轮最小值之间的代价
cost[j]=0; //将最小值点加入U中(将到达当前最小值位置的代价清0,也有防止之后重复遍历的作用)
for(int p=0;p<vertxNum;p++){
if(edge[p][j]<cost[p]){ //从所有与当前最小值邻接点出发找到最小值点代价最小的
cost=edge[p][j];
adjvex[p]=j; //记录新加入顶点前上一轮迭代得到的最小值位置
}
}
}
}
实例
如上图,求其最小生成树。
代码
#include<iostream>
using namespace std;
const int MaxSize=100;
class MGraph
{
public:
MGraph(char a[ ], int n, int e); //构造函数——建图(建立n个顶点e条边的图)
~MGraph( ){ }; //析构函数(图使用静态存储,无需手动析构!)
void Prim(int v);
private:
char vertex[MaxSize]; //存放图的顶点
int edge[MaxSize][MaxSize]; //邻接矩阵
int vertxNum, edgeNum; //图的顶点数和边数
};
MGraph :: MGraph(char a[ ], int n, int e)
{
int i, j, k, w;
vertxNum = n; edgeNum = e;
for (i = 0; i < vertxNum; i++) //存储顶点
vertex[i] = a[i];
for (i = 0; i < vertxNum; i++)
//初始化邻接矩阵:初始化权值(赋很大的数值即可)
for (j = 0; j < vertxNum; j++)
if (i == j)
edge[i][j] = 0; //顶点自己到自己不需要代价
else
edge[i][j] = 100;
for (k = 0; k < edgeNum; k++) //依次输入每一条边
{
cout << "输入边依附的两个顶点的编号,以及边上的权值:";
cin >> i >> j >> w; //输入边依附的两个顶点的编号
edge[i][j] = w; edge[j][i] = w; //置有边标志
}
}
int FindMin(int r[],int n){
int index;
int min=100; //图中所有权值最大不超过100
for(int i=0;i<n;i++)
if(r[i]!=0 &&r[i]<min){
min=r[i];
index=i;
}
return index; //返回最小值在数组中的位置
}
void MGraph :: Prim(int v){
int adjvex[MaxSize],cost[MaxSize];
int i,j,k;
//通过起点对adj、cost数组初始化
for(i=0;i<vertxNum;i++){
cost[i]=edge[v][i];
adjvex[i]=v;
//将起点的邻接矩阵录入cost中,将所有加点位置(adjvex)置为起点。
}
for(k=1;k<vertxNum;k++) {
j=FindMin(cost,vertxNum); //在cost数组内寻找最小值
cout<<"("<<vertex[j]<<","<<vertex[ adjvex[j] ]<<")" <<cost[j]<<endl; //输出本轮最小值到上一轮最小值之间的代价
cost[j]=0; //为防止之后的遍历重复,将本轮最小值位置的数据清0
for(int p=0;p<vertxNum;p++){
if(edge[p][j]<cost[p]){ //从所有与当前最小值邻接点出发找到最小值点代价最小的
cost[p]=edge[p][j];
adjvex[p]=j; //记录新加入点之前上一轮迭代中最小值的位置
}
}
}
}
int main( )
{
/*测试数据为带权无向图, 输入边依次为 :
(0 1 34)(0 2 46)(0 5 19)(1 4 12)(2 3 17)(2 5 25)(3 4 38)(3 5 25)(4 5 26) */
char ch[ ]={'A','B','C','D','E','F'};
MGraph MG(ch, 6, 9);
MG.Prim(0);
return 0;
}
运行结果
即最小生成树为: