并查集--算法,优化,变种
来源:互联网 发布:正规购买淘宝小号平台 编辑:程序博客网 时间:2024/05/22 18:24
一、定义
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。
基础的并查集能实现以下三个操作:1.建立集合;2.查找某个元素是否在一给定集合内(或查找一个元素所在的集合); 3.合并两个集合.“并”“查”“集”三字由此而来。
并查集能解决的问题一般可以转化为这样的形式:初始时n个元素分属不同的n个集合,通过不断的给出元素间的联系,要求实时的统计元素间的关系(即是否存在直接或间接的联系)。
并查集本身不具有结构,可以用数组、链表以及树等实现。最常用的是数组实现。
二、实现
数组实现:
建立标记数组father,用father[i]表示元素i所属集合的标记。
图示
1.建立集合
开始时每个元素各自独立,可以把每个元素所属集合标记为其自身序号。
void make(){int i;for(i=1;i<=n;i++) father[i]=i;}
2.合并集合&查找元素所属集合
find函数返回的是元素所属集合的根结点(别忘了并查集是树型的数据结构)。方法就是循环或递归,寻找当前结点的父结点,父结点的父结点,父结点的父结点的父结点......直到找到一个结点的父结点是它自己,那么它就是根结点。
int find(int x)//非递归写法{while(father[x]!=x) x=father[x];return x;}int find(int x)//递归写法{if(father[x])!=x)return find(father[x]);return x;}
因此,比较两个元素x,y是否是同一集合的方法就是比较find(x)是否等于find(y)。
bool judge(int x,int y){ x=find(x); y=find(y); if(x==y) return true; else return false;}
合并集合的方法就是将其中一个点所在集合的根结点的父结点设定为另一个点所在集合的根结点。
void union1(int x,int y)//union是关键字,不能用,函数名可以随便换一个方便的{ x=find(x); y=find(y); father[y]=x;}
1.路径压缩
前面的做法就是将元素的父亲结点指来指去地指,当这棵树是链的时候,可见判断两个元素是否属于同一集合需要O(n)的时间。
举个例子:在前面方法的第5步后,如果要查询第3、5元素所在集合的根结点,那么每次都需要查询father[3](father[5])、father[2](father[4])、father[1]的值。
于是,路径压缩产生了作用。
路径压缩就是在找完根结点之后,在递归回来的时候顺便把路径上元素的父亲结点都设为根结点。
int find(int x)//递归写法{ if(father[x]!=x) father[x]=find(father[x]); return father[x];}
int find(int x)//非递归写法,不太好记但是更快,列几组数据试一下也不难理解{int r=x,q;while(r!=father[r])r=father[r];while(x!=r){q=father[x];father[x]=r;x=q;}return r;}(这个优化平时都可以用,但是对于某些题会造成麻烦,例如加权并查集,这样的题需要特殊处理)
在合并两个集合(就是两棵树)的时候,如果待合并的树的深度不相同,那么就有两种选择:一种是以深度较小的树的根结点为新的根结点,另一种是以深度较大的树的根结点为新的根结点。而事实上,选择以深度较大的树的根结点为新的根结点较好,因为这样的话新生成的树深度会更小,可以防止树的退化(退化指越来越接近链表,即深度大而分支少),使资源利用更合理。而合并时这样选择,就叫做“按秩合并”。
按秩合并的基本思想是将深度较小的树指到深度较大的树的根上。
按秩合并需要新开一个数组depth来记录深度。depth[x]是(("以x为根结点的树"的某个叶结点到x的最长路径上)边的数目)的一个最大值。(即以x为根结点的树的树高)
(这个优化比较麻烦,简单题一般不用)
void make(){int i;for(i=1;i<=n;i++){father[i]=i;depth[i]=0;//如果初值为0则可以省略 }}
void union1(int x,int y){int fx=find(x),fy=find(y);if(depth[fx]>depth[fy])father[fy]=fx;else{father[fx]=fy;if(depth[fx]==depth[fy])depth[fy]++;}}
①求无向图最小生成树的Kruskal算法
Kruskal将一个连通块当做一个集合(连通块指无向图中相互连通的一些点)。对于一张有n个点的无向图,首先将所有的边按从小到大排序,并认为每一个点都是孤立的,分属于n个独立的集合。然后按顺序枚举每条边。如果这条边连接两个不同的集合,那么就将这条边加入最小生成树,这两个不同的集合就合并成了一个;如果这条边连接的两个点属于同一集合,就跳过。直到选了n-1条边为止。具体参见Kruscal算法。
有的时候,不仅需要像普通并查集一样记录一些元素之间有无关系,还需要记录它们之间有怎样的关系,这时候就需要引入加权并查集。
通常情况下,用一个数组r来记录这些关系,r[i]表示元素i与父结点的关系。至于是什么关系,还要根据具体要求来看。
在find(x)函数中进行路径压缩的同时,许多结点的父结点会改变,这时就需要根据实际情况调整权值以保证其正确性。
在union1(x,y)函数中,(不妨设将y集合并入x集合)由于y的父结点的改变,需要调整y对应的权值,但不需要调整y的子结点对应的权值,因为子结点权值会在find(子结点)时得到调整。
典型例题:
1.银河英雄传说
一道裸的加权并查集的题
2.食物链
一道变式题
2.还是食物链(通过上面两个变种的描述,可以看出它们的适用范围有很大重叠)
- 并查集--算法,优化,变种
- hdu 3367 Pseudoforest kruskal算法的变种+并查集
- POJ1182 食物链 【并查集变种】
- HDU 5326 Work(并查集变种)
- A Bug's Life(并查集变种)
- 并查集优化
- POJ 1456 Supermarket(贪心算法,可用并查集优化)
- 并查集及其简单应用:优化kruskal算法
- 算法:并查集的实现及简单优化
- 【算法——Python实现】并查集及优化
- 并查集算法
- 并查集算法
- 并查集算法
- 并查集算法
- 并查集算法
- 并查集算法
- 并查集算法
- 并查集算法
- 微博广告Hubble系统:秒级大规模分布式智能监控平台架构实践
- 自定义属性--你真的了解match_parent吗
- Android常用控件
- c++中的引用
- gcc中(-I -l -L)参数的意义
- 并查集--算法,优化,变种
- SpringMVC执行流程
- 上手ajax
- 尝试使用laravel
- Android通过OpenCV获取摄像头帧数据并在悬浮框显示
- 小结学习微信小程序
- 解决光纤猫恢复出厂功能后的上网问题
- javaweb编译部署
- 通过反射了解集合泛型本质