python机器学习案例系列教程——最小生成树(MST)的Prim算法和Kruskal算法
原创
©著作权归作者所有:来自51CTO博客作者wx636261b2d66e0的原创作品,请联系作者获取转载授权,否则将追究法律责任
全栈工程师开发手册 (作者:栾鹏)
python数据挖掘系列教程
最小生成树MST
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。
也就是说,用原图中有的边,连接n个节点,保证每个节点都被连接,且使用的边的数目最少。
最小权重生成树
在一给定的无向图$G = (V, E)
(u, v)
u $与顶点 $v $的边(即),而 $w(u, v) $代表此边的权重,若存在 $T
的$ w(T) $最小,则此
为
最小生成树其实是最小权重生成树的简称。
应用:
例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。这就需要找到带权的最小生成树
Prim算法
1)、输入:一个加权连通图,其中顶点集合为
,边集合为
;
2)、初始化:
,其中
为集合
中的任一节点(起始点),
,为空;
3)、重复下列操作,直到
:
- a.在集合
- 中选取权值最小的边
- ,其中
- 为集合
- 中的元素,而
- 不在
- 集合当中,并且
- (如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
- b.将
- 加入集合
- 中,将
- 边加入集合
- 中;
4)、输出:使用集合
和
来描述所得到的最小生成树
Kruskal算法简述
假设 $W_N=(V,{E}) $是一个含有n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集
循环中可加入已加入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;
}