动态连通性问题:union-find算法

问题描述是这样的:
输入一列整数对,每个整数对(p,q)表示p和q是相连的,每个点称作触点,每个相连的触点的集合称作连通分量。
输出所有不能相连的整数对(即连通分量)的数量

通过union-find算法解决该问题。其API如下:

方法描述
UF(int N)初始化N个触点
void union(int p, int q)连接p和q
int find(int p)p所在连通分量的标识符
boolean connected(int p, int q)如果p和q在同一个连通分量中则返回true
int getCount()连通分量的数量

有三种实现方法,先把它们的共同点拿出来作为基类和接口

// 算法各个实现的基类
class UF {
    protected int[] id;   // 以每个触点为索引的连通分量的id
    protected int count;  // 连通分量的数量
    public int getCount() {return count;}
}
// union-find算法的各个实现只有find()和union()不同
interface union_find {
    public abstract int find(int p);
    public abstract void union(int p, int q);
    // 根据id[]来确定两个触点是否位于同一个连通分量
    public default boolean connected(int p, int q) {return find(p) == find(q);}
}

quick-find算法

该实现保证id[p] == id[q]时p和q连接,即要保证同一个连通分量中所有触点的id[]值都相同。
其时间复杂度为 O ( n ² ) O(n²) O(n²)

class QuickFind extends UF implements union_find {
    public QuickFind(int N) {
        count = N;
        id = new int[N];
        // 初始化,为每个连通分量分配一个id
        for (int i = 0; i < N; i++)
            id[i] = i;
    }

    @Override
    public int find(int p) {
        return id[p];
    }

    @Override
    public void union(int p, int q) {
        int pID = find(p);
        int qID = find(q);
        if (pID == qID)
            return;
        for (int i = 0; i < id.length; i++) {
            if (id[i] == pID)
                id[i] = qID;
        }
        count--;
    }
}

quick-union算法

该实现相当于构造了一个森林,每一个连通分量都是一棵树。
每个触点所对应的id[]值都是它所在的连通分量里的另一个触点(或者它自己,即为根触点)。
当两个触点的根触点相同时,说明他们在同一个连通分量中。
其最坏情况下的时间复杂度也为 O ( n ² ) O(n²) O(n²)

class QuickUnion extends UF implements union_find {
    public QuickUnion(int N) {
        count = N;
        id = new int[N];
        for (int i = 0; i < N; i++)
            id[i] = i;
    }

    @Override
    public int find(int p) {
        while (p != id[p])
            p = id[p];
        return p;
    }

    @Override
    public void union(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if (pRoot == qRoot)
            return;
        id[pRoot] = qRoot;
        count--;
    }
}

加权quick-union算法

第三种实现是对第二种的改进。
改进之处在于会记录每棵树的大小,只将较小的书链接到较大的树上。有效地降低了树的高度,使其时间复杂度下降到 O ( l o g n ) O(logn) O(logn)

class WeightedQuickUnion extends QuickUnion implements union_find {
    private int[] size; // 每个连通分量(树)的大小
    public WeightedQuickUnion(int N) {
        super(N);
        size = new int[N];
        for (int i = 0; i < N; i++)
            size[i] = 1;
    }

    @Override
    public void union(int p, int q) {
        int i = find(p);
        int j = find(q);
        if (i == j)
            return;
        if (size[i] < size[j]) {
            id[i] = j;
            size[j] += size[i];
        }
        else {
            id[j] = i;
            size[i] += j;
        }
        count--;
    }
}

使用路径压缩的方法可以再做一点改进,让树更加扁平化。
做法是在检查触点的同时将它们全部链接到根触点上。

@Override
public int find(int p) {
    int temp = p;
    // 找到根触点
    while (p != id[p])
        p = id[p];
    // 把沿途的触点都直接链接到根触点p上
    while (temp != id[temp])
        id[temp] = p;
    return p;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值