【数据结构/C语言版】【图】最小生成树 Prime + Kruskal

生成树和最小生成树有许多重要的应用
例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树。

什么是生成树?
如果一个无向连通图不包含回路(连通图中不存在环),
那么就是一个树。

什么是最小生成树?
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。
最小生成树其实是最小权重生成树的简称。

Prime 普里姆算法

Prim算法每次循环都将一个蓝点(未访问点)u变为白点(已经访问点),并且此蓝点u与白点相连的最小边权min[u]还是当前所有蓝点中最小的。这样相当于向生成树中添加了n-1次最小的边,最后得到的一定是最小生成树。
Prime算法的**时间复杂度为o(n^2)**适合处理稠密图。

测试数据

3
6 10
v1 v2 v3 v4 v5 v6
v1 v2 6
v1 v3 1
v1 v4 5
v2 v3 5
v2 v5 3
v3 v5 6
v3 v6 4
v3 v4 5
v4 v6 2
v5 v6 6

测试输出

v1—v3
v3—v6
v6—v4
v3—v2
v2—v5

代码

#include<iostream>
#include<climits>
#include<string>
#include<cstdlib> 
using namespace std;
#define ERROR -1

#define MAX_VERTEX_NUM 20 //最多顶点个数 
#define INFINITY INT_MAX //最大值 
typedef enum { DG, DN, UDG, UDN } GraphKind; //有向图、有向网、无向图、无向网
typedef int VRType, Status;
typedef string VertexType;

typedef struct ArcCell {
	VRType adj; //VRType是顶点关系类型。对无权图用0
				//表示相邻否;对带权图则为权值类型
}ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM];

typedef struct {
	VertexType vexs[MAX_VERTEX_NUM]; //顶点向量 
	AdjMatrix arcs; //邻接矩阵 
	int vexnum, arcnum; //图的当前顶点数和弧数 
	int kind;
}MGraph;

//根据映射定位下标 
int LocateVex(MGraph G, VertexType v)
{
	for (int i = 0; i < G.vexnum; i++) {
		if (v == G.vexs[i]) return i;
	}
	return ERROR;
}

Status CreateDG(MGraph& G)//有向图 
{
	cout << "请输入顶点数和弧数:\n";
	cin >> G.vexnum >> G.arcnum;
	for (int i = 0; i < G.vexnum; i++) {
		cout << "请输入向量信息:\n";
		cin >> G.vexs[i];
	}

	for (int i = 0; i < G.vexnum; i++) {
		for (int j = 0; j < G.vexnum; j++) {
			G.arcs[i][j].adj = INFINITY;
		}
	}

	VertexType v, u; // 出发点 目标点 
	for (int k = 0; k < G.arcnum; k++) {
		cout << "请输入第" << k + 1 << "条弧的信息(v,u):\n";
		cin >> v >> u;
		int i = LocateVex(G, v);
		int j = LocateVex(G, u);
		G.arcs[i][j].adj = 1;
	}
	return 1;
}

Status CreateDN(MGraph& G)//有向网 
{
	cout << "请输入顶点数和弧数:\n";
	cin >> G.vexnum >> G.arcnum;
	for (int i = 0; i < G.vexnum; i++) {
		cout << "请输入向量信息:\n";
		cin >> G.vexs[i];
	}

	for (int i = 0; i < G.vexnum; i++) {
		for (int j = 0; j < G.vexnum; j++) {
			G.arcs[i][j].adj = INFINITY;
		}
	}

	VertexType v, u; // 出发点 目标点 
	for (int k = 0; k < G.arcnum; k++) {
		cout << "请输入第" << k + 1 << "条弧的信息(v,u):\n";
		cin >> v >> u;
		int i = LocateVex(G, v);
		int j = LocateVex(G, u);
		cout << "请输入包含的额外信息(边权):\n";
		cin >> G.arcs[i][j].adj;
	}
	return 1;
}

Status CreateUDG(MGraph& G)//无向图 
{
	cout << "请输入顶点数和弧数:\n";
	cin >> G.vexnum >> G.arcnum;
	for (int i = 0; i < G.vexnum; i++) {
		cout << "请输入向量信息:\n";
		cin >> G.vexs[i];
	}

	for (int i = 0; i < G.vexnum; i++) {
		for (int j = 0; j < G.vexnum; j++) {
			G.arcs[i][j].adj = INFINITY;
		}
	}

	VertexType v, u; // 出发点 目标点 
	for (int k = 0; k < G.arcnum; k++) {
		cout << "请输入第" << k + 1 << "条弧的信息(v,u):\n";
		cin >> v >> u;
		int i = LocateVex(G, v);
		int j = LocateVex(G, u);
		G.arcs[i][j].adj = G.arcs[j][i].adj = 1;
	}
	return 1;
}

