首先什么是最小生成树(我的教材称为最小支撑树)?它是一个图的极小连通子图。那什么是图?什么是极小连通子图?
图是一种数据结构,由顶点集合V(非空)和边集合E构成,记为G=(V,E)。G即Graph图,V即Vertex顶点,E即Edge边。连通图可以理解为能够一笔画出来的图。而极小连通子图则是包含一个连通图的所有顶点(假设有n个),却只有(n-1)条边(这些边仍然属于E)的子图,并且这个子图仍然是连通的(如下图的G1和G2)。
而最小生成树不仅要是极小连通子图,并且它边上的权值之和要最小。显然,由一个连通图构造的最小生成树可能不唯一,但权值之和是唯一的。
这个问题的一个解法是基于贪心思想的Prim算法。设这个图是G=(V,E),最小生成树T=(Vt,Et),它的思想是从一个顶点u出发,即初始时T中的Vt={ u }, Et={ },寻找Vt中的顶点与其它顶点(即V-Vt,集合的差运算)构成的边的最小权值
-------------------------------以下的讨论均以上图为例(请忽略我的鬼画符......)-------------------------------
在这个图中,假设我们从V1点出发(哪个点一样),可以看到,当前Vt中只有一个顶点{ V1 },从Vt各点到V1,V2,...,V6的最小权值用一个数组low_cost[]表示,约定-1为结点已经加入Vt,∞表示Vt各点不能到达结点。则数组的值是{-1,6,1,5,∞,∞}。由此可以,下一步我们应当把V3加入Vt,因为此时Vt的顶点到其余各点权值最小的便是V3,权值为1。如此Vt={ V1,V3 },Et={ (V1->V3) }
要注意的是,此时的low_cost[]数组应当进行更新,因为V3加入Vt,故而而V3到V2的距离是5,比原来的6要小;V3到V4、V5的距离是6、4,比原来的无穷大∞要小,因此low_cost[] = { -1,5,-1,5,6,4 }。
显然,此时low_cost[]中权值最小(不计-1)的是4,即V6,因此将V6加入Vt,此时Vt={ V1,V3,V6 }, Et={ (V1->V3), (V3->V6) }。同上可知,V6到V4的距离为2,比原来的6要小,因此low_cost[] = { -1,5,-1,5,2,-1 }。
同理再选取V5加入Vt......
............
如此一直进行到V == Vt为止,时间复杂度为O( |V|² ) ,|V|是图的顶点个数。
以下是源码和基于以上例子调试结果:
其中代码部分参考 https://blog.csdn.net/qq_35644234/article/details/59106779
#include <iostream>
#include <string>
#include <vector>
#include <climits>
using namespace std;
typedef struct{
int vexnum; //顶点数
int arcnum; //边数
int **arc; //邻接矩阵
string *name;
}Matrix_Graph;
bool newGraph(Matrix_Graph& g)
{
cout << "-------准备使用邻接矩阵法创建无向带权图-------" << endl;
cout << "请输入顶点数和边数(空格隔开): ";
cin >> g.vexnum >> g.arcnum;
// 图可以没有边,但不能没有顶点
// 若无向图有n个顶点,则最多有n*(n-1)/2条边(无向完全图)
if( g.vexnum<0 || g.arcnum<=0 || g.arcnum>(g.vexnum*(g.vexnum-1)/2) ){
cerr << "数据输入有误,请检查数据!" << endl;
g.vexnum = g.arcnum = 0;
return false;
}
// 邻接矩阵初始化
g.arc = new int*[g.vexnum+1];
g.name = new string[g.vexnum+1];
for(int i = 1; i <= g.vexnum; ++i){
g.name[i] = "V" + to_string(i); //从1开始计数,边为V1, V2, V3...
g.arc[i] = new int[g.vexnum+1];
for(int j = 1; j <= g.vexnum; ++j)
g.arc[i][j] = INT_MAX; //各边距离初始化为无穷大
}
// 输入各边的权值
int vstart, vend, weight;
int n = g.arcnum;
cout << "请输入" << n << "条边的起始顶点、终止顶点和权值(空格隔开)" << endl;
while( n-- ){
// 懒,这里就不做输入检查了
cin >> vstart >> vend >> weight;
g.arc[vstart][vend] = g.arc[vend][vstart] = weight;
}
return true;
}
// 该结构体数组存储当前最小生成树(Vt)中的顶点到
// 其它顶点(V-Vt)的边的最小距离weight,-1代表该顶点已经在V中了
// vstart是该边起点,vend是该边终点
typedef struct{
int vstart;
int vend;
int weight;
}Closest;
void printRes(vector<string>& Vt, vector<Closest>& Et, Closest *low_cost)
{
cout << "当前最小生成树的顶点有:";
for(auto v : Vt)
cout << v << ", ";
cout << endl;
cout << "当前最小生成树的边:";
for(auto e : Et)
cout << "V"+to_string(e.vstart) << "->" << "V"+to_string(e.vend) << ", ";
cout << endl;
cout << "当前到各结点最小权值:" << endl;
for(int i = 1; i <= low_cost[0].weight; ++i){
cout << "到V" << i << ": ";
if( low_cost[i].weight == INT_MAX )
cout << "∞" << endl;
else
cout << low_cost[i].weight << endl;
}
cout << endl;
}
bool Prim(const Matrix_Graph& g, int start = 1)
{
// 以下2个vector仅为了输出过程及结果,对算法无影响
vector<string> Vt; // 存储当前最小生成树的结点
vector<Closest> Et;// 存储当前最小生成树的边
int i, j, k;
Closest *low_cost = new Closest[g.vexnum+1];
low_cost[0].weight = g.vexnum; // 记录顶点数,方便输出
for(i = 1; i <= g.vexnum; ++i){
low_cost[i].weight = g.arc[start][i];
low_cost[i].vstart = start;
low_cost[i].vend = i;
}
low_cost[start].weight = -1;
Vt.push_back(g.name[start]); // 起始顶点存储进Vt
printRes(Vt, Et, low_cost);
// 将剩下的 g.vexnum-1 个结点加入Vt中
for(i = 1; i < g.vexnum; ++i)
{
// 查找当前可连接的最小权值边
int index = 0, min = INT_MAX;
for(j = 1; j <= g.vexnum; ++j){
if( low_cost[j].weight!=-1 && low_cost[j].weight<min ){
min = low_cost[j].weight;
index = j;
}
}
if( index == 0 ){
cerr << "此图不能形成最小生成树!" << endl;
return false;
}
// 该边的weight置-1,表明存储进V
low_cost[index].weight = -1;
Vt.push_back(g.name[index]);
Et.push_back(low_cost[index]);
// 更新low_cost数组,因为此时新加入的结点可能
// 比原来的结点到其它结点的权重要小
int newPos = low_cost[index].vend; //新加入结点的编号
for(k = 1; k <= g.vexnum; ++k){
if( g.arc[newPos][k] < low_cost[k].weight ){
low_cost[k].weight = g.arc[newPos][k];
low_cost[k].vstart = newPos;
low_cost[k].vend = k;
}
}
// 打印信息
printRes(Vt, Et, low_cost);
}
cout << "------最小生成树构造完毕!------" << endl;
return true;
}
int main()
{
Matrix_Graph g;
if( newGraph(g) ){
Prim(g);
}
return 0;
}