1、不相交集类的算法:
这是处理等价问题的一种算法,这里的等价指的是集合,a与b等价,是指a∈S,b∈S。通过不相交集类的两个基本操作find就可找出a和b的集合标志是否一致,若一致则说明它们是一个集合的。当然,对于集合的表示,用的是森林的数据结构。集合的标志就是根节点,若根节点一样,就说明集合一样。而除了唯一的根节点之外,剩下的节点里面存放都是父节点的标志。因此,我们只需要一个数据就能表示一个不相交集的结构了。不相交集类的另一个操作就是union。意图是把两个不属于同一集合的元素,进行合并,使其等价
在union(int root1,int root2)操作中,合并的方式是有三种:
第一种是按照root1为根,root2为子节点的方式,也就是如果root1和root2合并的话,就需要把root2中原本存放的集合标志,换成指向root1.
第二种是集合中节点数个数的大小来判断谁合并谁;若root1的个数大于root2的个数,则root1为根,root2为节点;
第三种是集合中树的高度的大小来判断谁合并谁;若root1的高度大于root2的高度,则root1为根,root2为节点;
第二、三种之所以这么做的原因是,按照第一种的方法,最终导致的是树变得太深,每次的find操作消耗的时间长。通过控制个数或者高度,可以有效的减缓树变得越来越深。当然,最终树的深度依旧会变大。尝试着在find操作进行优化
find路径压缩算法:
每次,在find(int x)找到跟节点之后,就将这个x添加到这个根节点的下面。那么,下次寻找x的根的时候,其实,只要进行一次搜索即可,也就意味这递归的次数将大大减少。这是牺牲了树的广度来减小树的深度。事实上,这种方法很有效,因为,我们的不相交集类是通过一个一维数组来储存的,广度的大小根本就不需要在意
import java.util.ArrayList;
/**
* 不相交集类
*
* @author Holy-Spirit
*
*/
public class DisjSet {
private int length;
private int[] array;
/**
* 初始化数组为-1
*
* 其中符号,代表的是这是根节点,也就是表示这个下标是这棵树的标志 符号后面的值为这棵树节点的个数,或者是高度值
*
* 如果大于0,说明这是树的子节点,里面存放的是父节点的下标
*
* @param length
* 数组的长度
*/
public DisjSet(int length) {
if (length <= 0) {
System.out.println("数组初始化长度有误");
return;
}
array = new int[length];
this.length = array.length;
for (int i = 0; i < array.length; i++) {
array[i] = -1;
}
}
/**
* 获取一个下标所在的整个集合的值
*
* @param index
* 下标值
* @return 整个集合的List
*/
public ArrayList<Integer> getDisjMember(int index) {
int count = array[index] < 0 ? (array[index] * -1)
: (array[find(index)] * -1);
ArrayList<Integer> list = new ArrayList<>();
list.add(index);
findAll(list, index, count);
return list;
}
/**
* @param list
* 需要返回的List对象
* @param parentRoot
* 父节点
* @param count
* 这个集合的节点个数
* @return 集合List
*/
private ArrayList<Integer> findAll(ArrayList<Integer> list, int parentRoot,
int count) {
if (count == 0) {
return list;
}
for (int i = 0; i < array.length; i++) {
if (array[i] == parentRoot) {
list.add(i);
findAll(list, i, count--);
}
}
return list;
}
/**
* 获取这个数组长度
*
* @return
*/
public int length() {
return this.length;
}
/**
* 判断是否全部合并
*
* @return
*/
public boolean isUnionAll() {
boolean isUnionAll = false;
for (int i = 0; i < array.length; i++) {
if (array[i] < 0) {
if (isUnionAll) {
isUnionAll = !isUnionAll;
break;
}
isUnionAll = !isUnionAll;
}
}
return isUnionAll;
}
/**
* 通过比较两个集合的个数来决定谁被归并 每次归并,在根节点,存放整个集合中节点的个数
*
* @param root1
* @param root2
*/
public void unionBySzie(int root1, int root2) {
int sumSize = array[root1] + array[root2];
if (array[root1] > array[root2]) {
array[root2] = sumSize;
array[root1] = root2;
} else {
array[root1] = sumSize;
array[root2] = root1;
}
}
/**
* 通过集合的高度进行归并
*
* 高度小的集合称为高度大的树的子节点
*
* @param root1
* @param root2
*/
public void unionByHeigh(int root1, int root2) {
if (array[root1] < array[root2]) {
array[root1] = root2;
} else {
if (array[root1] == array[root2]) {
array[root1]--;
}
array[root2] = root1;
}
}
/**
* 路径压缩的查找方式 通过根的下标来标识树集合
*
* 路径压缩的思想是,union的最后或多或少会破坏一棵树的平衡性 所以每次
* find后,都将这个find的位置中存放的父节点的下标值改成根节点的下标,这样一来,树的高度将变小。但是
*
* @param x
* @return 最终返回的是这个集合的根节点,因为根节点中存放着这棵树的大小或者高度
*/
public int find(int x) {
if (array[x] < 0) {
return x;
} else {
return array[x] = find(array[x]);
}
}
}
不相交集类的思想在很多地方都有用处,比如要产生一个迷宫...