您的位置:首页 > 编程语言 > Java开发

并查集实现-(秩优化+路径压缩+java)

2018-02-09 02:44 393 查看
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
--百度百科


并查集(Union/Find)从名字可以看出,主要涉及两种基本操作:合并和查找。这说明,初始时并查集中的元素是不相交的,经过一系列的基本操作(Union),最终合并成一个大的集合。合并之后的结构逻辑上是一个森林。

应用

并查集数据结构非常简单,基本操作也很简单。用途也简单,就是检查两点是否联通。在实际中有很多应用,比如,求解无向图中连通分量的个数,生成迷宫……这些应用本质都是初始时都是一个个不连通的对象,经过一步步处理,变成连通的了,如迷宫,初始时,起点和终点不连通,随机地打开起点到终点路径上的一个方向,直至起点和终点连通了,就生成了一个迷宫。

存储结构

int count;// 元素数量

int[] rank; // 下标所对应节点层数

int[] parent;// 下标表示节点,值表示其父元素

主要函数及思路

void Union(int p,int q);

合并两个节点的思路就是,先找到两个节点的根节点,然后将一个节点接到另外一个节点下面即可,涉及到一个优化就是使用秩优化,因为如果随意将一个节点接到另外一个节点也是可以的,但是这种方法效率低下且慢,因为有时候一个根节点的层级很高,而另外一个层级很低,如果把层级高的放到层级低的下面这样层级就会增加,查找所用的时间就更长,数据少还好,多的时候效率太低了,所以引入一个数组记录每个根节点层数,每次合并把层数低的放到层数高的节点下面,这样层数就不会增加。

int find(int p);

此函数会返回p元素对应的根节点元素,因为我们初始化的时候根节点的父元素就是自己,所以我们查找的时候只要顺着查上去,遇到一个元素的父节点就是自己的,那么这个就是根节点了,在这里可以有一个优化,因为一旦元素一多起来,可能退化成一条链,每次得到根节点都将会使用O(n)的复杂度,这显然不是我们想要的。对此,我们必须要进行路径压缩,即我们找到最久远的祖先时“顺便”把它的子孙直接连接到它上面。这就是路径压缩了。

boolean isConnected(int p, int q);

只要调用find方法找到各自的根节点,如果p和q的根节点一样,那么就代表联通

具体代码:

import java.util.Random;

public class UnionFind {

private int count;// 元素数量
private int[] rank; // 层数
private int[] parent;// 节点

UnionFind(int count) { //初始化操作
this.count = count;
rank = new int[count];
parent = new int[count];

for (int i = 0; i < count; i++) {
rank[i] = 1;
parent[i] = i;

}

}

public void Union(int p, int q) { // 合并两个元素

int pRoot=find(p); //定位到其根节点
int qRoot=find(q);
if(pRoot==qRoot) //如果两个都相等,证明已经是连接好的,不必再合并
return;

if(rank[pRoot]>rank[qRoot]){ //如果p根节点的层数大于q的,那么将qRoot接到pRoot下面,这样接好以后层级不会增加
parent[qRoot]=pRoot;

}else if(rank[pRoot]<rank[qRoot]){

parent[pRoot]=qRoot;
}else //如果等于,则随便哪边层级都会加一
{
parent[pRoot]=qRoot;
rank[qRoot]=rank[qRoot]+1;
}

}

public int find(int p) {// 查找元素 返回根元素

while(p!=parent[p]){
parent[p]=parent[parent[p]]; //路径压缩
p=parent[p];
}

return p;
}

public boolean isConnected(int p, int q) {

return find(p) == find(q);
}

public static void main(String[] arg){

int N=10000000;
UnionFind uf=new UnionFind(N);
double startTime=System.currentTimeMillis();
int tempA;
int tempB;
Random random = new Random();
//进行N次合并操作
for(int i=0;i<N;i++){
tempA=random.nextInt(N)%N;
tempB=random.nextInt(N)%N;
uf.Union(tempA, tempB);

}
//进行N次查找
for(int i=0;i<N;i++){
tempA=random.nextInt(N)%N;
tempB=random.nextInt(N)%N;
uf.isConnected(tempA, tempB);

}

double endTime=System.currentTimeMillis();

System.out.println(endTime-startTime);

}

}


经过本机测试,千万级数据,只需要4025毫秒,相当于4秒,百万级数据217毫秒,0.217秒,当然这个数据根据电脑配置不同会有所不同,但是效率是很明显的,这就是并查集的优势所在,一个算法解决的问题越少所需要的时间应该越少。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