Status CreateUDN(MGraph& G)//无向网 
{
	cout << "请输入顶点数和弧数:\n";
	cin >> G.vexnum >> G.arcnum;
	for (int i = 0; i < G.vexnum; i++) {
		cout << "请输入第" << i + 1 << "向量信息:\n";
		cin >> G.vexs[i];
	}

	for (int i = 0; i < G.vexnum; i++) {
		for (int j = 0; j < G.vexnum; j++) {
			G.arcs[i][j].adj = INFINITY;
		}
	}

	VertexType v, u; // 出发点 目标点 
	int w; // 权重 
	for (int k = 0; k < G.arcnum; k++) {
		cout << "请输入第" << k + 1 << "条弧的信息(v,u):\n";
		cin >> v >> u;
		int i = LocateVex(G, v);
		int j = LocateVex(G, u);
		cout << "请输入包含的额外信息(边权):\n";
		cin >> G.arcs[i][j].adj;
		G.arcs[j][i].adj = G.arcs[i][j].adj;
	}
	return 1;
}

Status CreateGraph(MGraph& G)
{
	cin >> G.kind;
	switch (G.kind)
	{
	case DG:return CreateDG(G);
	case DN:return CreateDN(G);
	case UDG:return CreateUDG(G);
	case UDN:return CreateUDN(G);
	default: return ERROR;
	}
}//CreateGraph

void TraveGraph(MGraph G)
{
	for (int i = 0; i < G.vexnum; i++) {
		for (int j = 0; j < G.vexnum; j++) {
			cout << G.arcs[i][j].adj << ' ';
		}
		cout << '\n';
	}
}

void Prime(MGraph G, VertexType u)
{
	struct closedge{
		VertexType adjvex;
		VRType lowcost;
	}closedge[MAX_VERTEX_NUM];
	int k = LocateVex(G, u);
	//辅助数组初始化
	for (int i = 0; i < G.vexnum; i++) {
		if (i != k) closedge[i] = { u,G.arcs[k][i].adj };
	}
	//初始
	closedge[k].lowcost = 0;
	for (int i = 1; i < G.vexnum; i++)//选择其余G.vexnum - 1个顶点
	{
		k = -1;
		for (int j = 0; j < G.vexnum; j++) {//求出T的下一个结点:第k顶点
			if (closedge[j].lowcost > 0 && (k == -1 || closedge[j].lowcost < closedge[k].lowcost)) k = j;
		}
		//输出生成树的边
		cout << closedge[k].adjvex << "—" << G.vexs[k] << endl;
		closedge[k].lowcost = 0;//第k顶点并入U集
		for (int j = 0; j < G.vexnum; j++) {
			if(G.arcs[k][j].adj<closedge[j].lowcost)
				closedge[j] = { G.vexs[k],G.arcs[k][j].adj };
		}
	}
}

int main()
{
	MGraph G;
	cout << "请输入要创建的图的类型(有向图 0、有向网 1、无向图 2、无向网 3):\n";
	CreateGraph(G);
	//TraveGraph(G);
	Prime(G, "v1");
	return 0;
}

Kruskal 克鲁斯卡尔算法

Kruskal(克鲁斯卡尔)算法是一种巧妙利用并查集贪心思想来求最小生成树的算法。平均时间复杂度为o(mlog m)

Kruskal首先将所有的边按从小到大顺序排序,并认为每一个点都是孤立的,分属于n个独立的集合。然后按顺序枚举每一条边。如果这条边连接着两个不同的集合,那么就把这条边加入最小生成树,这两个不同的集合就合并成了一个集合;如果这条边连接的两个点属于同一集合,就跳过。直到选取了n-1条边为止。
由于克鲁斯卡尔贪心思想,其生成树有特殊性质最大边权最小的生成树。

测试数据

6 10
1 2 6
1 3 1
1 4 5
2 3 5
2 5 3
3 5 6
3 6 4
3 4 5
4 6 2
5 6 6

测试输出

13
46
25
36
23

代码

#include<iostream>
#include<algorithm>
using namespace std;
#define MAX_VERTEX_NUM 20 //最多顶点个数
#define MAX_ARCNUM_NUM 20 //最多边个数 

int fa[MAX_VERTEX_NUM];

//存储边
struct node {
	int u, v, w;
	bool operator<(const node& t)const {
		return this->w < t.w;
	}
}edge[MAX_ARCNUM_NUM];

//并查集模板
int find_fa(int x)
{
	return fa[x] == x ? x : find_fa(fa[x]);
}

void init(int n)
{
	for (int i = 1; i <= n; i++) fa[i] = i;
}

void _union(int x, int y)
{
	int root_x = find_fa(x), root_y = find_fa(y);
	if (root_x != root_y)
		fa[root_x] = root_y;
}

int main()
{
	//顶点数 边数
	int vexnum, arcnum;
	cin >> vexnum >> arcnum;
	init(vexnum);
	for (int i = 0; i < arcnum; i++) {
		int u, v, w;
		cin >> u >> v >> w;
		edge[i] = { u,v,w };
	}
	//贪心:优先选取边权小的加入同一集合
	sort(edge, edge + arcnum);
	//vexnum个顶点的最小生成树最多有vexnum条边
	int ans = vexnum - 1;
	for (int i = 0; i < arcnum && ans; i++)
	{
		int root_u = find_fa(edge[i].u);
		int root_v = find_fa(edge[i].v);
		//在同一集合中退出!
		if (root_u == root_v) continue;
		//不在同一集合中合并!
		fa[root_u] = root_v;
		cout << edge[i].u << "—" << edge[i].v << endl;
		ans--;
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sophon、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值