prim 算法 c++实现

1.概述

  设G =(V,E)是无向连通带权图,即一个网络。E中每条边(v,w)的权为c[v][w]。如果G的子图G’是一棵包含G的所有顶点的树,则称G’为G的生成树。生成树上各边权的总和称为该生成树的耗费。在G的所有生成树中,耗费最小的生成树称为G的最小生成树

     网络的最小生成树在实际中有广泛应用。例如,在设计通信网络时,用图的顶点表示城市,用边(v,w)的权c[v][w]表示建立城市v和城市w之间的通信线路所需的费用,则最小生成树就给出了建立通信网络的最经济的方案。

  最小生成树性质:

   设G=(V,E)是连通带权图,U是V的真子集。如果(u,v)属于E,且u属于U,v属于V-U,且在所有这样的边中,(u,v)的权c[u][v]最小,那么一定存在G的一棵最小生成树,它以(u,v)为其中一条边。这个性质有时也称为MST性质。

 

2.PRIM算法

  设G=(V,E)是连通带权图,V={1,2,…,n}。

  构造G的最小生成树的Prim算法的基本思想是:首先置S={1},然后,只要S是V的真子集,就作如下的贪心选择:选取满足条件i属于S,j属于V-S,且c[i][j]最小的边,将顶点j添加到S中。这个过程一直进行到S=V时为止。

  在这个过程中选取到的所有边恰好构成G的一棵最小生成树。

  利用最小生成树性质和数学归纳法容易证明,上述算法中的边集合T始终包含G的某棵最小生成树中的边。因此,在算法结束时,T中的所有边构成G的一棵最小生成树。

  例如,对于右图中的带权图,按Prim算法选取边的过程如下页图所示。  

按Prim算法选取边的过程如下页图所示。

  在上述Prim算法中,还应当考虑如何有效地找出满足条件i在S中,j也在V-S中,且权c[i][j]最小的边(i,j)。实现这个目的的较简单的办法是设置2个数组closest和lowcost。

  在Prim算法执行过程中,先找出V-S中使lowcost值最小的顶点j,然后根据数组closest选取边(j,closest[j]),最后将j添加到S中,并对closest和lowcost作必要的修改。

  用这个办法实现的Prim算法所需的计算时间为O(n2).

       其实这个算法和迪杰斯特拉算法非常相似,都是每次都要取目前已知的到达未连通结点的最小权值,当加入该点之后再刷新lowcost数组和closest数组,对比看是否未加入树的点通过该刚加入树的点是否有更短的路径也就是更小的权值,若有则刷新,若没有则不处理,以保证lowcost和closest数组里的数据都是根据当前已知情况得到的最小权值数组和该顶点最接近的顶点编号。

图是搬运的,因为自己懒得做。

代码如下:

#include<iostream>
#include<vector>
using namespace std;
void prim(vector<vector<int>> &VGraph, vector<int> &lowcost, vector<int> &closest, vector<bool> &visited)  //思路类似于迪杰斯特拉算法
{
	int size = lowcost.size();
	visited[0] = true;
	for (int i = 1; i < size; i++)
	{
		lowcost[i] = VGraph[0][i];
		closest[i] = 0;
		visited[i] = false;
	}
	cout << "0";
	int weight = 0;
	for (int i = 0; i < size; i++)
	{
		int min = 99999;
		int index = 1;
		for (int j = 0; j < size; j++)
		{
			if (lowcost[j] < min&&!visited[j])
			{
				min = lowcost[j];
				index = j;
			}
		}
		if (index == 1 && min == 99999)
		{
			cout << "\n最小生成树权值为:" << weight<<endl;
			return;
		}
		else
		{
			weight += min;
		}
		cout << " -> " << index;
		visited[index] = true;
		for (int j = 1; j <size; j++)  //因为新加入了j点,所以要查找新加入的j点到未在S中的点K中的权值是不是可以因此更小
		{
			if ((VGraph[index][j]<lowcost[j]) && (!visited[j]))  //lowcost表示从已知树到某一点所需付出的最小权值
			{ 
				lowcost[j] = VGraph[index][j];
				closest[j] = index;
			}
		}
	}
}
int main()
{
	int M, N;
	cin >>M>>N;
	vector<vector<int>> VGraph(M);
	vector<int> lowcost(M);
	vector<int> closest(M);
	vector<bool> visited(M);
	for (int i = 0; i < M; i++)
	{
		VGraph[i].resize(M);
	}
	for (int i = 0; i < M; i++)
	{
		for (int j = 0; j < M; j++)
		{
			VGraph[i][j] = 99999;
		}
	}
	for (int i = 0; i < N; i++)
	{
		int a, b;
		cin >> a >> b;
		int length;
		cin >> length;
		VGraph[a][b] = VGraph[b][a] = length;
	}
	prim(VGraph, lowcost, closest, visited);
}

 

以下是Prim算法C++实现: ```cpp #include <iostream> #include <vector> #include <queue> #include <cstring> using namespace std; const int MAXN = 1001; // 最大点数 const int INF = 0x3f3f3f3f; // 无穷大 struct Edge { int to; // 边的终点 int w; // 边的权值 Edge(int _to, int _w): to(_to), w(_w) {} }; vector<Edge> adj[MAXN]; // 邻接表存图 int dist[MAXN]; // 记录每个点到当前生成树的距离 bool vis[MAXN]; // 标记每个点是否已经加入生成树 int Prim(int s) { memset(dist, INF, sizeof(dist)); // 初始化距离为无穷大 memset(vis, false, sizeof(vis)); // 初始化所有点都未加入生成树 priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > pq; // 小根堆,存储 (点到生成树的最短距离, 点的编号) 的二元组 dist[s] = 0; pq.push(make_pair(0, s)); // 将起点加入堆中 int ans = 0; // 记录生成树的总权值 while (!pq.empty()) { int u = pq.top().second; pq.pop(); if (vis[u]) continue; // 如果该点已经加入生成树,则跳过 vis[u] = true; // 将该点加入生成树 ans += dist[u]; // 更新生成树的总权值 for (unsigned int i = 0; i < adj[u].size(); ++i) { int v = adj[u][i].to; int w = adj[u][i].w; if (!vis[v] && w < dist[v]) { dist[v] = w; pq.push(make_pair(dist[v], v)); // 将 v 加入堆中 } } } return ans; } int main() { int n, m; cin >> n >> m; // 输入点数和边数 for (int i = 0; i < m; ++i) { int u, v, w; cin >> u >> v >> w; // 输入一条边的起点、终点和权值 adj[u].push_back(Edge(v, w)); adj[v].push_back(Edge(u, w)); // 无向图 } cout << Prim(1) << endl; // 从第一个点开始生成最小生成树 return 0; } ``` 该算法的时间复杂度为O(mlogn),其中n为点数,m为边数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值