数据结构基础_并查集(UnionFind)

一. 认识并查集

  • 可以高效的解决连接问题(Connectivity Problem)
  • 检查网络中节点间的连接状态(网络是个抽象概念:用户之间形成的网络)
  • 数学中的集合类实现(合并问题)
  • 连接问题和路径问题:连接问题只需回答是或否,而路径问题要回答出具体的路径;
  • 对于一组数据,并查集主要支持三个动作:
  1. union(p, q) -----------------并操作,将元素p,q并入同一个组内
  2. find(p)------------------------查操作,返回元素所在组号,通常是一个private的辅助函数
  3. isConnected(p, q)---------查操作,查询元素p,q是否在一个组内(同一个组内的元素是相互连接的)

二.并查集的实现

1. Quick Find并查集:

基本数据表示:

  • 并查集内部实际是存储了一个数组,数组的索引(id)表示元素的编号(这里的元素可以是各种类型),索引存储的值表示该元素所属的集合,而属于同一集合的元素即是连接起来的元素(下图,id为0,2,4,6,8的元素同属于集合0,是连接起来的)。

数据结构基础_并查集(UnionFind)_第1张图片

  • Quick Find并查集,实现一个了一个find函数,用来支持快速查询操作,查询操作时间复杂度为O(1), 合并操作时间复杂度为O(n);

数据结构基础_并查集(UnionFind)_第2张图片数据结构基础_并查集(UnionFind)_第3张图片数据结构基础_并查集(UnionFind)_第4张图片

package UFs;

import UnionFind.UnionFind;

public class quickFindUF implements UnionFind {
    //维护一个数组,数组的索引表示不同的元素(元素可以是各种类型),索引所对应的值表示该索引所属的集合
    private int[] id;

