线段树、Trie和并查集03:并查集
并查集,又称Union Find,和之前的树结构不同,并查集的指针由孩子指向父亲
常用于解决连接问题,比如网络中节点间的连接状态
对一组数据,并查集主要支持合并和查询两个动作,unionElement(p, q)和isConnected(q, p)
数组实现并查集(Quick Find)
效率低,不推荐
/**
* 定义一个并查集接口
*/
interface UF{
int getSize();
boolean isConnected(int p, int q);
void unionElement(int p, int q);
}
/**
* 该版本只是用数组模拟了并查集的实现,查询操作很快,但是合并操作却很慢
* find()方法的时间复杂度为O(1),而unionElement()方法的时间复杂度为O(n)
*/
class UnionFind implements UF{
/**
* id数组存放所有的元素,其值为该元素所属的集合ID
* 初始时每个元素的ID都不一样
*/
int [] id;
public UnionFind(int size){
id = new int[size];
for (int i = 0; i < id.length; i++) {
id[i] = i;
}
}
@Override
public int getSize(){
return id.length;
}
/**
* 查询元素p所属的集合ID
* 时间复杂度为O(1)
*/
private int find(int p){
if (p < 0 || p >= id.length){
throw new IllegalArgumentException("索引值非法");
}
return id[p];
}
/**
* 判断两个元素是否是连接的,即集合ID是否相同
*/
@Override
public boolean isConnected(int p, int q){
return find(p) == find(q);
}
/**
* 将两种不同集合的所有元素合并到同一种集合
* 时间复杂度为O(n)
*/
@Override
public void unionElement(int p, int q){
int pID = id[p];
int qID = id[q];
if (pID == qID){
return;
}
else {
for (int i = 0; i < id.length; i++) {
if (id[i] == pID){
id[i] = qID;
}
}
}
}
}
树结构实现并查集(Quick Union)
将元素看作是节点(不用真正的使用节点类),用树结构实现,指针由孩子指向父亲
interface UF{
int getSize();
boolean isConnected(int p, int q);
void unionElement(int p, int q);
}
/**
* 使用数组模拟树结构实现并查集,但是不使用节点类
* find()和unionElement()方法的时间复杂度为O(h)
*/
class UnionFind implements UF{
/**
* parent()方法存放所有的元素,其值为所在树的根节点(指针由下往上)
* 初始时每个元素都是一颗树,只有一个根节点
*/
int[] parent;
public UnionFind(int size){
parent = new int[size];
for (int i = 0; i < parent.length; i++) {
parent[i] = i;
}
}
@Override
public int getSize(){
return parent.length;
}
/**
* 查询元素p的根节点,判断条件是当前节点的下一个节点是否是自身
* 时间复杂度为O(h)
*/
private int find(int p){
if (p < 0 || p >= parent.length){
throw new IllegalArgumentException("索引值非法");
}
while (parent[p] != p){
p = parent[p];
}
return p;
}
@Override
public boolean isConnected(int p, int q){
return find(p) == find(q);
}
/**
* 合并p和q两种集合,就是让p的根节点指向q
* 时间复杂度为O(h)
*/
@Override
public void unionElement(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot){
return;
}
else {
parent[pRoot] = qRoot;
}
}
}
基于size的优化
存在的问题:当运行的次数很大时,树结构的深度也会很深,甚至会退化成链表,而isConnected()方法的时间复杂度相对较高,会导致其性能下降,甚至不如数组实现的性能
优化空间:合并集合的同时考虑当前集合的元素个数,让个数少的树指向个数多的树,避免树太深
interface UF{
int getSize();
boolean isConnected(int p, int q);
void unionElement(int p, int q);
}
class UnionFind implements UF{
/**
* 定义一个nums[]数组存放每棵树的节点个数,初始时都是1
*/
int[] parent;
int[] nums;
public UnionFind(int size){
parent = new int[size];
nums = new int[size];
for (int i = 0; i < parent.length; i++) {
parent[i] = i;
nums[i] = 1;
}
}
@Override
public int getSize(){
return parent.length;
}
private int find(int p){
if (p < 0 || p >= parent.length){
throw new IllegalArgumentException("索引值非法");
}
while (parent[p] != p){
p = parent[p];
}
return p;
}
@Override
public boolean isConnected(int p, int q){
return find(p) == find(q);
}
/**
* 将元素个数少的集合合并到元素个数多的集合,避免树的链表化
*/
@Override
public void unionElement(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot){
return;
}
else {
if (nums[pRoot] <= nums[qRoot]){
parent[pRoot] = qRoot;
nums[qRoot] += nums[pRoot];
}
else {
parent[qRoot] = pRoot;
nums[pRoot] += nums[qRoot];
}
}
}
}
基于rank的优化
存在问题:虽然基于size的优化已经大大提高了并查集的性能,但是其存在一个不理想的情况,那就是如果一棵树高度很高但是元素个数少,其会指向元素个数多但是高度很低的树,这样又增加了树的最大深度
优化空间:更合理的做法是让高度低的树指向高度高的树
interface UF{
int getSize();
boolean isConnected(int p, int q);
void unionElement(int p, int q);
}
class UnionFind implements UF{
/**
* 定义一个rank[]数组存放每棵树的高度,初始时都是1
*/
int[] parent;
int[] rank;
public UnionFind(int size){
parent = new int[size];
rank = new int[size];
for (int i = 0; i < parent.length; i++) {
parent[i] = i;
rank[i] = 1;
}
}
@Override
public int getSize(){
return parent.length;
}
private int find(int p){
if (p < 0 || p >= parent.length){
throw new IllegalArgumentException("索引值非法");
}
while (parent[p] != p){
p = parent[p];
}
return p;
}
@Override
public boolean isConnected(int p, int q){
return find(p) == find(q);
}
/**
* 让高度低的树指向高度高的树
* 当两棵树高度相等的时候,让一颗指向另一颗,然后更新高度值
*/
@Override
public void unionElement(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot){
return;
}
else {
if (rank[pRoot] < rank[qRoot]){
parent[pRoot] = qRoot;
}
else if (rank[pRoot] > rank[qRoot]){
parent[qRoot] = pRoot;
}
else {
parent[pRoot] = qRoot;
rank[qRoot]++;
}
}
}
}
路径压缩优化(Path Compression)
在每次查找时,如果当前节点的父亲节点不是自身,那就指向爷爷节点,然后再向下寻找,直到根节点,这样整棵树的高度就会进一步降低
此时可以解释为什么rank优化不能称为height优化,因为在路径压缩中,高度一直在变,而排名不会变,所以称为rank更恰当
interface UF{
int getSize();
boolean isConnected(int p, int q);
void unionElement(int p, int q);
}
class UnionFind implements UF{
int[] parent;
int[] rank;
public UnionFind(int size){
parent = new int[size];
rank = new int[size];
for (int i = 0; i < parent.length; i++) {
parent[i] = i;
rank[i] = 1;
}
}
@Override
public int getSize(){
return parent.length;
}
private int find(int p){
if (p < 0 || p >= parent.length){
throw new IllegalArgumentException("索引值非法");
}
/**
* 只添加一句,让当前节点指向爷爷节点
*/
while (parent[p] != p){
parent[p] = parent[parent[p]];
p = parent[p];
}
return p;
}
@Override
public boolean isConnected(int p, int q){
return find(p) == find(q);
}
@Override
public void unionElement(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot){
return;
}
else {
if (rank[pRoot] < rank[qRoot]){
parent[pRoot] = qRoot;
}
else if (rank[pRoot] > rank[qRoot]){
parent[qRoot] = pRoot;
}
else {
parent[pRoot] = qRoot;
rank[qRoot]++;
}
}
}
}
最终优化(所有子节点直接指向根节点)
让所有子节点都直接指向根节点,树的高度始终只有两层
递归的寻找到每个节点的根节点,也会消耗相当多的性能,时间复杂度比路径压缩要高点
interface UF{
int getSize();
boolean isConnected(int p, int q);
void unionElement(int p, int q);
}
class UnionFind implements UF{
int[] parent;
int[] rank;
public UnionFind(int size){
parent = new int[size];
rank = new int[size];
for (int i = 0; i < parent.length; i++) {
parent[i] = i;
rank[i] = 1;
}
}
@Override
public int getSize(){
return parent.length;
}
private int find(int p){
if (p < 0 || p >= parent.length){
throw new IllegalArgumentException("索引值非法");
}
/**
* 递归的查找到一个节点的根节点
* 让其直接指向根节点,这样所有的节点都指向了根节点,树的高度只有两层
*/
if (parent[p] != p){
parent[p] = find(parent[p]);
}
return parent[p];
}
@Override
public boolean isConnected(int p, int q){
return find(p) == find(q);
}
@Override
public void unionElement(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot){
return;
}
else {
if (rank[pRoot] < rank[qRoot]){
parent[pRoot] = qRoot;
}
else if (rank[pRoot] > rank[qRoot]){
parent[qRoot] = pRoot;
}
else {
parent[pRoot] = qRoot;
rank[qRoot]++;
}
}
}
}
六种实现的性能比较
import java.util.Random;
public class Algorithm {
public static void main(String[] args) {
int[] scale = {10000, 100000};
int count = 100000;
for (int n : scale){
System.out.println("测试用例规模为" + n);
UnionFind unionFind = new UnionFind(n);
UnionFindOptimized unionFindOptimized = new UnionFindOptimized(n);
UnionFindOptimized2 unionFindOptimized2 = new UnionFindOptimized2(n);
UnionFindOptimized3 unionFindOptimized3 = new UnionFindOptimized3(n);
UnionFindOptimized4 unionFindOptimized4 = new UnionFindOptimized4(n);
UnionFindOptimized5 unionFindOptimized5 = new UnionFindOptimized5(n);
System.out.println("数组实现并查集:" + test(unionFind, count) + "秒");
System.out.println("树实现并查集:" + test(unionFindOptimized, count) + "秒");
System.out.println("优化size实现并查集:" + test(unionFindOptimized2, count) + "秒");
System.out.println("优化rank实现并查集:" + test(unionFindOptimized3, count) + "秒");
System.out.println("路径压缩优化实现并查集:" + test(unionFindOptimized4, count) + "秒");
System.out.println("最终优化实现并查集:" + test(unionFindOptimized5, count) + "秒");
System.out.println();
}
}
public static double test(UF uf, int count){
int size = uf.getSize();
Random random = new Random();
long startTime = System.nanoTime();
for (int i = 0; i < count; i++) {
int a = random.nextInt(size);
int b = random.nextInt(size);
uf.unionElement(a, b);
}
for (int i = 0; i < count; i++) {
int a = random.nextInt(size);
int b = random.nextInt(size);
uf.isConnected(a, b);
}
long endTime = System.nanoTime();
return (endTime - startTime) / 1000000000.0;
}
}
/**
* 最终优化
*/
interface UF{
int getSize();
boolean isConnected(int p, int q);
void unionElement(int p, int q);
}
class UnionFindOptimized5 implements UF{
int[] parent;
int[] rank;
public UnionFindOptimized5(int size){
parent = new int[size];
rank = new int[size];
for (int i = 0; i < parent.length; i++) {
parent[i] = i;
rank[i] = 1;
}
}
@Override
public int getSize(){
return parent.length;
}
private int find(int p){
if (p < 0 || p >= parent.length){
throw new IllegalArgumentException("索引值非法");
}
if (parent[p] != p){
parent[p] = find(parent[p]);
}
return parent[p];
}
@Override
public boolean isConnected(int p, int q){
return find(p) == find(q);
}
@Override
public void unionElement(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot){
return;
}
else {
if (rank[pRoot] < rank[qRoot]){
parent[pRoot] = qRoot;
}
else if (rank[pRoot] > rank[qRoot]){
parent[qRoot] = pRoot;
}
else {
parent[pRoot] = qRoot;
rank[qRoot]++;
}
}
}
}
/**
* 路径压缩优化
*/
class UnionFindOptimized4 implements UF{
int[] parent;
int[] rank;
public UnionFindOptimized4(int size){
parent = new int[size];
rank = new int[size];
for (int i = 0; i < parent.length; i++) {
parent[i] = i;
rank[i] = 1;
}
}
@Override
public int getSize(){
return parent.length;
}
private int find(int p){
if (p < 0 || p >= parent.length){
throw new IllegalArgumentException("索引值非法");
}
/**
* 只添加一句,让当前节点指向爷爷节点
*/
while (parent[p] != p){
parent[p] = parent[parent[p]];
p = parent[p];
}
return p;
}
@Override
public boolean isConnected(int p, int q){
return find(p) == find(q);
}
@Override
public void unionElement(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot){
return;
}
else {
if (rank[pRoot] < rank[qRoot]){
parent[pRoot] = qRoot;
}
else if (rank[pRoot] > rank[qRoot]){
parent[qRoot] = pRoot;
}
else {
parent[pRoot] = qRoot;
rank[qRoot]++;
}
}
}
}
/**
* 基于rank优化
*/
class UnionFindOptimized3 implements UF{
int[] parent;
int[] rank;
public UnionFindOptimized3(int size){
parent = new int[size];
rank = new int[size];
for (int i = 0; i < parent.length; i++) {
parent[i] = i;
rank[i] = 1;
}
}
@Override
public int getSize(){
return parent.length;
}
private int find(int p){
if (p < 0 || p >= parent.length){
throw new IllegalArgumentException("索引值非法");
}
while (parent[p] != p){
p = parent[p];
}
return p;
}
@Override
public boolean isConnected(int p, int q){
return find(p) == find(q);
}
@Override
public void unionElement(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot){
return;
}
else {
if (rank[pRoot] < rank[qRoot]){
parent[pRoot] = qRoot;
}
else if (rank[pRoot] > rank[qRoot]){
parent[qRoot] = pRoot;
}
else {
parent[pRoot] = qRoot;
rank[qRoot]++;
}
}
}
}
/**
* 基于size优化
*/
class UnionFindOptimized2 implements UF{
int[] parent;
int[] nums;
public UnionFindOptimized2(int size){
parent = new int[size];
nums = new int[size];
for (int i = 0; i < parent.length; i++) {
parent[i] = i;
nums[i] = 1;
}
}
@Override
public int getSize(){
return parent.length;
}
private int find(int p){
if (p < 0 || p >= parent.length){
throw new IllegalArgumentException("索引值非法");
}
while (parent[p] != p){
p = parent[p];
}
return p;
}
@Override
public boolean isConnected(int p, int q){
return find(p) == find(q);
}
@Override
public void unionElement(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot){
return;
}
else {
if (nums[pRoot] <= nums[qRoot]){
parent[pRoot] = qRoot;
nums[qRoot] += nums[pRoot];
}
else {
parent[qRoot] = pRoot;
nums[pRoot] += nums[qRoot];
}
}
}
}
/**
* 树结构实现
*/
class UnionFindOptimized implements UF{
int[] parent;
public UnionFindOptimized(int size){
parent = new int[size];
for (int i = 0; i < parent.length; i++) {
parent[i] = i;
}
}
@Override
public int getSize(){
return parent.length;
}
private int find(int p){
if (p < 0 || p >= parent.length){
throw new IllegalArgumentException("索引值非法");
}
while (parent[p] != p){
p = parent[p];
}
return p;
}
@Override
public boolean isConnected(int p, int q){
return find(p) == find(q);
}
@Override
public void unionElement(int p, int q){
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot){
return;
}
else {
parent[pRoot] = qRoot;
}
}
}
/**
* 数组实现
*/
class UnionFind implements UF{
int [] id;
public UnionFind(int size){
id = new int[size];
for (int i = 0; i < id.length; i++) {
id[i] = i;
}
}
@Override
public int getSize(){
return id.length;
}
private int find(int p){
if (p < 0 || p >= id.length){
throw new IllegalArgumentException("索引值非法");
}
return id[p];
}
@Override
public boolean isConnected(int p, int q){
return find(p) == find(q);
}
@Override
public void unionElement(int p, int q){
int pID = id[p];
int qID = id[q];
if (pID == qID){
return;
}
else {
for (int i = 0; i < id.length; i++) {
if (id[i] == pID){
id[i] = qID;
}
}
}
}
}