Prim算法简易教程(~简单易懂,附最详细注释代码)

1 最小生成树(Minimum Spanning Tree,MST)

在一给定的无向图 G = ( V , E ) G = (V, E) G=(V,E) 中, ( u , v ) (u, v) (u,v)代表连接顶点 u u u 与顶点 v v v 的边,而 w ( u , v ) w(u, v) w(u,v) 代表此边的权重,若存在 T T T E E E 的子集且为无循环图,使得 w ( T ) w(T) w(T) 最小,则此 T T T G G G 的最小生成树,因为 T T T是由图 G G G产生的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QXLE9kV6-1597109054856)(C:\Users\剑殇\AppData\Roaming\Typora\typora-user-images\image-20200802101000918.png)]

最小生成树其实是最小权重生成树的简称。我们称求取该生成树的问题成为最小生成树问题。一个连通图可能有多个生成树。当图中的边具有权值时,总会有一个生成树的边的权值之和小于或者等于其它生成树的边的权值之和。广义上而言,对于非连通无向图来说,它的每一连通分量同样有最小生成树,它们的并被称为最小生成森林。以有线电视电缆的架设为例,若只能沿着街道布线,则以街道为边,而路口为顶点,其中必然有一最小生成树能使布线成本最低。

img

如图,这个是一个平面图,图中黑色线描述的就是最小生成树,它的权值之和小于其他的生成树。

那么,我们如何来求最小生成树呢,由最小生成树的定义我们可以知道构建最小生成树是可以利用贪心算法去实现的,我们接下来介绍的两种算法也都是利用贪心算法去求得 M S T MST MST的。因为贪心算法的策略就是在每一步尽可能多的选择中选择最优的,在当前看是最好的选择,这种策略虽然一般不能在全局中寻找到最优解,但是对于最小生成树问题来说,它的确可以找到一颗权重最小的树。

2 Prim算法

2.1 简介

普里姆算法(Prim’s algorithm),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。该算法于1930年由捷克数学家沃伊捷赫·亚尔尼克发现;并在1957年由美国计算机科学家罗伯特·普里姆独立发现;1959年,艾兹格·迪科斯彻再次发现了该算法。因此,在某些场合,普里姆算法又被称为DJP算法、亚尔尼克算法或普里姆-亚尔尼克算法。(来源于维基百科)

2.2 具体步骤

Prim算法是利用贪心算法实现的,我们确定根节点,从这个结点出发。普里姆算法按照以下步骤逐步扩大树中所含顶点的数目,直到遍及连通图的所有顶点。下面描述我们假设 N = ( V , E ) N=(V,E) N=(V,E)是连通网, T E TE TE N N N上最小生成树中边的集合

​ ① U = { u 0 } ( u 0 ∈ V ) , T E = { } U=\{u_0\}(u_0∈V) ,TE= \{\} U={u0}(u0V),TE={}

​ ② 在所有 u ∈ U , v ∈ ( V − U ) u∈U,v∈(V-U) uU,v(VU)的边 ( u , v ) ∈ E (u,v)∈E (u,v)E找到一条权值最小的边 ( u 0 , v 0 ) (u_0,v_0) (u0,v0)并入集合 T E TE TE,同时 v 0 v_0 v0并入集合 U U U

​ ③ 重复②步骤,知道 U = V U=V U=V为止。

此时 T E TE TE中必有 n − 1 n-1 n1条边,则 T = ( V , T E ) T=(V,TE) T=(V,TE)即为我们求得的 N N N的最小生成树。

2.3 算法示例图

在这里插入图片描述

2.4 算法实现

我们如果对Dijkstra算法很熟悉的话,Prim算法也很好实现了,它们都是利用了一样的思路,但却不相同。我们用利用 l o w c o s t lowcost lowcost数组来表示到集合中最近的距离,用 c l o s e s t closest closest数组来表示最小生成树的边。怎么来表示呢?我们用顶点来形容边,也就是说我们要求的就是 c l o s e t closet closet数组。其中 c l o s e s t [ i ] closest[i] closest[i]表示的值就是与 i i i顶点相邻边的顶点序号。具体看代码(附带打印最小生成树代码)。

/*
*邮箱:unique_powerhouse@qq.com
*blog:https://me.csdn.net/hzf0701
*注:文章若有任何问题请私信我或评论区留言,谢谢支持。
*
*/
#include<bits/stdc++.h>	//POJ不支持

#define rep(i,a,n) for (int i=a;i<=n;i++)//i为循环变量,a为初始值,n为界限值,递增
#define per(i,a,n) for (int i=a;i>=n;i--)//i为循环变量, a为初始值,n为界限值,递减。
#define pb push_back
#define IOS ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
#define fi first
#define se second
#define mp make_pair

using namespace std;

const int inf = 0x3f3f3f3f;//无穷大
const int maxn = 1e3;//最大值。
typedef long long ll;
typedef long double ld;
typedef pair<ll, ll>  pll;
typedef pair<int, int> pii;
//*******************************分割线,以上为自定义代码模板***************************************//

