并查集

/**
*@ author StormMaybin
*@ Date 2016-09-13
*/

生命不息,奋斗不止!


Union-Find算法

API

Public class UF

UF(int N)                       //以整数标识(0-N-1)初始化
void union (int p. int q)      //连通p和q
int find (int p)              //p所在的分量标识
boolean connected (int p, int q)    //判断p和q是否在同一个分量中
int count ()                //连通分量的个数

UF算法的初衷是表示连通类的问题,选择的数据结构正好是数组这种最简单常用的数据结构。

Quick-Find算法实现

package com.stormma.qf;

import java.io.BufferedInputStream;
import java.util.Scanner;

public class QF
{
    private int [] id;    //分量id
    private int count;    //分量数量
    public QF (int N)
    {
        //初始化分量id数组
        count = N;
        id = new int [N];
        for (int i = 0; i < N; i++)
            id[i] = i;
    }
    public int count ()
    {
        return count;
    }
    public boolean connected (int p, int q)
    {
        return find(p) == find(q);
    }
    public int find (int p)
    {
        return id [p];
    }
    public void union (int p, int q)
    {
        //将p和q归并到相同的分量中
        int pID = find(p);
        int qID = find(q);

        //如果p和q已经在相同的分量中则不需要再次连通
        if (pID == qID)
        {
            return ;
        }
        //将p的分量命名为q的名称
        for (int i = 0; i < id.length; i++)
        {
            if (id[i] == pID)
                id[i] = qID;
        }
        count --;
    }
    public static void main (String [] args)
    {
        Scanner scan = new Scanner(new BufferedInputStream(System.in));
        int N = scan.nextInt();
        UF qf = new QF(N);
        while (scan.hasNext())
        {
            int p = scan.nextInt();
            int q = scan.nextInt();
            if (qf.connected(p, q))
                continue;
            else
                qf.union(p, q);
            System.out.println(p+"---"+q);
        }
        scan.close();
    }
}

我们从上面代码中可以看出,find的时间复杂度是O(1),但是Union时间复杂度是O(n)。如果我们期望最后得到一个连通分量,那么我们需要调用N-1次union(),那么算法复杂度是(N+3)(N-1)~N^2。

Quick Union

旨在提高union()方法的速度。我们可以稍微修改一下find()方法和union()方法,来得到稍微快一点的union。

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

现在我们来看一下QF和QU到底什么区别,QF中,id[p]标识p的祖先,对于QF操作union的时候,假设这时候把p分量合并到q分量中去,那么p的这个分量中的所有触点的祖先都是q这个分量的祖先,所以我们在union()方法中遍历开始修改祖先是p的触点的祖先为q分量的祖先。
但是对于QU却不是这样的,QU中定义的id[p]标识的是p的父亲,现在我们要找到p的祖先只能一直网上找(找p的父亲,如果p的父亲不是祖先,那我们接着找p的父亲的父亲······)。然后操作union的时候我们找到了p的祖先,合并p和q分量的时候,只需要把p的祖先的父亲设置为q的祖先,这样就可以了,这样看来union速度确实是快了一点,但是find速度变慢了。假设这时候我们有一组数是0 1 2 3 4 5 6 ······N-1,我们先把0连接到1,然后把1连接到2,2连接到3,N-2连接到N-1,这时候我们需要访问find()方法的次数就变成了2(1+2+3+······N)~N^2。继续改进算法

Weighted Quick - union (加权QU)

package com.stormma.wqu;

public class WQU
{

    private int[] id;
    private int[] sz;// 分量大小
    private int count;

    public WQU(int N)
    {
        count = N;
        id = new int[N];
        for (int i = 0; i < N; i++)
            id[i] = i;
        sz = new int[N];
        for (int i = 0; i < N; i++)
            sz[i] = 1;
    }

    public int count()
    {
        return count;
    }

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

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

    public void union(int p, int q)
    {
        int i = find(p);
        int j = find(q);
        if (i == j)
            return;
        // 小数根节点连到大数根节点
        if (sz[i] < sz[j])
        {
            id[i] = j;
            sz[j] += sz[i];
        }
        else
        {
            id[j] = i;
            sz[i] += sz[j];
        }
        count --;
    }

}

前面的QU之所以慢,是因为最坏情况下会出现把大数连接到小数的情况,那么我们在WQU这算法中维护一个数组,来维护i这个结点的深度(即大小),这样我们在操作union的时候,我们始终把小的树连接到大树上去!代码中sz数组就是这个含义。其他地方和QU算法差不多。

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