博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
最小生成树 kruskal prim
阅读量:5140 次
发布时间:2019-06-13

本文共 5282 字,大约阅读时间需要 17 分钟。

差不多就要CCF考试了,弱鸡我还是挺慌的(好想上400分保研~),最近开始来逐渐巩固自己所储备的知识。

CCF的第四题往往是图论,今天就来回顾一下经典的最小生成树算法:Kruskal && Prim。

  • Kruskal:遍历边,边的累加生成树--适用于边较稀疏的情况。

  边用结构体数组存储,属性有:两个端点,权值。

  将所有边由小到大排序, 以最短的边为算法的起始。依次遍历剩下的边,结合并查集判断是否会形成强连通,不行成强连通的剩下边加入到树中。截止条件,边数 == 点数 - 1。

  个人容易出错的地方:

  1)并查集的Unite函数中,两个点的根节点应要通过FindRoot函数来查找,而不是直接用Root【】数组(数组存储的是父节点,单并不一定是祖宗节点),连通性是通过最祖宗的节点是否相连来判断的。

  2)遇到多组测试用例时,要记得将重复使用的量重新初始化。

/*kruskal*/#include 
using namespace std;const int maxe = 1e4, maxv = 1e3;struct Edge{ int u, v, w; int index;}edge[maxe];int root[maxv], e, v;bool vis[maxe];//初始的父节点就是自己inline void ini(){ for(int i=1; i<=v; ++i) root[i] = i; return;}//查找时,压缩路径,即父找爷,我找爷inline int find_root(int q){ while(q != root[q]){ root[q] = root[root[q]]; q = root[q]; }return q;}//由于已经进行路径压缩,无需再考虑生成畸形树的情况inline void unite(int a, int b){ root[find_root(a)] = find_root(b);}inline void comp(const Edge& a, const Edge& b){ return a.w < b.w;}void kruskal(){ sort(edge, edge + e, comp); for(int i=0; i
> v >> e; ini(); for(int i=0; i
> edge[i].u >> edge[i].v >> edge[i].w; edge[i].index = i; } kruskal(); return 0;}

 

 

  • Prim:遍历点, 点的集合生成树--适用于边较密集的情况

  由于边较密集,用邻接矩阵table【】【】来存边,存边权时注意边的双向性

  用minCnt【】数组存所有点到树的距离,个人习惯以1为初始的树根st(哪个点都无所谓,因为所有点都要取到),即初始的minCnt为所有点到st点的距离table[st][index]。

  vis【】标记点进入树的情况。

  先初始化minCnt,标记我们的st点(初始的树),因为已经找到一个点了,所以之后是一个总点数 - 1次的循环,即 n - 1 次, 找到剩下的 n - 1个点。在这个大循环中,每次遍历剩下的点,找到到树距离最短的(用minLen存最短距离,minNode存最小的点的编号,桶排思想),标记入树,同时累加树的总权值。接着,由于树的扩张,我们需要更新剩下点到树的最小距离minCnt, 又是一个循环。总体上就是,一个大循环,内嵌两个小循环。

  个人容易出错的地方:

  1)这里各点到树的距离会随着树的生成不断变化,所以没有必要一开始sort排序,每次查找时用循环桶排就好。

  2)最初始的点就是初始的树,所以到树的距离为0,minCnt【st】 = 0。

  这里贴一道CCF的第四题(这是比较早的CCF题,所以不难,竟然只是个模板题~),顺便扇自己一巴掌--我做题时,一看以为是求单源最短路径,上来就敲spfa,结果浪费了时间。真正考试时要稳一点,在纸上大致模拟一遍思路看是否符合题意再开始写。

