赞
踩
目录
我们可以想象这样一个过程,开始时有n个元素,某些元素开始和其他元素按照一定规律进行集合合并,有可能就会分成几个集合。在这个过程中要反复某个元素是哪个集合的,这样的运算,被抽象成数据类型叫做“并查集”;
还是不太理解?举个例子
例如:现在有10个人,分别对其进行编号,下标都为-1(为什么是-1,后面会解释),如下图:
现在将这个一个个零散人组建成如下三个团体:
他们的下标被修改为:
解释:
1. 数组的下标对应集合中元素的编号
2. 数组中如果为负数,负号表示当前下标为根结点,数字代表该集合中元素个数
3. 数组中如果为非负数,他的下标代表该元素的双亲结点
若继续合并,分成了如下两个小团体,数组变化如下:
通过以上栗子,可以发现,并查集可以解决以下问题:
1.查找元素属于哪个集合
方法:通过树一直找到根,也就是数组中的负数
2.查看两个元素是否属于同一个集合
方法:通过树一直找到根,若根相同说明是同一集合;
3.将两个集合合并成一个集合
方法:将一个集合名称改成另一个集合名称;
4.求集合个数
方法:数组中元素为负数是当前下标集合的个数;
底层是一个数组,并且全部初始化为-1;
- public class UnionFindSet {
- public int[] elem;
-
- public UnionFindSet(int n) {
- this.elem = new int[n];
- Arrays.fill(elem, -1);
- }
- }
这里实现很简单,只需要比较两个元素的根节点是否相同即可,那么就需要先找到两个根节点,可以通过一个循环进行查找,当查找到的元素小于0时,说明找到根节点了;
- /**
- * 查找数组 x下标是否是根节点
- * @param x
- * @return
- */
- public int findRoot(int x) {
- if(x < 0) {
- throw new IndexOutOfBoundsException("下表不合法,是负数");
- }
- while(elem[x] >= 0) {
- x = elem[x];
- }
- return x;
- }
-
- /**
- * 查询 x1 和 x2 是不是同一个集合
- * @param x1
- * @param x2
- * @return
- */
- public boolean isSameUnionFindSet(int x1, int x2) {
- int index1 = findRoot(x1);
- int index2 = findRoot(x2);
- return index1 == index2;//跟结点是否相同
- }
这里首先需要找到需要合并的两个元素的根节点,只有根节点不同才能进行合并,怎么合并呢?就是修改两个值,元素一的值因该被修改为这两个元素的和(这是更新孩子的数量),元素二的值因该被修改为元素一的下标(这是跟新另一颗树的根结点)(这里也很简单,建议参照上面我画的图去对比);
- /**
- * 合并操作
- * @param x1
- * @param x2
- */
- public void union(int x1, int x2) throws Exception {
- //先找到 x1, x2 的根节点
- int index1 = findRoot(x1);
- int index2 = findRoot(x2);
- if(index1 == index2) {
- return;
- }
- elem[index1] = elem[index1] + elem[index2];
- elem[index2] = index1;
- }
遍历数组,并用一个计数器记录负数的个数,就是集合的个数;
- /**
- * 集合个数
- * @return
- */
- public int getCount() {
- int count = 0;
- for(int x : elem) {
- if(x < 0) {
- count++;
- }
- }
- return count;
- }
- import java.util.Arrays;
-
- public class UnionFindSet {
- public int[] elem;
-
- public UnionFindSet(int n) {
- this.elem = new int[n];
- Arrays.fill(elem, -1);
- }
-
- /**
- * 查找数组 x下标是否是根节点
- * @param x
- * @return
- */
- public int findRoot(int x) {
- if(x < 0) {
- throw new IndexOutOfBoundsException("下表不合法,是负数");
- }
- while(elem[x] >= 0) {
- x = elem[x];
- }
- return x;
- }
-
- /**
- * 查询 x1 和 x2 是不是同一个集合
- * @param x1
- * @param x2
- * @return
- */
- public boolean isSameUnionFindSet(int x1, int x2) {
- int index1 = findRoot(x1);
- int index2 = findRoot(x2);
- return index1 == index2;//跟结点是否相同
- }
-
- /**
- * 合并操作
- * @param x1
- * @param x2
- */
- public void union(int x1, int x2) {
- //先找到 x1, x2 的根节点
- int index1 = findRoot(x1);
- int index2 = findRoot(x2);
- if(index1 == index2) {
- return;
- }
- elem[index1] = elem[index1] + elem[index2];
- elem[index2] = index1;
- }
-
- /**
- * 集合个数
- * @return
- */
- public int getCount() {
- int count = 0;
- for(int x : elem) {
- if(x < 0) {
- count++;
- }
- }
- return count;
- }
-
- public void print() {
- for(int x : elem) {
- System.out.print(x + " ");
- }
- System.out.println();
- }
-
- /**
- * 测试
- * @param args
- */
- public static void main(String[] args) {
- UnionFindSet unionFindSet = new UnionFindSet(10);
- System.out.println("合并:0 和 6:");
- unionFindSet.union(0, 6);
- System.out.println("合并:0 和 7:");
- unionFindSet.union(0, 7);
- System.out.println("合并:0 和 8:");
- unionFindSet.union(0, 8);
-
- System.out.println("合并:1 和 4:");
- unionFindSet.union(1, 4);
- System.out.println("合并:1 和 9:");
- unionFindSet.union(1, 9);
- System.out.println("合并:2 和 3:");
- unionFindSet.union(2, 3);
- System.out.println("合并:2 和 5:");
- unionFindSet.union(2, 5);
- unionFindSet.print();
-
- System.out.println("合并:8 和 1:");
- unionFindSet.union(8, 1);
- unionFindSet.print();
- System.out.println(unionFindSet.isSameUnionFindSet(6, 9));
- System.out.println(unionFindSet.isSameUnionFindSet(8, 2));
- }
- }
面试考的少,自行斟酌~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。