int n,m;//图的大小和边数。
int graph[maxn][maxn];//图
int lowcost[maxn],closest[maxn];//lowcost[i]表示i到距离集合最近的距离,closest[i]表示i与之相连边的顶点序号。
int sum;//计算最小生成树的权值总和。
void Prim(int s){
	//初始化操作,获取基本信息。
	rep(i,1,n){
		if(i==s)
			lowcost[i]=0;
		else
			lowcost[i]=graph[s][i];
		closest[i]=s;
	}
	int minn,pos;//距离集合最近的边,pos代表该点的终边下标。
	sum=0;
	rep(i,1,n){
		minn=inf;
		rep(j,1,n){
			//找出距离点集合最近的边。
			if(lowcost[j]!=0&&lowcost[j]<minn){
				minn=lowcost[j];
				pos=j;
			}
		}
		if(minn==inf)break;//说明没有找到。
		sum+=minn;//计算最小生成树的权值之和。
		lowcost[pos]=0;//加入点集合。
		rep(j,1,n){
			//由于点集合中加入了新的点,我们要去更新。
			if(lowcost[j]!=0&&graph[pos][j]<lowcost[j]){
				lowcost[j]=graph[pos][j];
				closest[j]=pos;//改变与顶点j相连的顶点序号。
			}
		}
	}
	cout<<sum<<endl;//closest数组就是我们要的最小生成树。它代表的就是边。
}
void print(int s){
	//打印最小生成树。
	int temp;
	rep(i,1,n){
		//等于s自然不算,故除去这个为n-1条边。
		if(i!=s){
			temp=closest[i];
			cout<<temp<<"->"<<i<<"边权值为:"<<graph[temp][i]<<endl;
		}
	}
}
int main(){
	//freopen("in.txt", "r", stdin);//提交的时候要注释掉
	IOS;
	while(cin>>n>>m){
		memset(graph,inf,sizeof(graph));//初始化。
		int u,v,w;//临时变量。
		rep(i,1,m){
			cin>>u>>v>>w;
			//视情况而论,我这里以无向图为例。
			graph[u][v]=graph[v][u]=w;
		}
		//任取根结点,我这里默认取1.
		Prim(1);
		print(1);//打印最小生成树。
	}
	return 0;
}



2.5 算法分析

对于此算法,我们图中有 n n n个顶点,则第一个进行初始化的循环语句的频度为 n n n,第二个循环语句的频度为 n n n但其中第二个循环中有两个内循环:第一个是在 l o w c o s t lowcost lowcost中求最小值,其频度为 n n n,第二个是重新选择具有最小权值的边,频度为 n n n,由此我们可知Prim算法的时间复杂度为 O ( n 2 ) O(n^2) O(n2),与图中的边数无关,故十分适合于稠密图。

2.6 测试

我们用示例来测试:

7 11
1 2 7
1 4 5
2 4 9
2 3 8
2 5 7
4 5 15
4 6 6
6 7 11
5 6 8
5 7 9
3 5 5

测试结果如图:
在这里插入图片描述

  • 158
    点赞
  • 675
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
Prim算法是一种贪心算法,用于求解加权无向图的最小生成树。从一个节点开始,每次选择一个与当前生成树相邻的最小边,加入生成树中,直到所有节点都被加入生成树。下面是Prim算法的伪代码: ``` 1. 选取一个起始节点u 2. 标记节点u为已访问 3. for v in u的邻居节点: 4. 将(u,v)加入候选边集合中 5. while 候选边集合不为空: 6. 从候选边集合中选择一条最小边(u,v),将v标记为已访问 7. 将(u,v)加入生成树中 8. for w in v的邻居节点: 9. 如果w未被访问过,将(v,w)加入候选边集合中 ``` 下面是一个示例代码,使用邻接矩阵来表示图: ```python import sys def prim(graph): n = len(graph) # 初始化 visited = [False] * n dist = [sys.maxsize] * n parent = [-1] * n dist[0] = 0 for i in range(n): # 找到未访问过的最近的节点 u = -1 for j in range(n): if not visited[j] and (u == -1 or dist[j] < dist[u]): u = j # 标记为已访问 visited[u] = True # 更新与节点u相邻的节点的距离 for v in range(n): if not visited[v] and graph[u][v] != 0 and graph[u][v] < dist[v]: dist[v] = graph[u][v] parent[v] = u return parent # 示例代码 if __name__ == '__main__': graph = [ [0, 2, 0, 6, 0], [2, 0, 3, 8, 5], [0, 3, 0, 0, 7], [6, 8, 0, 0, 9], [0, 5, 7, 9, 0] ] parent = prim(graph) for i in range(1, len(parent)): print("{}-{}".format(parent[i], i)) ``` 输出结果为: ``` 0-1 1-2 0-3 1-4 ``` 其中,输出的每一行表示生成树中的一条边,如“0-1”表示0和1之间有一条边。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

unique_pursuit

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

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

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

打赏作者

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

抵扣说明:

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

余额充值