Some Jupyter Machine Learning Algorithm File 3 of 4 There may be some bugs, please don't mind.
8.0 KiB
最小生成树MST¶
一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。
也就是说,用原图中有的边,连接n个节点,保证每个节点都被连接,且使用的边的数目最少。
最小权重生成树¶
在一给定的无向图$G = (V, E) $中,$(u, v) $代表连接顶点$ u $与顶点 $v $的边(即),而 $w(u, v) $代表此边的权重,若存在 $T $为$ E $的子集(即)且为无循环图,使得
$$w(t)=\sum_{(u,v)\in t}w(u,v)$$
的$ w(T) $最小,则此 $T$ 为 $G$ 的最小生成树。
最小生成树其实是最小权重生成树的简称。
应用:¶
例如:要在n个城市之间铺设光缆,主要目标是要使这 n 个城市的任意两个之间都可以通信,但铺设光缆的费用很高,且各个城市之间铺设光缆的费用不同,因此另一个目标是要使铺设光缆的总费用最低。
这就需要找到带权的最小生成树
Prim算法¶
1)、输入:一个加权连通图,其中顶点集合为$V$,边集合为$E$;
2)、初始化:$V_{new}= \{x\}$,其中$x$为集合$V$中的任一节点(起始点),$E_{new}= \{\}$,为空;
3)、重复下列操作,直到$V_{new}= V$:
a.在集合$E$中选取权值最小的边$<u, v>$,其中$u$为集合$V_{new}$中的元素,而$v$不在$V_{new}$集合当中,并且$v∈V$(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
b.将$v$加入集合$V_{new}$中,将$<u, v>$边加入集合$E_{new}$中;
4)、输出:使用集合$V_{new}$和$E_{new}$来描述所得到的最小生成树
Kruskal算法简述¶
假设 $W_N=(V,{E}) $是一个含有n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:
先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。
之后,从网的边集 $E$ 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;
反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。
依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。
循环中可加入已加入MST的点的数量的判断,有可能提前结束循环,提高效率。
下面是hdu1233的源代码,一个用Prim算法,另一个用Kruskal,标准的MST问题。
#include <cstdio.h> #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; }