数据结构之图的应用(最小生成树之Prim算法实现)(C++)

最小代价生成树的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;
}

四、输出结果 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值