小橙书阅读指南(十三)——连通性算法(union-find)

上一章我大概说明了什么是图论以及无向图的基础概念,本章咱们要研究一种更广泛的算法——连通性算法。它属于图论的分支,也是一种抽象算法。在深刻算法以前,咱们先提出一个具体的问题:假设在空间中存在N个点,咱们能够经过线段链接任意两点,相互链接的点属于同一组连通份量,咱们如何计算点p和点q之间是否连通。算法的核心是:如何表示连通性以及如何检查连通性。算法

下面提供算法的抽象接口:数组

/**
 * 连通性算法
 */
public interface UnionFind {
    /**
     * p点和q点之间添加一条通路
     *
     * @param p
     * @param q
     */
    void union(int p, int q);

    /**
     * 获取p点的连通份量
     *
     * @param p
     * @return
     */
    int find(int p);

    /**
     * 判断p点和q点是否存在一条通路
     *
     * @param p
     * @param q
     * @return
     */
    boolean connected(int p, int q);

    /**
     * 连通份量的数量
     *
     * @return
     */
    int count();
}

1、快速查询算法Quick-Findide

咱们将空间中的点这一律念抽象成int[](整形数组),i表明不一样的点int[i]表明不一样的连通份量。一种比较容易理解的想法是,从属于同一组连通份量的任一点p和q一定int[p]等于int[q]。所以当咱们须要查询点p和q是否连通的时候只须要判断int[p] == int[q]是否成当即可。学习

/**
 * 连通性算法:quick-find
 */
public class QuickFind implements UnionFind {
    private int[] id; // 份量id
    private int count;

    public QuickFind(int n) {
        count = n;
        id = new int[n];
        for (int i = 0; i < n; i++) {
            id[i] = i;
        }
    }

    @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--;
    }

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

    @Override
    public boolean connected(int p, int q) {
        return find(p) == find(q);
    }

    @Override
    public int count() {
        return count;
    }
}

find()操做的速度显然是很快的,由于它只须要访问id[]数组一次。可是对于每一对数组union()都须要扫描整个id[]数组。所以quick-find算法通常没法处理大型数组。ui

算法图示:spa

2、快速链接算法Quick-Unioncode

咱们要讨论的下一个算法的重点是提升union()方法的速度,为此可能会稍微牺牲一下find()的效率,可是一般状况下这样作是值得的。Quick-Union算法考虑把属于同一组连通份量的点链接成一棵树,i表明点,int[i]表明i的父节点,根节点p等于int[p]。blog

public class QuickUnion implements UnionFind {
    private int count;
    private int[] id;

    public QuickUnion(int n) {
        count = n;
        id = new int[n];
        for(int i = 0; i < n; i++) {
            id[i] = i;
        }
    }
    @Override
    public void union(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if(pRoot == qRoot) {
            return;
        }

        id[pRoot] = qRoot;
        count--;
    }

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

    @Override
    public boolean connected(int p, int q) {
        return false;
    }

    @Override
    public int count() {
        return 0;
    }
}

快速链接算法的每一次链接会分别遍历两次连通份量,在连通份量中包含元素数量相对总数而言比较小的状况下能够提供很是不错的速度。接口

算法图示:io

 

事实上,不管是Quick-Find算法仍是Quick-Union算法,他们在图论的基础上基本是起到相互补充的做用。更重要的一点是,咱们经过对他们的学习能够认识到,十全十美的算法很难实现,更多的时候算法针对某一个问题的痛点才是有效的。

相关文章
相关标签/搜索