最小代价生成树的3条构造准则:
1).只能使用网络中的边来构造最小生成树;
2).能且只能使用 n-1 条边来连接网络中的 n 个顶点;
3).选用的 n-1 条边不能产生回路。
一、Prim算法简介
基本思想:给定任意一带权连通网络 N={V,E},T={U,TE}是N的最小生成树(生成树也是图)。
1.算法始终将顶点集合V分成没有元素重叠的两部分,U 和 V-U;
2.生成树T的初始状态为 U={u。}(即U只包含V中的一个初始顶点),TE为空集;
3.重复执行以下操作:在所有集合U 和集合V-U 的边中找出一条代价最小的边(u。, v。)并入集合TE,同时 v。并入U,直至集合U中包含了V中全部顶点为止。此时TE中必有n-1条边,则T就是N的最小生成树。
4.引入辅助数组 closeEdge,记录 从 U 到 V-U 具有最小代价的边。
(提示:上面这句话的意思是,集合U中顶点只能做边的边尾,集合V-U中的顶点只能做边头,限定了方向;
这样就能保证不会形成回路,(这一点以 lowcost = 0 来实现)
1.新入集合U中的顶点 不会指向集合U中的原有顶点;
2.集合V-U中的顶点 不会指向集合U中的顶点。)
4.1.集合V-U中的每个顶点,在closeEdge数组中都对应一个下标,即 vi -> closeEdge[i-1],它包含两个域 adjvex、lowcost;
4.2.adjvex存储集合U中顶点(下标索引);
4.3.lowcost存储从 adjvex 到 i 的边的权值;
4.4.当closeEdge对应下标 k 位置的lowcost=0时,代表 k 顶点已从集合V-U转入到集合U;
5.Prim算法时间复杂度为:O(n^2),即与顶点数有关,与边无关,故适用于边稠密的网的最小生成树的求解。
注:代码中使用的头文件"GraphAdjMat.h"参看之前博文“数据结构之图(邻接矩阵实现)”;头文件"ObjArrayList.h"参看之前博文“数据结构之顺序列表(支持对象元素)”。
二、图解
三、代码
//文件名:"MST_PRIM.cpp"
#include "stdafx.h"
#include <string>
#include "GraphAdjMat.h"
using namespace std;
struct CloseEdge
{
int adjVex; //集合U 顶点索引 (边尾)
int lowCost; //边代价(权重)
};
GraphAdjMat * GraphAdjMat::MiniSpanTree_Prim(string * vertex)
{
/*
. 最小(代价)生成树:( Minimum Cost Spanning Tree )
. 入参:
. string * vertex: 起始顶点
. 出参:
. GraphAdjList *: 生成树的图对象
. 算法实现:( prim 算法 )
. 时间复杂度:O(n^2)
*/
//判断是否为无向网
if (this->type != UDN)
return NULL;
//初始化最小生成树 图
GraphAdjMat * mst = new GraphAdjMat(GraphAdjMat::UDN);
mst->Init(new ObjArrayList<string>(), new ObjArrayList<ArcData>());
//辅助数组 closeEdge, 初始化为最大顶点个数
// 用于存放 集合U -> 集合V-U 中的各条代价边
CloseEdge closeEdge[this->_MAX_VERTEX_NUM];
//定位起始顶点位置
int index = _Locate(*vertex);
//最小生成树中 插入第一个顶点
mst->InsertVertex(vertex);
//1.1.将 index 位置顶点(第一个顶点) 加入到 集合U,用 0 标记
closeEdge[index].lowCost = 0;
//1.2.向closeEdge 中加入第一个顶点的所有边信息
for (int i = 0; i < this->_MAX_VERTEX_NUM; i++)
{
if (index != i)
{
//初始化 各顶点与起始顶点的依附关系 到 closeEdge
//注:1.考虑到顶点删除操作后的不连续存储,故遍历整个静态数组;2.包含 无顶点信息的 无穷位置
closeEdge[i].adjVex = index;
closeEdge[i].lowCost = this->arcs[index][i].adj;
}
}
//2.循环 n-1 次(即 顶点数 - 1)寻找 n-1 条最小代价边,根据 MST 性质
for (int n = 0; n < this->vexNum - 1; n++)
{
//输出 closeEdge 数组
cout << endl << "数组:" << endl;
for (int i = 0; i < _MAX_VERTEX_NUM; i++)
{
cout << "(" << closeEdge[i].adjVex << "->" << i << ":" << closeEdge[i].lowCost << ") ";
}
cout << endl;
//2.1.寻找 集合U -> 集合V-U 中的最小代价边
int k = 0; //最小代价边 对应的顶点位置
for (int i = 0; i < this->_MAX_VERTEX_NUM; i++)
{
/*
. 过滤掉 lowCost == 0 的位置,即只在 代表 集合V-U 的下标索引 中找
. ** 解析:
. 1.从 集合U 到 集合V-U 的边中,集合U 中顶点都充当了 边尾(Tail),集合V-U 中顶点都充当了 边头(Head)
. 2.在 closeedge 数组中,其下标索引代表 集合V-U ,即为 边头;
. 3.在 closeedge 数组中,其某个位置中 lowcost = 0 代表 该位置下标索引 已入 集合U,即成为 边尾;
. 4.在 closeedge 数组中,adjVex 代表 集合U 中的顶点 ,即为 边尾;
. 注:在这样规定了 集合U 中顶点只能做 边尾 的前提下,
. 有效的避免了 新入 集合U 的顶点 去指向 集合U 中的原有顶点
*/
if (closeEdge[k].lowCost == 0)
{
k++;
continue; //直到找到第一个 集合V-U 中下标索引
}
if (closeEdge[i].lowCost < closeEdge[k].lowCost && closeEdge[i].lowCost > 0)
{
k = i;
}
}
//2.2.向最小生成树(图)中插入此顶点、最小代价边,并输出边信息
mst->InsertVertex(new string(this->vexs[k]));
mst->InsertArc(new ArcData{ this->vexs[closeEdge[k].adjVex], this->vexs[k], closeEdge[k].lowCost });
cout << "k:" << k << " arc: " << this->vexs[closeEdge[k].adjVex] << " -> " << this->vexs[k] << " : " << closeEdge[k].lowCost << endl;
//2.3.将 k位置顶点 加入到 集合U,用 0 标记
closeEdge[k].lowCost = 0;
//2.4.将其所有代价最小边加入到 closeEdge
for (int i = 0; i < this->_MAX_VERTEX_NUM; i++)
{
/*
. 这一判断其实包含了两层含义
. ** 解析:
. 1.有个前提:所有边的权重都大于 0,这样 lowcost = 0 的位置(已入 集合U ,
. 其不能再做 边头,即排除了属于 集合U 内部的 'k -> i' 的这条边
. 2.对于 集合U 指向 集合V-U 的边中,存在这样的边:'k1 -> i' 和 'k2 -> i',只保留权值小的那条边
*/
if (this->arcs[k][i].adj < closeEdge[i].lowCost)
{
closeEdge[i].adjVex = k;
closeEdge[i].lowCost = this->arcs[k][i].adj;
}
}
}
//3.返回 mst
return mst;
}
//文件名:"MST_PRIM_Test.cpp"
#include "stdafx.h"
#include <iostream>
#include "GraphAdjMat.h"
#include "ObjArrayList.h"
using namespace std;
int main()
{
//初始化顶点数据
string * v1 = new string("v1");
string * v2 = new string("v2");
string * v3 = new string("v3");
string * v4 = new string("v4");
string * v5 = new string("v5");
string * v6 = new string("v6");
string * v7 = new string("v7");
ObjArrayList<string> * vexs = new ObjArrayList<string>();
vexs->Add(v1);
vexs->Add(v2);
vexs->Add(v3);
vexs->Add(v4);
vexs->Add(v5);
vexs->Add(v6);
vexs->Add(v7);
//初始化边(弧)数据
GraphAdjMat::ArcData * arc0 = new GraphAdjMat::ArcData{ "v1", "v2", 18 };
GraphAdjMat::ArcData * arc1 = new GraphAdjMat::ArcData{ "v1", "v7", 23 };
GraphAdjMat::ArcData * arc2 = new GraphAdjMat::ArcData{ "v1", "v6", 6 };
GraphAdjMat::ArcData * arc3 = new GraphAdjMat::ArcData{ "v1", "v5", 4 };
GraphAdjMat::ArcData * arc4 = new GraphAdjMat::ArcData{ "v2", "v7", 12 };
GraphAdjMat::ArcData * arc5 = new GraphAdjMat::ArcData{ "v2", "v3", 5 };
GraphAdjMat::ArcData * arc6 = new GraphAdjMat::ArcData{ "v2", "v4", 8 };
GraphAdjMat::ArcData * arc7 = new GraphAdjMat::ArcData{ "v3", "v4", 10 };
GraphAdjMat::ArcData * arc8 = new GraphAdjMat::ArcData{ "v4", "v7", 15 };
GraphAdjMat::ArcData * arc9 = new GraphAdjMat::ArcData{ "v4", "v5", 20 };
GraphAdjMat::ArcData * arc10 = new GraphAdjMat::ArcData{ "v5", "v7", 25 };
GraphAdjMat::ArcData * arc11 = new GraphAdjMat::ArcData{ "v5", "v6", 7 };
ObjArrayList<GraphAdjMat::ArcData> * arcsList = new ObjArrayList<GraphAdjMat::ArcData>();
arcsList->Add(arc0);
arcsList->Add(arc1);
arcsList->Add(arc2);
arcsList->Add(arc3);
arcsList->Add(arc4);
arcsList->Add(arc5);
arcsList->Add(arc6);
arcsList->Add(arc7);
arcsList->Add(arc8);
arcsList->Add(arc9);
arcsList->Add(arc10);
arcsList->Add(arc11);
//测试:无向网最小生成树
cout << endl << "无向网初始化:";
GraphAdjMat * udn = new GraphAdjMat(GraphAdjMat::UDN);
udn->Init(vexs, arcsList);
udn->Display();
cout << endl << "最小生成树构造过程:(从 v1 顶点起)" << endl;
GraphAdjMat * mst = udn->MiniSpanTree_Prim(v1);
cout << endl << "最小生成树显示:(从 v1 顶点起)";
mst->Display();
return 0;
}
四、输出结果