图中连通块的个数:并查集
2016-06-28 20:00
218 查看
图的连通性问题
在地图上有若干城镇(点),已知所有有道路直接相连的城镇对。要解决整幅图的连通性问题。比如,随意给你两个点,让你判断它们是否连通;或者问你整幅图一共有几个连通块,也就是被分成了几个互相独立的块。修路工程问题会问还需要修几条路才能将所有城镇连通起来,实质就是求有几个连通块。如果只有1个连通块,说明整幅图上的点都连起来了,不用再修路了;如果是2个连通块,则只要再修1条路,从两个分支中各选一个点,把它们连起来,那么所有的点都连通了;如果是3个连通块,则只要再修2条路……
所以,若存在n个连通块,只要修n-1条路,就能把所有点连通。
实际给定的城镇有几百个,路有若干条,而且可能存在回路。 这时就要用到并查集。
PS:
其实求连通子图个数,除了并查集,用DFS和BFS也能够实现。
DFS:每次从某一点出发,遍历完与它相连的所有点,子图数num+1;当遍历完所有点后,num即为所求。
并查集
并查集的概念
并查集由两个函数(并、查函数)和一个整型数组(集)构成。函数join是合并;
函数find是查找;
数组pre记录了每个点的前导点是什么;
并查集的局限及改进
单纯的并查集只能保证得到独立子图的个数,但是,(即使经过了路径压缩)它不能保证同属于一个独立子图的节点的前导节点都相同。在这种情况下,如果还要判断两个节点是否属于同一个子图,还要再进行一次
find(i)操作。
换句话说,经过一次对每个节点的遍历的find()操作,可以保证同一子图中的节点的前导节点都相同。
并查集的实现
用一个有趣的方式解释并查集的实现:江湖上的大侠有师父和徒弟、下属,构成了许多树结构。连通块的个数可以认为是门派的个数。
![](http://img.my.csdn.net/uploads/201107/29/0_1311901712oy9f.gif)
pre数组
pre[1000]这个数组记录了每个大侠的上级是谁。
pre[15]=3
表示15号大侠的上级是3号大侠。如果一个人的上级就是他自己,那说明他就是掌门了,查找到此为止。也有孤家寡人自成一派的,比如欧阳锋,那么他的上级就是他自己。每个人都只认自己的上级。比如胡青牛只知道自己的上级是杨左使,而不认识张无忌。要想知道自己的掌门是谁,只能一级级查上去。
find函数和路径压缩
路径压缩算法是指在每次查询上级的同时,都进行优化处理,所以整个树的层数都会维持在比较低的水平上。![](http://img.my.csdn.net/uploads/201107/29/0_131190167189S8.gif)
find函数以及压缩路径可以用一个递归函数简单实现:
int find(int a){ if(pre[a]!=a) pre[a]=find(pre[a]);//路径压缩,本结点更新为根结点的子结点 return pre[a]; }
join函数
再来看看join函数,就是在两个点之间连一条线,这样一来,原先它们所在的两个块的所有点就都连通了。用pre[]数组,该如何实现连线呢? 很简单,将一个门派的掌门指定为另一个门派掌门的下属。
//让虚竹和周芷若做朋友 void join(int x,int y) { int fx=find(x),fy=find(y);//虚竹的老大是玄慈,周芷若的老大是灭绝 //玄慈和灭绝显然不是同一个人 //于是让前者成为后者的下属 if(fx!=fy) pre[fx]=fy; }
完整代码实现
#include<iostream> using namespace std; int pre[1050]; //保存节点的直接父节点 //查找x的根节点 int find(int a){ if(pre[a]!=a) pre[a]=find(pre[a]);//路径压缩,本结点更新为根结点的子结点 return pre[a]; } //连接两个连通块 void join(int x,int y) { int fx=Find(x),fy=Find(y); if(fx!=fy) pre[fy]=fx; } int main() { int N,M,a,b,i,j,ans=0; while(scanf("%d%d",&N,&M) && N) { //初始化pre数组 for(i=1;i<=N;i++) pre[i]=i; //根据连通情况,构建pre数组 for(i=1;i<=M;i++) { scanf("%d%d",&a,&b); join(a,b); } for(i=1;i<=N;i++) if(pre[i]==i) ans++; //计算连通子图的个数ans cout<<ans; return 0; }
求小岛个数
给定一个由1和0组成的二维字符数组,1代表陆地,0代表水。问被水包围的连通陆地区域的个数。这题可以用DFS的递归着色来解,也可以用并查集来做。
class Solution { public: vector<int> pre; int count=0; int numIslands(vector<vector<char>>& grid) { if(grid.size()==0 || grid[0].size()==0) return 0; int row = grid.size(); int col = grid[0].size(); pre.resize(row*col+1,0); //对pre数组进行初始化 for(int i=0;i<row;i++) for(int j=0;j<col;j++) { int num = col*i+j; pre[num] = num; } //遍历图中的每个点 for(int i=0;i<row;i++) for(int j=0;j<col;j++) { if(grid[i][j]=='1') { int down=i+1,right=j+1; if(down<row && grid[down][j]=='1') join(col*i+j,col*down+j); if(right<col && grid[i][right]=='1') join(col*i+j,col*i+right); } } //再遍历一次,计算islands的个数 int ans = 0; for(int i=0;i<row;i++) for(int j=0;j<col;j++) { int num = col*i+j; if(pre[num] == num && grid[i][j]=='1') ans++; } return ans; } //并,将联通的点的pre设为同一个值 void join(int x,int y){ int fx=find(x); int fy=find(y); if(fx != fy) pre[fx] = fy; } //找到a的祖先,并且路径压缩 int find(int a){ if(pre[a] != a) pre[a] = find(pre[a]); return pre[a]; } };
相关文章推荐
- IE8下对象数组的长度问题
- PyCharm 中文注释报错 SyntaxError: Non-ASCII character
- JavaScript绑定事件的方法[3种]
- android opengl jni中纹理贴图
- [bzoj3572][HNOI2014]世界树
- gcc编译器对宽字符的识别
- Jquery的各个Ajax函数,$.get(),$.post(),$.ajax(),$.getJSON()的用法
- 项目中二级指针的使用
- 文件和目录属性
- Solr - 主/从同步
- APUE环境配置
- 《剑指offer》:[58]二叉树的下一个结点
- 关于ArcGIS API for Javascript的安装_3.17API与SDK
- LSM树
- 解决Qt程序发布时中文乱码问题(通过QApplication.addLibraryPath加载QTextCodec插件)
- PostgreSQL的创建表
- Java5种常用排序算法
- javaweb学习总结(五)——Servlet开发(一)
- 异步下载照片墙
- EventBus的使用