1、算法思想:转自:http://blog.csdn.net/hpuhjh/article/details/47832795
一、算法介绍:
并查集(Union-find Sets)是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题。
并查集的基本操作有两个:
1:合并
union(x, y):把元素 x 和元素 y 所在的集合合并,要求 x 和 y 所在的集合不相交,如果两个集合相交则不合并。
2:查询
find(x):找到元素 x 所在的集合的代表,该操作常用于判断两个元素是否位于同一个集合,只要将它们各自的代表比较一下就可以了。
并查集类似一个森林,是用树形结构来实现的,所以以下的讲解用树形结构来构造模型:
事先声明:
(1)一个集合对应一棵树。
(2)一个集合中的元素对应一个节点。
二、算法实现:
一、初始化:
我们用n个节点表示n个元素,有一点要特别注意:一个节点,若它的父节点等于它本身,则说明这个节点是根节点。
定义数组 per[], per[x]代表x的父节点。初始化时我们把per[x] = x,相当于每个节点都是独立的根节点(每个根节点都代表一个集合)
二、查找:
find(x),查找元素x所在的集合,即查找节点x在哪一棵树上,这里我们知道,在一棵树中,根节点是唯一的,父节点子节点都是相对而言的。要想确定节点x在哪一课树,我们只需要找到x的根节点就可以了。
如果判断节点x 和 节点y在不在同一棵树上,我们只需要找到x 和 y的根节点,若x和y的根节点相同,则x,y在同一颗树上,否则在不用的树上。
代码:
图1.1
如图1.1所示:
我们令节点1为根节点。查找节点4在哪一棵树,我们只要找到4的根节点就可以了。
查找过程: 找到4的父节点per[4]为2,不等于它本身,继续向上查找, 2它的父节点per[2]为1,不等于它本身,继续向上查找,1的父节点per[1]为1等于它本身,说明1是根节点。
这里有一个路径压缩的优化, 当我们查找到4的根节点为1时,我们直接将per[4] = 1,即直接把4连在根节点1上,而且在查找4时还会找到2,可能还有其他的节点,将这些节点的per[]通通都设置为1,这样下次再查找4的子节点所在的树时,查找次数就缩短了1.
这里压缩路径有两种写法,一种是递归的,一种是非递归的。
1> 递归:
2>非递归
请读者自己模拟一下这两种压缩路径的方式不同之处在于:只是各个节点修改父节点的顺序不同而已。递归方法:先修改距离父节点近的节点,这由递归性质决定的;非递归方法:先修改离父节点最远的节点。
三、合并:
合并x 和 y所在的树, 只需要把其中一个树的根节点设置为令一个树根节点的子节点即可;
但是这里有一个问题, 是把 x的根节点设置为 y根节点的子节点,还是把y的根节点设置为x根节点的子节点。
节点1和节点2是一棵树,根节点为1, 节点3是一棵树,根节点是自身为3.
图1.2
图1.3
如图1.2所示:
现在我们根节点3作为根节点1的子节点,此时查找2的根节点,只需要查找一次。
如图1,3所示
现在我们根节点1作为根节点3的子节点,此时查找2的根节点,需要先找到1,再找到3,多了一次查找。
所以这里存在一种优化。我们可以设置一个数组rank[ ],用它来记录每一棵树的深度,合并时如果两棵树的深度不用,那么从深度(rank)小的向深度(rank)达的连边。(但注意,压缩路径时会使树的深度发生变化,但我们不修改rank 的值)
2、牛客网刷题:
题目描述
输入描述:
第一行包含两个整数N(1≤N≤10000),M(1≤M≤1000000)。N表示公有N块空地。 接下来M行,每行包含两个整数P(1≤P≤N),Q(1≤Q≤N),K代表P,Q两个间没有鳄鱼,需要耗费K的木材。
输出描述:
一个整数,即耗费木材最少的情况下,最长的那根木材长度。
输入
4 3 1 2 1 2 3 1 3 4 2
输出
2
package schooloffer;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;
/**
* Created by caoxiaohong on 17/9/21.
* 亮亮解出了卷轴隐藏的秘密,来到了一片沼泽地。这里有很多空地,而面试直通卡可能埋在任意一块空地中,好在亮亮发现了一堆木材,....
* 算法思想:并查集+最小生成树
* 求:最小生成树中:权值最大的那个路径
*/
public class SearchTreasure {
static class Edge{
private int x;
private int y;
private int weight;
Edge(int x,int y,int weight){
this.x=x;
this.y=y;
this.weight=weight;
}
}
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
int N,M;
while (scanner.hasNext()){
String[] str=scanner.nextLine().split(" ");
N=Integer.valueOf(str[0]);
M=Integer.valueOf(str[1]);
Edge[] values=new Edge[M];
for(int i=0;i<M;i++){
String[] w=scanner.nextLine().split(" ");
int x=Integer.valueOf(w[0]);
int y=Integer.valueOf(w[1]);
int weight=Integer.valueOf(w[2]);
Edge edge=new Edge(x,y,weight);
values[i]=edge;
}
System.out.println(solution(values,N,M));
}
}
//并查集:初始化
private static void init(int[] father,int n){
for(int i=0;i<=n;i++)
father[i]=i;
}
//并查集:查找根节点
private static int getFather(int[] father,int x){
int r=x;
while (father[r]!=r) // father[x]=y:表示x点对应对根节点为y
r=father[r];
//压缩路径:
int i=x;
while (i!=r) {
int j=father[i];
father[i]=r;
i=j;
}
return r;
}
//并查集:合并
private static void mergeSet(int[] father,int fx,int fy){
if(fx>fy){
father[fx]=fy;
}else {
father[fy]=fx;
}
}
//总算法
private static int solution(Edge[] values,int n,int m){
int result=0;
//初始化
int[] father=new int[n+1];
init(father,n);
//对valuse[]按照weight排序
Arrays.sort(values, new Comparator<Edge>() {
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight-o2.weight;
}
});
for(int i=0;i<m;i++){
int fx=getFather(father,values[i].x);
int fy=getFather(father,values[i].y);
if(fx!=fy){
if(result<values[i].weight)
result=values[i].weight;
//合并节点
mergeSet(father,fx,fy);
}
}
return result;
}
}