【慢速学数据结构】集合(并查集)篇

概述

上一篇实现的集合其实没什么卵用,跟个vector似的。并查集就比它厉害多了。

比如,有这么个场景。

有10台电脑(1,2,3…10),已知这些电脑之间已经实现了连接,1和2,2和4,3和5,4和7,5和8,6和9,6和10。
问:2和7之间是否连接?

上一篇实现的集合对解决这个问题没有任何帮助,然而用并查集就轻松多了,因为它能快速判断一个元素和另一个元素是否属于同一个集合,有多快?O(logn),如果使用了路径压缩优化的话,能达到渐进下的O(1),而且它只有两个操作,find和union,很简单。

实现

类声明:

template<typename T>
class DisjointSet {
public:
    set(int maxsize);
    virtual ~set();

public:
    typedef struct {
        T data;
        int parent;
    } SetType;

    // insert a element as a unique set.
    void insert(const T& data);
    int find(const T& data);
    int setUnion(const T& dataA, const T& dataB);

private:
    vector<SetType> container_;

};

为什么要这么设计类?

  • 初始化一个固定大小的数组来表示树,可以增加效率。
  • 简洁,与并查集的特点相符。

所以,当要判断两个元素是否属于同一个集合,只要find(a)==find(b)就行了,前提是这两个元素要先放到数组里面去。

来看一下find函数。思路就是先遍历一遍数组,看这个元素是否存在,然后去根据它的parent值往上寻找根结点,返回根节点的索引。所谓的路径压缩,就是把每个结点的父节点都直接指向它的根结点,从而减少下次查找的时间。

    int find(const T& x) {
        auto i = 0;
        // traversal the container to get the index of x
        for( ; i< container_.size() && container_[i].data_ != x ; i++)  ;

        // if didn't find return -1
        if( i == container_.size()) return -1;

        // get x 's root
        auto ti = i; // for path compression
        while( container_[i].parent_ > 0) {
            i = container_[i].parent_;
        }

        // path compression
        while( container_[ti].parent_ > 0) {
            int before = ti;
            ti = container_[ti].parent_;
            container_[before].parent_ = i;
        }

        // find it, return index of root
        return i;
    }

来看一下union函数。思路就是先找到两个元素各自的根结点,然后将一方的根节点指向另一方的根节点就行了。这里还有个优化,就是按高度求并,也就是尽量让矮的树挂到高的树上,这样的好处是可以保证并完后的树的高度,只会在两棵树高度相等的时候增加1。

    int setUnion( const T& a, const T& b) {
        root1 = find(a);
        root2 = find(b);

        int ht1 = container_[root1].parent_, ht2 = container_[root2].parent_;
        int newHight = ht1 + ht2;

        if(ht1 > ht2) 
            container_[root2].parent_ = root1;
        else 
            container_[root1].parent_ = root2;

    }

应用

用它来做一下文章一开始提出来的那个计算机连接的问题。

        DisjointSet<int> s(100);

        for(auto i=1; i<11; i++)
            s.insert(i);

        s.setUnion(1, 2);
        s.setUnion(2, 4);
        s.setUnion(3, 5);
        s.setUnion(4, 7);
        s.setUnion(5, 8);
        s.setUnion(6, 9);
        s.setUnion(6, 10);

        // then the sets would be {1,2,4,7} {5,3,8} {9,6,10}
        assert( s.find(2) == s.find(7)); // so this is true

听说这个东西刷oj也很爽,来两道?

http://poj.org/problem?id=1611
http://poj.org/problem?id=2524

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值