//minimum spanning tree //边较多,用prim#include 
#include
#include
#include
using namespace std;#define Inf 0x3f3f3f3f#define ll long longconst int maxp = 1000 + 2, st = 1;int table[maxp][maxp], minCnt[maxp]; // 到树的距离bool vis[maxp];int n, m, a, b, c, cntp;ll sumw;void Prim(){ for(int i=2;i<=n;++i){ minCnt[i] = table[st][i]; } minCnt[st] = 0; vis[st] = true; //第一个节点入树 for(int t=1; t < n; ++t){
//找到剩下的n-1个节点 int minNode = 0, minLen = Inf; for(int j = 1; j <= n; ++j){ if(!vis[j] && minCnt[j] < minLen){ minLen = minCnt[j]; minNode = j; } else continue; } sumw += minLen; //生成的一支的权值 vis[minNode] = true; for(int j = 1; j<= n; ++j){ if(!vis[j]){
//更新到树的距离 minCnt[j] = min(minCnt[j], table[minNode][j]); } } }}inline int read(){ char ch = getchar(); int ans = 0; while(ch < '0' || ch > '9') ch = getchar(); while(ch >= '0' && ch <= '9'){ ans = (ans << 3) + (ans << 1) + ch - '0'; ch = getchar(); }return ans;}int main(){ n = read(), m = read(); memset(table, Inf, sizeof(table)); while(m--){ a = read(), b = read(), c = read(); table[a][b] = table[b][a] = min(table[a][b], c); } Prim(); printf("%lld\n", sumw); return 0;}

   和dijkstra类似,Prim也可以堆优化来提高效率,用链式向前星存图,用priority_queue来代替第一个小循环的桶排查找。由于使用链式向前星存图,所以在更新剩下未进入树的点到树的距离的操作变成了将当前入树的点的临边连接的点入队,这个很容易理解,当前点入树影响的必然是与之相连的点,相连的点到树的新距离(不一定是最小,但不用担心,priority_queue会自动将其排序好,我们只需要关注有新的距离出现即可)加入到priority_queue中,这样减少了许多不必要的遍历更新。仍然以上边的CCF的为例。有没有湖大的朋友啊,点个赞呀~

//边较密集邻接矩阵存图,Prim minimum spannning tree//注意是双向图,存双向边#include 
#include
#include
#include
#include
using namespace std;#define ll long long#define Inf 0x3f3f3f3fconst int maxv = 1e3 + 5, maxe = 1e5 + 5, st = 1;struct Node{ int index; ll dis; //到树的距离 Node(int a=0, ll b=0):index(a), dis(b){} bool operator < (const Node& b) const{ return this->dis > b.dis; }};struct Edge{ int to, w; int next;}e[maxe * 2];int table[maxv][maxv], head[maxv];int n, m, a, b, w, cnte;ll dis[maxv], ans;bool vis[maxv]; //标记是否入树//堆优化版本的Prim//用priority_queue中的优先弹出代替原来的桶排//同时链式向前星优化邻接矩阵,这样更新节点的时候就只会遍历到相邻的点,省去了不必要的遍历inline void Prim_que(){ priority_queue
pq; pq.push(Node(st, 0)); int cntv = 0; while(cntv < n && pq.size()){ Node cur = pq.top(); pq.pop(); if(vis[cur.index]) continue; vis[cur.index] = true; cntv += 1, ans += cur.dis; for(int i = head[cur.index]; i; i = e[i].next){ if(vis[e[i].to]) continue; pq.push(Node(e[i].to, e[i].w)); } } return;}inline void add_edge(const int& from, const int& to, const int& w){ ++cnte; e[cnte].to = to, e[cnte].w = w; e[cnte].next = head[from]; head[from] = cnte; return;}inline int read(){ char ch = getchar(); int ans = 0; while(ch < '0' || ch > '9') ch = getchar(); while(ch >= '0' && ch <= '9'){ ans = (ans << 3) + (ans << 1) + ch - '0'; ch = getchar(); } return ans;}int main(){ n = read(), m = read(); memset(table, Inf, sizeof(table)); while(m--){ a = read(), b = read(), w = read(); add_edge(a, b, w); add_edge(b, a, w); } Prim_que(); printf("%lld\n", ans); return 0;}

 

转载于:https://www.cnblogs.com/GorgeousBankarian/p/11144382.html

你可能感兴趣的文章
[转帖] BIO与NIO、AIO的区别
查看>>
[转帖]哈佛结构和冯·诺依曼结构的区别
查看>>
手指滑动屏幕原理
查看>>
对于javascript里面闭包的理解
查看>>
LANMP安装总结
查看>>
因为没有打开的文档,所以这一命令无效==操作word问题
查看>>
C++获取Windows7 32位系统中所有进程名(类似于任务管理器中的进程)
查看>>
【19】235. Lowest Common Ancestor of a Binary Search Tree
查看>>
关闭vs的编译警告
查看>>
opencv载入,显示及保存图像
查看>>
C++回调机制实现(转)
查看>>
iOS基础篇 - UIWindow的简单介绍
查看>>
五十六. playbook基础 、 playbook进阶
查看>>
PICT测试工具的安装及使用
查看>>
ORA-28000: the account is locked-的解决办法
查看>>
只有ReflectionOnlyLoadFrom才可以拯救与GAC冲突的强命名程序集
查看>>
Day44:MySQL(单表的表记录的操作)
查看>>
家用路由器端口映射实战
查看>>
视频播放的基本原理
查看>>
webstorm vscode 常用设置
查看>>