您的位置:首页 > 其它

《算法》union&find算法及改进

2016-11-15 15:25 218 查看
算法详细内容参考《算法》第4版1.5案例研究:union-find算法

分析运行结果:

package union;
import java.util.Arrays;
import java.util.Random;

class WeightQuickUnion {
private int[] id; //分量id,每个节点所属集合标识
private int[] size; //以该节点为根其分量大小
private int count; //分量数量

//initialize
public WeightQuickUnion(int N){
count = N; //初始化每个节点都是一个分量,union时减少count
id = new int
;
for(int i = 0; i < N; ++i){
id[i] = i;
}
size = new int
;
for(int i = 0; i < N; ++i){
size[i] = 1;
}
}
public int count(){
return count;
}
public boolean connected(int left, int right){
return find(left) == find(right);
}

//O(logN) time 返回节点所属集合标识
public int find(int node){
while(node != id[node]){
node = id[node];
}
return node;
}

public void find_quick_union(int left, int right){
int left_id = find(left);
int right_id = find(right);
if(left_id != right_id){
if(size[left_id] < size[right_id]){
id[left_id] = right_id;
size[left_id] += size[right_id];
}else{
id[right_id] = left_id;
size[right_id] += size[left_id];
}
--count;
}
return ;
}
}

class WeightQuickCompressUnion {
private int[] id; //分量id,每个节点所属集合标识
private int[] size; //以该节点为根其分量大小
private int count; //分量数量

//initialize
public WeightQuickCompressUnion(int N){
count = N; //初始化每个节点都是一个分量,union时减少count
id = new int
;
for(int i = 0; i < N; ++i){
id[i] = i;
}
size = new int
;
for(int i = 0; i < N; ++i){
size[i] = 1;
}
}
public int count(){
return count;
}
public boolean connected(int left, int right){
return find(left) == find(right);
}

//O(logN) time 返回节点所属集合标识
public int find(int node){
int root = node;
while(root != id[root]){
root = id[root];
}
while(node != root){
node = id[node];
id[node] = root;
}
return root;
}

public void find_quick_union(int left, int right){
int left_id = find(left);
int right_id = find(right);
if(left_id != right_id){
if(size[left_id] < size[right_id]){
id[left_id] = right_id;
size[left_id] += size[right_id];
}else{
id[right_id] = left_id;
size[right_id] += size[left_id];
}
--count;
}
return ;
}
}

final class StdRandom {

private static Random random; // pseudo-random number generator
private static long seed; // pseudo-random number generator seed

// static initializer
static {
// this is how the seed was set in Java 1.4
seed = System.currentTimeMillis();
random = new Random(seed);
}

// don't instantiate
private StdRandom() { }

/**
* Sets the seed of the pseudorandom number generator.
* This method enables you to produce the same sequence of "random"
* number for each execution of the program.
* Ordinarily, you should call this method at most once per program.
*
* @param s the seed
*/
public static void setSeed(long s) {
seed = s;
random = new Random(seed);
}

/**
* Returns the seed of the pseudorandom number generator.
*
* @return the seed
*/
public static long getSeed() {

4000
return seed;
}

/**
* Returns a random real number uniformly in [0, 1).
* @return a random real number uniformly in [0, 1)
*/
public static double uniform() {
return random.nextDouble();
}

/**
* Returns a random integer uniformly in [0, n).
*
* @param n number of possible integers
* @return a random integer uniformly between 0 (inclusive) and {@code n} (exclusive)
* @throws IllegalArgumentException if {@code n <= 0}
*/
public static int uniform(int n) {
if (n <= 0) throw new IllegalArgumentException("argument must be positive");
return random.nextInt(n);
}

}

