用邻接表存储,并按Kruskal算法求最小生成树 实验
3.1 实验内容
根据书P262习题3给定的无向带权图,用邻接表作为存储结构,用kruskal算法构造其最小生成树。
克鲁斯卡尔算法的基本思想是:设一个有n个顶点的连通网络G={V,E},先构造一个包括全部n个顶点和0条边的森林F={T0,T1,…,Tn-1},以后每一步向F中加入一条边(v, u),它应是所依附的两个顶点v和u分别在森林F的两棵不同的树上的所有边中具有最小权值的边。由于这条边的加入,使F中的某两棵树合并为一棵,树的棵数减一。如此,经过n-1步,最终得到一棵有n-1条边且各边权值总和达到最小的生成树--最小生成树。
3.2文件结构、开发环境等说明
开发环境版本 | VisualStudio 2022 | 工程文件名 | ex2_2_prim.sln |
头文件个数 | 7个 | 源程序文件个数 | 1个 |
文件名 | 文件类型 | 功能简介 | 备注 |
AdjListDirNetwork.h | 头文件 | 邻接表类模板头文件,包括数据成员及成员函数的声明与定义 | |
AdjListDirNetworkArc.h | 头文件 | 邻接表边结点类模板头文件,包括数据成员及成员函数的声明与定义 | |
AdjListDirNetworkVec.h | 头文件 | 邻接表顶点结点类模板头文件,包括数据成员及成员函数的声明与定义 | |
Kruskal.h | 头文件 | Kruskal算法实现最小生成树的构造 | |
Assistance.h | 头文件 | 辅助软件包 | |
MineHeap.h | 头文件 | 最小堆类模板头文件,包括数据成员及成员函数的声明与定义 | |
UFSets.h | 头文件 | 并查集类模板头文件,包括数据成员及成员函数的声明与定义 | |
text.cpp | 源文件 | 测试文件 |
3.3实现技术
1、邻接表作为存储结构
通过三个头文件“AdjListDirNetwork.h”、“AdjListDirNetworkArc.h”、“AdjListDirNetworkVec.h”,用邻接表类、邻接表边结点类、邻接表顶点结点类将图用邻接表的存储结构表示(这里不展示相关代码)。
2、最小堆存放连通网络中的边
在克鲁斯卡尔算法中,利用最小堆来存放连通网络中的边,堆中每个元素代表连通网络中的一条边(这里不做堆类的代码展示)
3、并查集存放连通分量
在利用并查集存放所有连通分量,同一个连通分量的顶点组成并查集的一个子集(等价类)。(这里不做并查集类的代码展示)
4、Kruskal算法
为了在堆中存放边的信息,并在堆调整时进行边上权值比较,下面先给出Kruskal算法中边类声明和实现。在这个边类中定义了一条边依附的两个顶点vertexl、vertex2以及边的权weight。另外,除了构造函数外,重载了赋值运算、<=和>两个关系运算。
template<class ElemType,class WeightType>
class KruskalEdge
{
public:
ElemType vertex1, vertex2;
WeightType weight;
KruskalEdge(ElemType v1, ElemType v2, WeightType w);
KruskalEdge() { vertex1 = 0; vertex2 = 0; weight = 0; };
KruskalEdge<ElemType, WeightType>& operator=(const KruskalEdge<ElemType, WeightType>& Ed);//赋值语句重载
bool operator<=(const KruskalEdge<ElemType, WeightType>& Ed);
bool operator>=(const KruskalEdge<ElemType, WeightType>& Ed);
bool operator>(const KruskalEdge<ElemType, WeightType>& Ed);
bool operator<(const KruskalEdge<ElemType, WeightType>& Ed);
};
克鲁斯卡尔算法步骤如下。
1)初始化,在并查集中,连通网络的每一个顶点独立成一个等价类,连通网络的所有的边建立最小堆,最小生成树T中没有任何边,T中边的条数计数器i为0。
2)如果T中边的条数计数器i等于顶点数减1,则算法结束;否则继续步骤3)。
3)选取堆顶元素代表的边(v,u),同时调整堆。
4)利用并查集的运算检查依附于边(vu)的两个顶点v和u是否在同一个连通分量(即并查集的同一个子集合)上,如果是则转步骤2);否则继续步骤5)。
5)将边(v,u)加入到最小生成树T中,同时将这两个顶点所在的连通分量合并成一个连通分量(即并查集中的相应两个子集合并成一个子集),继续步骤2)。
template <class ElemType, class WeightType>
void MiniSpanTreeKruskal(const AdjListDirNetwork<ElemType, WeightType>& g)
{
int count, VexNum = g.GetVexNum();
KruskalEdge<ElemType, WeightType> KEdge;
MineHeap<KruskalEdge<ElemType, WeightType> > ha(g.GetEdgeNum());
ElemType* kVex, v1, v2;
kVex = new ElemType[VexNum]; // 定义顶点数组,存储顶点信息
for (int i = 0; i < VexNum; i++)
g.GetElem(i, kVex[i]);
UFSets<ElemType> f(kVex, VexNum);// 根据顶点数组构造并查集
for (int v = 0; v < VexNum; v++)
for (int u = g.FirstAdjVex(v); u >= 0; u = g.NextAdjVex(v, u))
if (v < u) { // 将v < u的边插入到最小堆
g.GetElem(v, v1);
g.GetElem(u, v2);
KEdge.vertex1 = v1;
KEdge.vertex2 = v2;
KEdge.weight = g.GetWeight(v, u);
ha.Insert(KEdge);
}
count = 0; // 表示已经挑选的边数
while (count < VexNum - 1) {
ha.DeleteTop(KEdge); // 从堆顶取一条边
v1 = KEdge.vertex1;
v2 = KEdge.vertex2;
if (f.Differ(v1, v2)) { // 边所依附的两顶点不在同一棵树上
cout << "边:( " << v1 << ", " << v2 << " ) 权:" << KEdge.weight << endl; // 输出边及权值
f.Union(v1, v2); // 将两个顶点所在的树合并成一棵树
count++;
}
}
}
5、测试代码
调用MiniSpanTreeKruskal(net)函数,生成最小生成树(完整代码在资源区)
cout << "Kruskal算法产生最小生成树的边:" << endl;
MiniSpanTreeKruskal(net);
cout << endl;
3.4测试结果
1、测试实例
2、邻接表作为存储结构
3、Kruskal算法
完整代码可看资源区
创作不易~麻烦点个赞~~谢谢大家~~~