python机器学习案例系列教程——最小生成树(MST)的Prim算法和Kruskal算法

全栈工程师开发手册 (作者:栾鹏)
python数据挖掘系列教程

最小生成树MST

一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。

也就是说,用原图中有的边,连接n个节点,保证每个节点都被连接,且使用的边的数目最少。

最小权重生成树

在一给定的无向图$G = (V, E) 中 , 中, (u, v) 代 表 连 接 顶 点 代表连接顶点 u $与顶点 $v $的边(即),而 $w(u, v) $代表此边的权重,若存在 $T 为 为 E $的子集(即)且为无循环图,使得

w ( t ) = ∑ ( u , v ) ∈ t w ( u , v ) w(t)=\sum_{(u,v)\in t}w(u,v) w(t)=(u,v)tw(u,v)

的$ w(T) $最小,则此 T T T G G G 的最小生成树。

最小生成树其实是最小权重生成树的简称。

应用:

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

Prim算法

1)、输入:一个加权连通图,其中顶点集合为 V V V,边集合为 E E E

2)、初始化: V n e w = { x } V_{new}= \{x\} Vnew={x},其中 x x x为集合 V V V中的任一节点(起始点), E n e w = { } E_{new}= \{\} Enew={},为空;

3)、重复下列操作,直到 V n e w = V V_{new}= V Vnew=V

  • a.在集合 E E E中选取权值最小的边 < u , v > <u, v> <u,v>,其中 u u u为集合 V n e w V_{new} Vnew中的元素,而 v v v不在 V n e w V_{new} Vnew集合当中,并且 v ∈ V v∈V vV(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);

  • b.将 v v v加入集合 V n e w V_{new} Vnew中,将 < u , v > <u, v> <u,v>边加入集合 E n e w E_{new} Enew中;

4)、输出:使用集合 V n e w V_{new} Vnew E n e w E_{new} Enew来描述所得到的最小生成树

Kruskal算法简述

假设 $W_N=(V,{E}) $是一个含有n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集 E E E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。

循环中可加入已加入MST的点的数量的判断,有可能提前结束循环,提高效率。

下面是hdu1233的源代码,一个用Prim算法,另一个用Kruskal,标准的MST问题。

#include <cstdio>
#include <algorithm>
using namespace std;

typedef int weight_t; 

#define SIZE 101

int N;

//图的邻接矩阵
weight_t Graph[SIZE][SIZE];

//各顶点到中间结果的最短距离,始终维护
weight_t D[SIZE];

//标志位
bool Flag[SIZE];

//Prim算法,返回MST的长度
weight_t Prim(){
	//初始化数组
	fill(D,D+SIZE,INT_MAX);
	fill(Flag,Flag+SIZE,false);

	//初始化第一个计算的点
	D[1] = 0;

	weight_t ans = 0;

	for(int i=1;i<=N;++i){
		//找出距离中间结果最近的点
		int k = -1;
		for(int j=1;j<=N;++j)
			if ( !Flag[j] && ( -1 == k || D[j] < D[k] ) )
				k = j;

		//将k点加入中间结果
		Flag[k] = true;
		ans += D[k];

		//更新剩余点到中间结果的最短距离
		for(int j=1;j<=N;++j)
			if ( !Flag[j] && Graph[k][j] < D[j] )
				D[j] = Graph[k][j];
	}

	return ans;
}

bool read(){
	scanf("%d",&N);
	if ( 0 == N ) return false;
	
	for(int i=0;i<N*(N-1)/2;++i){
		int a,b,w;
		scanf("%d%d%d",&a,&b,&w);
		Graph[a][b] = Graph[b][a] = w;
	}
	
	return true;
}

int main(){
	while( read() ){
		printf("%d\n",Prim());
	}
	return 0;
}
#include <cstdio>
#include <algorithm>
using namespace std;

typedef int weight_t; 

#define SIZE 101

//并查集结构
int Father[SIZE];
void init(int n){for(int i=0;i<=n;Father[i]=i++);}
int find(int x){return Father[x]==x?x:Father[x]=find(Father[x]);}
void unite(int x,int y){Father[find(y)]=Father[find(x)];}

int N;

//边结构
struct edge_t{
	int s;
	int e;
	weight_t w;
}Edge[SIZE*SIZE/2];
int ECnt = 0;

//重载,用于边排序
bool operator < (edge_t const&lhs,edge_t const&rhs){
	if ( lhs.w != rhs.w ) return lhs.w < rhs.w;
	if ( lhs.s != rhs.s ) return lhs.s < rhs.s;
	return lhs.e < rhs.e;
}

//生成边
inline void mkEdge(int a,int b,weight_t w){
	if ( a > b ) swap(a,b);

	Edge[ECnt].s = a;
	Edge[ECnt].e = b;
	Edge[ECnt++].w = w;
}

//Kruskal算法,vn是点的数量,en是边的数量,返回MST的长度
weight_t Kruskal(int vn,int en){
	init(vn);//并查集初始化
	sort(Edge,Edge+en);//边排序

	weight_t ans = 0;
	for(int i=0;i<en;++i){
		//该边已存在于MST中
		if ( find(Edge[i].s) == find(Edge[i].e) )
			continue;

		//将该边加入MST
		ans += Edge[i].w;
		unite(Edge[i].s,Edge[i].e);
		--vn;

		//MST已完全生成
		if ( 1 == vn ) break;
	}

	return ans;
}

bool read(){
	scanf("%d",&N);
	if ( 0 == N ) return false;
	
	ECnt = 0;
	for(int i=0;i<N*(N-1)/2;++i){
		int a,b,w;
		scanf("%d%d%d",&a,&b,&w);
		mkEdge(a,b,w);
	}
	
	return true;
}

int main(){
	while( read() ){
		printf("%d\n",Kruskal(N,ECnt));
	}
	return 0;
}
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

腾讯AI架构师

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

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

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

打赏作者

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

抵扣说明:

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

余额充值