最小生成树简单讲解及代码实现
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
并查集
为了防止树变成一条单链的最坏情况,在进行合并时,可以用重 量规则进行合并。将重量小的树合并到重量大的树中。
bool *root; int *parent; //若当前节点为根,则root为true //parent用来保存该树中节点的总数 void Initialize(int n) { root=new bool[n+1]; parent=new int[n+1]; for(int e=1;e<=n;e++) { parent[e]=1; root[e]=true; } } int Find(int e) { while(!root[e]) e=parent[e]; return e; } void Union(int i,int j) { if(parent[i]<parent[j]) { parent[j]+=parent[i]; root[i]=false; parent[i]=j; } else{ parent[i]+=parent[j]; root[j]=false; parent[j]=I; } }
Kruskal算法的实现
graph g; int main() { int i,j,w; while(scanf("%d",&g.n)!=EOF) { int k=0; for(i=0;i<g.n;i++) { for(j=0;j<g.n;j++) { scanf("%d",&w); if(i<j) { g.edge[k].u=i; g.edge[k].v=j; g.edge[k].w=w; k++; } } } g.m=k; g.kruskal(); } return 0; }
int Graph::Prim(int s) { int i,j,ans=0,len[N]; bool flag[N]; for(i=1;i<=n;i++){ len[i]=INF; flag[i]=0; } CurrentSize=0; node q,q1,q2; q.id=s; q.val=0; Insert(q); for(j=1;j<=n;j++) { do{ DeleteMin(q); }while(flag[q.id]); ans+=q.val; flag[q.id]=1; for(i=0;i<v[q.id].size();i++) {Hale Waihona Puke Baiduq1=v[q.id][i]; if(!flag[q1.id] && len[q1.id]>q1.val) { len[q1.id]=q1.val; q2.id=q1.id; q2.val=q1.val; Insert(q2); } } } return ans; }
a[1] 2 a[2] 8 4 a[3] 堆为空,第一个数2插入到a[1] 第二个数8插入到a[2],满足每个节点 都小于等于其子节点的值 第三个数4插入到a[3],满足每个节点 都小于等于其子节点的值 第四个数10插入到a[4],满足每个节点 都小于等于其子节点的值 第五个数7插入到a[5] 不满足每个节点都小于等于其子 节点的值,需要进行调整。 将7上移到其父节点a[2],8下移到a[5] 此时所有节点都满足节点值小于等于 其子节点的值。若不满足,继续上移 。
最小生成树 MST (minimum spanning tree)
by nkzgm
并查集
Kruskal算法需要用到并查集,并查集实现程序如下:
int *parent; void initialize(int n) {//初始化,每个类/树有一个元素 parent=new int[n+1]; for(int e=1;e<=n;e++) parent[e]=0; } int Find(int e) {//返回包含e的树的根节点 while(parent[e]) e=parent[e]; //上移一层 return e; } void Union(int i,int j) { //将根为i和j的两棵树进行合并 parent[j]=i; }
a[4] 10
a[5] 8
最小堆
插入程序: //node为节点结构,CurrentSize为数组大小 //node a[N]; void Insert(node& x) { int i = ++CurrentSize; //插入一个节点,数组大小加1 while(i!=1 && x<a[i/2]) {//若堆不为空并且插入节点的值小于其 父节点的值 a[i]=a[i/2];//父节点的值下移 i/=2; //上移 } a[i]=x; //i为1或节点i的值a[i]大于a[i/2] } 删除程序: void DeleteMin(node& x) { x = a[1]; //最小值为a[1] node y = a[CurrentSize--]; int i=1,ci=2; //ci为i的子节点 while (ci<=CurrentSize) { if(ci<CurrentSize && a[ci]>a[ci+1]) ci++; //选取节点i子节点最小的一个 if(y<=a[ci]) break; a[i]=a[ci]; i=ci; ci*=2; //下移 } heap[i]=y;//ci大于数组大小或a[ci]>=y }
Prim的最小堆实现
//vector<node>v[i]存储从i出发的边 //heap[N]存储最小堆 #include<stdlib.h> #include<stdio.h> #include<vector> using namespace std; #define N 1001 #define INF 1<<30 struct node { int id,val; //id为节点编号,val为边的权值 bool operator< (node s){ return val<s.val;} bool operator> (node s){ return val>s.val;} bool operator==(node s){ return val==s.val;} bool operator<=(node s){ return val<=s.val;} bool operator>=(node s){ return val>=s.val;} node operator= (node s){ id=s.id; val=s.val; return *this;} }; class Graph { private: int CurrentSize; node heap[N]; public: int n,m; vector<node>v[N]; void Insert(node& x); void DeleteMin(node& x); int Prim(int); }; void Graph::Insert(node& x) { int i = ++CurrentSize; while(i!=1 && x<heap[i/2]) { heap[i]=heap[i/2]; i/=2; } heap[i]=x; }
Kruskal算法的实现
并查集还有其他优化策略,比如路径压缩,这里不再论述。对于 kruskal算法,在判断加入的边(u,v)是否构成回路时,只需判断Find(u) 与Find(v)是否相等即可。若相等,则构成回路,否则不构成回路。 例子的kruskal程序如下:
#include <stdio.h> #include <memory.h> #include <algorithm> using namespace std; #define N 10000 struct Edge { int u,v,w; }; class UFset { private: bool *root; int *parent; int n; public: UFset(int n) { int i; this->n=n; root=new bool[n]; parent=new int[n]; for(i=0;i<n;i++) { parent[i]=1; root[i]=true; } } ~UFset() { delete []parent; delete []root; } int Find(int e) {// Return root of tree containing e. // Compact path from e to root. int i=e; // Find root while(!root[i]) i=parent[i]; // compact int j=e;// start at e while(j!=i)// j is not root { int pj=parent[j]; parent[j]=i;// move j to level 2 j=pj;// j moves to old parent } return i; }
Prim的最小堆实现
void Graph::DeleteMin(node& x) { x = heap[1]; node y = heap[CurrentSize--]; int i=1,ci=2; while (ci<=CurrentSize) { if(ci<CurrentSize && heap[ci]>heap[ci+1]) ci++; if(y<=heap[ci]) break; heap[i]=heap[ci]; i=ci; ci*=2; } heap[i]=y; }
a[4] 10
a[5] 7
最小堆的删除
从最小堆中删除最小值:
a[1] 2 8 a[2] 7 4 a[3] 最小堆的最小值肯定是a[1], 将a[1]的值取出,同时将数组的最后 一个值a[5]赋值给a[1],再删除a[5]。 此时节点a[1]不满足最小堆的 条件,取节点a[1]子节点a[2],a[3]的 最小值a[3](值为4),将4赋值给a[1], 将a[1]的原值8赋值给a[3],此时所有 节点都满足条件。若a[3]不满足条 件,继续向下移动。
Kruskal算法的实现
bool Union(int x,int y) {// Combine trees with roots px and py. int px=Find(x); int py=Find(y); if(px==py) return false; if(parent[px]<parent[py]) {// px becomes subtree of py parent[py]+=parent[px]; root[px]=false; parent[px]=py; } else {// py becomes subtree of px parent[px]+=parent[py]; root[py]=false; parent[py]=px; } return true; } }; class graph { public: int n,m; Edge edge[N]; void kruskal(); }; bool cmp(const Edge &e1,const Edge &e2) { return e1.w<e2.w; } void graph::kruskal() { sort(edge,edge+m,cmp); UFset ufset(n); int min=0,k=0; for(int i=0;i<m;i++) { if(ufset.Union(edge[i].u,edge[i].v)) { min+=edge[i].w; ++k; } if(k==n-1) break; } printf("%d\n",min); }
最小堆
最小堆是最小的完全二叉树,其每个节点的值都小于或 等于其子女节点(如果有的话)的值。最小堆可以用数组存 储,根节点放在a[1],依次进行存储。例如:
a[1] 2 a[2] 7 4 a[3]
a[4] 10
a[5] 8
最小堆有两种操作,分别是插入和删除操作。
最小堆的插入
插入顺序为2 8 4 10 7: