Union-Find

union-find用于动态连通性问题,根据某种连通关系,将可连通的两个集合合并在与同一集合中;当查询两个元素是否连通时,即判断是否处于同一集合。用于网络连通、变量名是否重名、数学集合等。


Union-find的描述
  • union-find

    数据结构:数组

    函数:初始化函数、unioned()函数、find()函数、isConnected()函数、count()函数

    用C++描述如下:

    class unionFind {
    private:
      int *site;
      int cnt = 0;
    public:
      unionFind(int N) {
      }
      void unioned(int p, int q) {
      }
      int find(int p) {
      }
      bool isConnected(int p, int q) {
      }
      int count(void) {
      }
    };

    部分函数的实现

    /*构造函数,初始化*/
    unionFind(int N) {
      cnt = N;
      site = new int[N];
      for(int i = 0; i < N; ++i) {
        site[i] = i;
      }
    }
    /*关于unioned,先判断是否处于同一连通集合中。若是,什么也不做;若不是,则使用合并两个集合*/
    
    /*关于find,只要将标记当前元素的集合的特征值返回即可*/
    
    /*判断两个元素所在集合是否连通*/
    bool isConnected(int p, int q) {
      return find(p) == find(q);
    }
    /*返回集合个数*/
    int count(void) {
      return cnt;
    }
  • Quick-find

    如果可以将 i 看作是元素,那么将site[ i ]作为标识位 i 所属的集合也是可实现的。

    那么find()函数的实现可以直接返回site[ i ]的值。

    int find(int p) {
    return site[p];
    }

    因此unioned()函数可以这样实现:

    void unioned(int p, int q) {
      int locOfP = find(p);
      int locOfQ = find(q);
      if(locOfP != locOfQ) {
        //两个集合并成一个,减少一个集合
        cnt--;
        //计算数组个数
        int size = sizeof(site) / sizeof(site[0]);
        for(int i = 0; i < size; ++i) {
          if(site[i] == locOfQ) site[i] = locOfP;
        }
      }
    }

    对于M次合并操作而言,每次合并都需要N+3次访问数组,也就是总共所需 M(N+3) 次的访问操作,如果有N个互不连通的元素都需要合并到同一个集合,那么需要N-1次合并操作,那么总访问次数就是 (N1)(N+3) 次,也近似为 N2 的访问次数。

  • Quick-union

    既然site[ i ]已经可以代表 i 所属的集合,那么直接将另一集合的值赋予site[ i ]就直接改变了 i 的所属集合。

    将上述用树的形式描述,就是将父结点降级为另一个父结点的字节点。

    unioned()函数的实现:

    void unioned(int p, int q){
      int rootOfP = find(p);
      int rootOfQ = find(q);
      if(rootOfP != rootOfQ) {
        site[rootOfQ] = rootOfP;
        cnt--;
      }
    }

    因此find()也相应的需要改变:

    int find(int p) {
      //反复迭代,找到根结点
      while(p != site[p]) p = site[p];
      return p;
    }

  • Weighted quick-union

    防止树形结构退化为链式结构,因此对根结点增加了权值。

    相应的也需要增加一些数据结构,需增加一个sz数组(与site数组声明方式相同),用于记录当前结点及子结点的个数(权值)。

    因此unioned()函数需修改为:

    void unioned(int p, int q) {
    int rootOfP = find(p);
    int rootOfQ = find(q);
    if(rootOfP != rootOfQ) {
      //树结点越多,树更可能越高
      if(sz[rootOfP] > sz[rootOfQ]) {
        site[rootOfQ] = rootOfP;
        sz[rootOfP] += sz[rootOfQ];
      }
      else {
        site[rootOfP] = rootOfQ;
        sz[rootOfQ] += sz[rootOfP];
      }
      cnt--;
    }
    }

    此外初始化函数也需要改变:

    unionFind(int N) {
    cnt = N;
    site = new int[N];
    for(int i = 0; i < N; ++i) site[i] = i;
    sz = new int[N];
    for(int i = 0; i < N; ++i) sz[i] = 1;
    }

  • Path compression

    可将部分结点直接上移到根结点上,从而减少find()函数的访问次数。

    对于find()函数的修改:

    int find(int p) {
    while(p != site[p]) {
      site[p] = site[site[p]];
      p = site[p];
    }
    return site[p];
    }

Union-find’s ADT
class unionFind {
private:
  int *site = null;
  int *sz = null;
  int cnt = 0;
public:
  unionFind(){}
  unionFind(int N) {
    cnt = N;
    site = new int[N];
    for(int i = 0; i < N; ++i) site[i] = i;
    sz = new int[N];
    for(int i = 0; i < N; ++i) sz[i] = 1;
  }
  ~unionFind() {
    delete []site;
    delete []sz;
  }
  int find(int p) {
    while(p != site[p]) {
      site[p] = site[site[p]];
      p = site[p];
    }
  }
  void unioned(int p, int q) {
    int rootOfP = find(p);
    int rootOfQ = find(Q);
    if(rootOfP != rootOfQ) {
      if(sz[rootOfP] > sz[rootOfQ]) {
        site[rootOfQ] = rootOfP;
        sz[rootOfP] += sz[rootOfQ];
      } else {
        site[rootOfP] = rootOfQ;
        sz[rootOfQ] += sz[rootOfP];
      }
      cnt--;
    }
  }
  bool isConnected(int p, int q) {
    return find(p) == find(q);
  }
  int count(void) {
    return cnt;
  }
};
延伸阅读

原文链接:1.5 Case Study: Union-Find-Princeton University.

中文翻译版:并查集(Union-Find)算法介绍 - dm_vincent.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值