public class Union {
private int[] id; //分量id,每个节点所属集合标识
private int count; //分量数量

//initialize
public Union(int N){
count = N; //初始化每个节点都是一个分量,union时减少count
id = new int
;
for(int i = 0; i < N; ++i){
id[i] = i;
}
}

public boolean quick_find_connected(int left, int right){
return quick_find(left) == quick_find(right);
}
public boolean find_connected(int left, int right){
return find(left) == find(right);
}
//O(1) time
public int quick_find(int node){
return id[node];
}

//O(n^2) time to union if union N times
public void quick_find_union(int left, int right){
int left_id = quick_find(left); //O(1) time to find
int right_id = quick_find(right);
if(left_id != right_id){
for(int node = 0; node < id.length; ++node){
if(quick_find(node) == left_id){
id[node] = right_id;
}
}
--count;
}
return ;
}

//O(logN) time 返回节点所属集合标识
public int find(int node){
while(node != id[node]){
node = id[node];
}
return node;
}

//O(N*treeHeight) time if union N times
public void find_lazy_quick_union(int left, int right){
int left_id = find(left);
int right_id = find(right);
if(left_id != right_id){
id[left_id] = right_id;
--count;
}
return ;
}

public static void main(String[] args) {
// TODO Auto-generated method stub
final int E = 90000;
final int N = 100000;
int[][] eadges = new int [E][2];
for(int i = 0; i < E; ++i){
eadges[i][0] = StdRandom.uniform(N);
eadges[i][1] = StdRandom.uniform(N);
}
// System.out.println(Arrays.deepToString(eadges));
long startTime;
long endTime;
System.out.println("Union");
Union UF = new Union(N);
startTime = System.currentTimeMillis();
for(int i = 0; i < E; ++i){
UF.quick_find_union(eadges[i][0], eadges[i][1]);
}
endTime = System.currentTimeMillis();
System.out.println("Run Time:"+(endTime-startTime)+"ms");
/*for(int p = 0; p < N; ++p){
for(int q = 0; q < N; ++q)
System.out.print(UF.quick_find_connected(p,q)+" ");
}*/
// System.out.println("");
System.out.println(UF.count);

System.out.println("LazyWeight");
Union LazyUF = new Union(N);
startTime = System.currentTimeMillis();
for(int i = 0; i < E; ++i){
LazyUF.find_lazy_quick_union(eadges[i][0], eadges[i][1]);
}
endTime = System.currentTimeMillis();
System.out.println("Run Time:"+(endTime-startTime)+"ms");
/*for(int p = 0; p < N; ++p){
for(int q = 0; q < N; ++q)
System.out.print(LazyUF.find_connected(p,q)+" ");
}*/
// System.out.println("");
System.out.println(LazyUF.count);

System.out.println("Weight");
WeightQuickUnion WQUF = new WeightQuickUnion(N);
startTime = System.currentTimeMillis();
for(int i = 0; i < E; ++i){
WQUF.find_quick_union(eadges[i][0], eadges[i][1]);
}
endTime = System.currentTimeMillis();
System.out.println("Run Time:"+(endTime-startTime)+"ms");
/*for(int p = 0; p < N; ++p){
for(int q = 0; q < N; ++q)
System.out.print(WQUF.connected(p, q)+" ");
}*/
// System.out.println("");
System.out.println(WQUF.count());

System.out.println("Weight & Compress");
WeightQuickCompressUnion WCUF = new WeightQuickCompressUnion(N);
startTime = System.currentTimeMillis();
for(int i = 0; i < E; ++i){
WCUF.find_quick_union(eadges[i][0], eadges[i][1]);
}
endTime = System.currentTimeMillis();
System.out.println("Run Time:"+(endTime-startTime)+"ms");
/*for(int p = 0; p < N; ++p){
for(int q = 0; q < N; ++q)
System.out.print(WCUF.connected(p, q)+" ");
}*/
// System.out.println("");
System.out.println(WCUF.count());
}
}


打印出来的结果:
Union
Run Time:5248ms
20303
LazyWeight
Run Time:739ms
20303
Weight
Run Time:736ms
20303
Weight & Compress
Run Time:19ms
20303
调大边数E
final int E = 99000;
final int N = 100000;
Union
Run Time:6076ms
16619
LazyWeight
Run Time:1296ms
16619
Weight
Run Time:1271ms
16619
Weight & Compress
Run Time:19ms
16619
调小边数E
final int E = 1000;
final int N = 100000;

Union
Run Time:43ms
99000
LazyWeight
Run Time:0ms
99000
Weight
Run Time:1ms
99000
Weight & Compress
Run Time:0ms
99000

union、LazyWeight、Weight、Weight & Compress对应教材quick-find、quick-union、加权quick-union、路径压缩的加权quick-union算法。

边数比较小时图比较稀疏,输出结果中99000表示一共99000个分量,而总共节点为100000,quick-union中每棵树高度都不大所以运行起来比加权quick-union更快,因为quick-union中多了求树节点数量、将小树指向大树的步骤。而增大边数时加权quick-union效果比quick-union更好,因为加权quick-union可以保证树高度最多为logN,N为节点数。
挺有用的结论,把小树的根指向大树的根可以保证树高度小于logN,证明:

小树节点总数N1,高度为h1,大树节点总数N2,高度为h2,;将小树根指向大树根节点会造成小树节点高度加1

h1+1 <= log N1 + 1 = log(N1+N1) <= log(N1 + N2)

大树节点高度不变

所以合并之后的树高度不大于log(N1 + N2)

又单个节点高度为0,数学归纳法即知
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: