最小生成树 《啊哈算法》读书笔记
2016-07-08 14:18
295 查看
最小生成树:任何只由G的边构成,并包含G的所有顶点的树称为G的生成树(G连通). 加权无向图G的生成树的代价是该生成树的所有边的代码(权)的和. 最小代价生成树是其所有生成树中代价最小的生成树。
假设WN=(V,{E})是一个含有n个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含n个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有n棵树的一个森(摘自 nocow)
求最小生成树的主要算法:Kruskal算法 Prim算法
Kruskal算法(克鲁斯卡尔算法):(如果想要边的总长度之和最短,我们自然可以想到首先先选最短的边)将所有的边排序,从最小的边开始选,每次连通最小的边,不能形成回路,所以就要求判断两点间是否已经连通。为了优化操作,我们这里用并查集优化,判断其是否在一个树上。如果不在一个树上,就加进去,继续添加。
时间复杂度为O(MlogM)
然后看到NOCOW上的超级精简代码,顺便贴上吧
的
Prim算法(普里姆算法) :选中任意一个顶点,将其加入到生成树中去(这里假设为顶点1)。用数组记录生成树到各个顶点的距离,每次都是这样。从数组中选出离生成树最近的顶点加入到生成树中(这里用的dijkstra的思想),用dis数组更新为生成树到每一个不在生成树中的顶点的距离(松弛),重复直到有了n个顶点为止。
代码:
堆优化后的代码:
假设WN=(V,{E})是一个含有n个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含n个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有n棵树的一个森(摘自 nocow)
求最小生成树的主要算法:Kruskal算法 Prim算法
Kruskal算法(克鲁斯卡尔算法):(如果想要边的总长度之和最短,我们自然可以想到首先先选最短的边)将所有的边排序,从最小的边开始选,每次连通最小的边,不能形成回路,所以就要求判断两点间是否已经连通。为了优化操作,我们这里用并查集优化,判断其是否在一个树上。如果不在一个树上,就加进去,继续添加。
时间复杂度为O(MlogM)
#include<iostream> #include<algorithm> #include<cstdio> using namespace std; struct edge { int u; int v; int w; }e[10]; int n,m; int f[7]={0},sum=0,counter=0; void quicksort(int left,int right) { int i,j; struct edge t; if(left > right) return; i = left; j = right; while(i!=j) { //注意顺序 //先从右边找 while(e[j].w >= e[left].w && i < j) j--; //从左边找 while(e[i].w <= e[left].w && i < j) i++; if(i<j) { t = e[i]; e[i]= e[j]; e[j] = t; //swap(e[i],e[j]); } } //基准归位 t = e[left]; e[left]= e[i]; e[i] = t; //swap(e[left],e[i]); quicksort(left, i-1); quicksort(i+1, right); return; } int getf(int v) { if(f[v]==v) return v; else { //路径压缩,找到每个人的祖宗 f[v] = getf(f[v]); return f[v]; } } bool merge(int v,int u) { int t1,t2;//t1,t2分别为v和u的boss,每次都是用boss解决 t1=getf(v); t2=getf(u); if(t1!=t2) { f[t2] = t1;//靠左原则 return 1; //路径压缩后,将f[u]的根值夜赋值为v的祖先f[t1] } return 0; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w); quicksort(1,m); //并查集优化 for(int i=1;i<=n;i++) f[i]=i; //Kruskal算法核心部分 for(int i=1;i<=m;i++)//枚举 { //判断一条边的两个顶点是否连通,即是否在一个集合中 if(merge(e[i].u,e[i].v)) { counter++; sum+=e[i].w; } if(counter == n-1) break; } printf("%d\n",sum); return 0; } /* 6 9 2 4 11 3 5 13 4 6 3 5 6 4 2 3 6 4 5 7 1 2 1 3 4 9 1 3 2 */
然后看到NOCOW上的超级精简代码,顺便贴上吧
的
/*使用Union-Find判断是否在一个集合,代码比较STL-style Author:YangZX*/ #include <iostream> #include<algorithm> using namespace std; const int MAXV = 1024, MAXE = 100001; int n, m, f[MAXV], ans, cnt; struct edge { int f, t, w; }es[MAXE]; bool cmp(const edge &a, const edge &b) { return a.w < b.w; } void Fill(int &a) { static int cnt = 0; a = ++cnt; } int get(int x) { return x == f[x] ? x : f[x] = get(f[x]); } void Kruskal(const edge &e) { if(get(e.f) != get(e.t)) { f[get(e.f)] = get(e.t); ans += e.w; cnt++; } } void Read(edge &e) { cin>>e.f>>e.t>>e.w; } int main() { cin>>n>>m; for_each(es+1, es+m+1, Read); make_heap(es+1, es+m+1, cmp); sort_heap(es+1, es+m+1, cmp); for_each(f+1, f+n+1, Fill); for_each(es+1, es+m+1, Kruskal); cout<<(cnt < n-1 ? -1: ans)<<endl; return 0; } /* 6 9 2 4 11 3 5 13 4 6 3 5 6 4 2 3 6 4 5 7 1 2 1 3 4 9 1 3 2 */
Prim算法(普里姆算法) :选中任意一个顶点,将其加入到生成树中去(这里假设为顶点1)。用数组记录生成树到各个顶点的距离,每次都是这样。从数组中选出离生成树最近的顶点加入到生成树中(这里用的dijkstra的思想),用dis数组更新为生成树到每一个不在生成树中的顶点的距离(松弛),重复直到有了n个顶点为止。
代码:
#include<iostream> #include<cstdio> using namespace std; const int INF = 99999999; int e[7][7],dis[7],book[7]={0}; int main() { int n,m; int counter = 0,sum = 0; scanf("%d%d",&n,&m); //初始化 for(int i = 1;i <= n;i++) for(int j = 1;j <= n;j++) if(i == j) e[i][j] = 0; else e[i][j] = INF; int t1,t2,t3; for(int i = 1;i <= m;i++) { scanf("%d%d%d",&t1,&t2,&t3); e[t1][t2] = e[t2][t1] = t3;//无向图 } //初始化dis数组 for(int i = 1;i <= n;i++) dis[i] = e[1][i]; //Prim算法核心 //将1号顶点加入生成树 book[1] = 1; counter++; int u,v,minn; while(counter < n) { minn = INF; for(int i = 1;i <= n;i++) { if(!book[i] && dis[i] < minn) { minn = dis[i]; u = i; } } book[u] = 1; counter++; sum += dis[u]; for(int v = 1;v <= n;v++) { if(!book[v] && dis[v] > e[u][v]) dis[v] = e[u][v]; } } printf("%d\n",sum); return 0; } /* 6 9 2 4 11 3 5 13 4 6 3 5 6 4 2 3 6 4 5 7 1 2 1 3 4 9 1 3 2 */
堆优化后的代码:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int INF = 99999999; const int N = 100; int dis ,book ={0}; int h ,pos ,size; void swap(int x,int y) { int t; t = h[x]; h[x] = h[y]; h[y] = t; t = pos[h[x]]; pos[h[x]] = pos[h[y]]; pos[h[y]] = t; } void siftdown(int i) { int t,flag = 0; while(i*2 <= size &&flag == 0) { if(dis[h[i]] > dis[h[i*2]]) t=2*i; else t = i; //如果它有左儿子,再对右儿子进行讨论 if(i*2+1 <= size) { if(dis[h[t]] > dis[h[i*2+1]]) t = i*2+1; } //如果发现最小的节点编号不是自己,说明子节点中有更小的 if(t!=i) { swap(t,i); i = t; } else flag = 1; } } void siftup(int i) { int flag = 0; if(i == 1) return; while(i!=1 && flag == 0) { if(dis[h[i]] < dis[h[i/2]]) swap(i,i/2); else flag = 1; i/=2; } } int pop() { int t; t = h[1]; pos[t] = 0; h[1] = h[size]; pos[h[1]] = 1; size--; siftdown(1); return t; } int main() { int n,m,k; int u ,v ,w ,first ,next ; int counter = 0,sum = 0; scanf("%d%d",&n,&m); //读入边 for(int i = 1;i <= m;i++) scanf("%d%d%d",&u[i],&v[i],&w[i]); //无向图 for(int i = m+1;i <= 2*m;i++) { u[i] = v[i-m]; v[i] = u[i-m]; w[i] = w[i -m]; } //邻接表储存边 for(int i = 1;i <= m;i++) first[i] = -1; for(int i = 1;i <= 2*m;i++) { next[i] = first[u[i]]; first[u[i]] = i; } //Prim算法核心 //讲1号顶点加入生成树 counter++; book[1] = 1; //初始化dis数组,这里是1号顶点到其余各顶点的初始距离 dis[1] = 0; for(int i = 2;i <= n;i++) dis[i] = INF; k = first[1]; while(k != -1) { dis[v[k]] = w[k]; k = next[k]; } //初始化堆 size = n; for(int i = 1;i <= size;i++) { h[i] = i; pos[i] = i; } for(int i = size/2;i >= 1;i--) { siftdown(i); } pop();//先弹出一个堆顶的元素,因为此时堆顶是1号顶点 int j; while(counter < n) { j = pop(); book[j] = 1; counter++; sum += dis[j]; //扫描当前顶点j所有的边,再以j为中间节点,进行松弛 k = first[j]; while(k != -1) { if(book[v[k]]==0&&dis[v[k]] > w[k]) { dis[v[k]] = w[k]; siftup(pos[v[k]]);//对该点再堆中进行向上调整 } k = next[k]; } } printf("%d\n",sum); }
相关文章推荐
- mybatis在xml文件中处理大于号小于号的方法
- ViewPager+Fragment取消预加载(延迟加载)
- linux批量替换文件内容3种方法(perl,sed,shell)
- 因数分解
- 黑马程序员----面试题之银行调度系统
- 2017.7.7 在eclipse中快速查找类:ctrl+shift+T
- IE8 ajax缓存问题
- XP安装IIS来加载aspx页面(Web调用SAP数据)
- How to create a project with existing folder of files in Visual Studio?
- iOS实战项目:属于2个人的地图(共享实时位置,泡妞专用)
- 小延迟大吞吐:LMAX架构
- Shell脚本编程之结构化命令
- 工欲善其事必先利其器——Android Studio使用技巧与快捷键
- 关于Java Static 和Final的详细阐述
- 【转】Android Studio -修改LogCat的颜色*美爆了*
- android6.0 PullToRefreshWebView中的FloatMath.floor()不能用了如何替换
- 微信网页开发之video标签[HTML5微信播放器video]
- 黑马程序员----面试题之交通管理系统
- Retrofit的简单使用
- Retrofit的简单使用