    public quickFindUF(int size){
        id = new int[size];

        //初始化每个元素都属于不同的集合
        for(int i=0; i

 

2. Quick Union并查集:

基本数据表示:

  • 将每一个元素,看作是一个节点,是一种奇怪的树结构,是由孩子节点指向父亲节点
  • 根节点的指针指向自己,合并两个元素,就是将其中一个元素的根节点指向另一个元素的根节点

数据结构基础_并查集(UnionFind)_第5张图片

  • 底层仍然可以通过维护一个数组来实现,这里,数组的索引仍然表示元素的编号(元素可以是各种类型),但是,索引下存储的值表示的是该元素指向的父亲节点。注意:这里,下标表示元素,下标存储的值表示该下标元素的父亲元素
  • 查询操作时间复杂度为O(h), 合并操作时间复杂度也为O(h)  (h为当前树的深度);

数据结构基础_并查集(UnionFind)_第6张图片

  • 初始化时:

数据结构基础_并查集(UnionFind)_第7张图片

package UFs;

import UnionFind.UnionFind;

public class quickUnionUF implements UnionFind {
    //内部维护一个数组,数组索引表示元素编号,索引内存储的值表示该索引的父亲编号
    //合并两个元素,就是将其中一个元素的根节点的父亲节点设为另一个元素的根节点
    //这里,下标表示元素,下标存储的值表示该下标元素的父亲元素
    private int[] parent;

    public quickUnionUF(int size){
        parent = new int[size];

        //初始化并查集,将每个元素的父亲节点都设为自己
        for(int i=0; i

quickUnion并查集基于size的优化

  • 基于size的优化,即维护一个sz数组,记录每个根节点所包含元素的个数,在合并元素时,将个数小的树合并到个数大的树上,避免合并后整棵树深度太深
  • sz[i]表示以i为根的集合所表示的树的总节点个数
package UFs;

import UnionFind.UnionFind;

//基于size的优化,即维护一个sz数组,记录每个根节点所包含元素的个数,在合并元素时,将个数小的树合并到个数大的树上,避免合并后整棵树深度太深
public class quickUnionUFII implements UnionFind {
    //内部维护一个数组,数组索引表示元素编号,索引内存储的值表示该索引的父亲编号
    //合并两个元素,就是将其中一个元素的根节点的父亲节点设为另一个元素的根节点
    //这里,下标表示元素,下标存储的值表示该下标元素的父亲元素
    private int[] parent;
    private int[] sz;  //用于记录每个根节点所拥有的节点个数,sz[i]表示以i为根的集合所表示的树的节点的总个数

    public quickUnionUFII(int size){
        parent = new int[size];
        sz = new int[size];

        for(int i=0; i

quickUnion并查集基于rank的优化

  • 好基于rank的优化,即维护一个rank数组,记录每个根节点的层数,在合并元素时,将层数小的树合并到个层数大的树上,避免合并后整棵树深度太深
  • rank[i]表示以i为根的集合所表示的树的层数
package UFs;

import UnionFind.UnionFind;

//基于rank的优化,即维护一个rank数组,记录每个根节点的层数,在合并元素时,将层数小的树合并到个层数大的树上,避免合并后整棵树深度太深
public class quickUnionUFIII implements UnionFind {
    //内部维护一个数组,数组索引表示元素编号,索引内存储的值表示该索引的父亲编号
    //合并两个元素,就是将其中一个元素的根节点的父亲节点设为另一个元素的根节点
    //这里,下标表示元素,下标存储的值表示该下标元素的父亲元素
    private int[] parent;
    private int[] rank;  //用于记录每个根节点的层数,rank[i]表示以i为根的集合所表示的树的层数

    public quickUnionUFIII(int size){
        parent = new int[size];
        rank = new int[size];

        for(int i=0; irank[qRoot]){
            parent[qRoot] = pRoot;
        }else {
            parent[qRoot] = pRoot;
            rank[pRoot]++;
        }
    }
}

 

quickUnion并查集经典的“路径压缩”的优化

  • 在find过程中实现

数据结构基础_并查集(UnionFind)_第8张图片数据结构基础_并查集(UnionFind)_第9张图片

package UFs;

import UnionFind.UnionFind;

//基于rank的优化,即维护一个rank数组,记录每个根节点的层数,在合并元素时,将层数小的树合并到个层数大的树上,避免合并后整棵树深度太深
//同时做了一个经典的优化,在find过程中添加了路径压缩优化
public class quickUnionUFIV implements UnionFind {

    //内部维护一个数组,数组索引表示元素编号,索引内存储的值表示该索引的父亲编号
    //合并两个元素,就是将其中一个元素的根节点的父亲节点设为另一个元素的根节点
    //这里,下标表示元素,下标存储的值表示该下标元素的父亲元素
    private int[] parent;
    private int[] rank;  //用于记录每个根节点的层数,rank[i]表示以i为根的集合所表示的树的层数

    public quickUnionUFIV(int size){
        parent = new int[size];
        rank = new int[size];

        for(int i=0; irank[qRoot]){
            parent[qRoot] = pRoot;
        }else {
            parent[qRoot] = pRoot;
            rank[pRoot]++;
        }
    }
}

quickUnion并查集经典的“路径压缩”的优化,递归实现

  • 可以在find(i)时,直接将i的parent设为根节点,时间性能上稍逊于非递归实现的“压缩路径”;
  • 非递归的“压缩路径”虽然不能在一次调用find(i)后就达到将i的parent设为根节点的效果,但多次对i调用find(i)后是可以的,避免了递归过程中花费的开销

数据结构基础_并查集(UnionFind)_第10张图片

package UFs;

import UnionFind.UnionFind;

//基于rank的优化,即维护一个rank数组,记录每个根节点的层数,在合并元素时,将层数小的树合并到个层数大的树上,避免合并后整棵树深度太深
//"压缩路径"优化的递归实现,可以在find(i)时,直接将i的parent设为根节点,性能上稍微逊与非递归的“压缩路径”
//因为非递归的“压缩路径”在多次对i进行find(i)后,也可达到将i的parent设为根节点的效果,避免了递归的开销。
public class quickUnionUFV implements UnionFind {

    //内部维护一个数组,数组索引表示元素编号,索引内存储的值表示该索引的父亲编号
    //合并两个元素,就是将其中一个元素的根节点的父亲节点设为另一个元素的根节点
    //这里,下标表示元素,下标存储的值表示该下标元素的父亲元素
    private int[] parent;
    private int[] rank;  //用于记录每个根节点的层数,rank[i]表示以i为根的集合所表示的树的层数

    public quickUnionUFV(int size){
        parent = new int[size];
        rank = new int[size];

        for(int i=0; irank[qRoot]){
            parent[qRoot] = pRoot;
        }else {
            parent[qRoot] = pRoot;
            rank[pRoot]++;
        }
    }
}

并查集的时间复杂度分析:

数据结构基础_并查集(UnionFind)_第11张图片

你可能感兴趣的:(数据结构)