并查集与优化

 

并查集,就如同它的名字一般。对集合进行查询,合并。

我们用一道题学习并查集。

hdu1232 畅通工程

 

有n个城市,城市之间一共有m条道路,给出两两城市有路,求再建设几条路就可以完成所有道路的互通。

我们将互通的城市建立一个树,例如第一组测试数据

4 2

1 3

4 3

现在我们把互通的城市连一条线。就形成了一颗树。现在一共两颗树,我们将两颗树连一条线。就形成了所有的道路变成了互通。输出就为1.

那我们怎么将这些节点变成一个树呢?

我们要借助一个数组模拟一下树。

static int set[]; //初始化 set[i] = i; 

当i与j是一颗树时,将set[i] = j; or set[j] = i;

set数组就形成了一颗颗树。例如:set[3] = 2; set[2] = 1;set[1] = 1;

{1,2,3}在set集合中为一颗树。

我们怎么去操作查询两个节点同一集合呢。

 public static int find(int k) {
        int x = k;
        while(x != set[x]) {
            x = set[x];
        }
        return x;
}

我们先将一个节点一直查询它的父节点。将它变得它的父节点。在接着刚刚的操作。直到它的父节点与本身一致。在操作另一个节点。如果两个节点的根节点一致说明两个节点为一个树,在一个集合中。但如果两个根节点不一致的情况,说明两个节点不在同一集合内。

例如刚刚的 :set[3] = 2; set[2] = 1;set[1] = 1;

我们查询1,2是否在一个集合中。

我们先查询节点1.x = 1 进行循环查找set  ,   

x = set[x];

 x = set[x(x = 1)]; (x这时变成了2)

x = set[x (x = 2)],(x这时变成了3)

节点1的根节点为3

我们再去查找节点2, 同上方法一致。 x = set[x (x = 2)],(x这时变成了3)

节点2的根节点为3

说明1,2为同一集合。

两个集合怎么合并呢?

	public static void merge(int k, int l) {
		int x = find(k);
		int y = find(l);
		if (x != y) {
			set[x] = y;
		}
	}

我们找到两个节点的根节点。将两个根节点合并就完成了两个集合合并过程。借助查找集合根节点。将查到的一个根节点的父节点变成另一个根节点就可以了。

set[x] = y;

这步操作完成两个集合合并。

并查集的学习就完成了。

这道题运用并查集可以写成:

import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

public class Main{
    static int set[];
    @SuppressWarnings("resource")
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            int n = scanner.nextInt();
            if (n == 0) {
                break;
            }
            int m = scanner.nextInt();
            set = new int[n + 1];
            for (int i = 0; i <= n; i++) {
                set[i] = i;
            }
            for (int i = 0; i < m; i++) {
                int x = scanner.nextInt();
                int y = scanner.nextInt();
                bit(x, y);
            }
            int sum = 0;
            for (int i = 1; i < set.length; i++) {
                if (set[i] == i) {
                    sum++;
                }
            }
            System.out.println(sum - 1);
            
        }
    }
    public static void bit(int k, int l) {
        int x = find(k);
        int y = find(l);
        if (x != y) {
            set[x] = y;
        }
    }
    public static int find(int k) {
        int x = k;
        while(x != set[x]) {
            x = set[x];
        }
        return x;
    }
}

这种方式的并查集可以优化。

如同左图我们查询节点3,4是否为一个集合时,我们从4查到3,查到2,查到1。浪费了时间。

我们就可以将左图变成右图。这样查询节点4时,直接就查到了节点1.

我们怎么操作呢?

	public static int find(int k) {
		if (k == set[k]) {
			return k;
		}
		return set[k] = find(set[k]);
	}

我们只需要在查询过程中,边查询边更新节点的父节点,让其变成根节点就可以了。

这样我们就完成了路径压缩的过程。

简单的自我理解,将就看。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值