前言
并查集类似数学中的集合,如果是两个集合相交,那就把这两个集合合并成一个集合。
社交网络中推荐可能认识的人,可能是类似原理。
二狗子与B是好友{二狗子, B}
B与A是好友{B,A}
两个相交是B
所以合并,{二狗子,B,A}
系统就可能会将B以可能认识的人给二狗子
发现了不错的文章:
参考链接:
http://blog.csdn.net/dm_vincent/article/details/7655764
http://blog.csdn.net/dm_vincent/article/details/7769159
实现功能
来及《啊哈,算法》
擒贼先擒王---并查集的应用
* 小哼的家乡出现多次抢劫事件,强盗人数众多,警察想查清楚到底有几个犯罪团伙是非常困难的,然而还是搜到了一些线索:
* 10个强盗
* 1号强盗与2号强盗是同伙
* 3号强盗与4号强盗是同伙
* 5号强盗与2号强盗是同伙
* 4号-6号
* 2号-6号
* 8号-7号
* 9号-7号
* 1号-6号
* 2号-4号
* 强盗同伙的同伙也是同伙,现在需要查出有几个独立的犯罪集团。
实现检索出独立犯罪集团的个数。
中文版参考
/**
* 思路来自《啊哈,算法》,我作为初学者,只是复述一遍加深印象。
*
* 首先,假设10个强盗互不认识,每个人的BOSS都是自己,我们要找出每个独立犯罪集团的BOSS,及其下属
* 使用一个一维数组 r 存储
* 每个下标代表强盗编号
* 下标对应的值代表其BOSS
* 例如r[2]=5 表示2号强盗的BOSS是5号强盗
*
* 初始状态
* index: 1 2 3 4 5 6 7 8 9 10
* r: {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
*
* 然后,我们逐条线索处理。
* 此处我们假设每一对强盗中,第二个服从第一个,如说到9号-7号。则7号服从9号。
*
* 1号-2号:
* 令2号强盗的BOSS为1号
* r[2] = 1 // Again,每个下标代表强盗编号,下标对应的值代表其BOSS
*
* index: 1 2 3 4 5 6 7 8 9 10
* r: {1, 1, 3, 4, 5, 6, 7, 8, 9, 10}
*
* 3号-4号:
* 令3号强盗的BOSS为4号
* r[3] = 4
*
* index: 1 2 3 4 5 6 7 8 9 10
* r: {1, 1, 4, 4, 5, 6, 7, 8, 9, 10}
*
* 5号-2号:
* 此处应是令2号强盗的BOSS为5号,然而
* 2号强盗的BOSS已经是1号了,如何解决?
*
* 方法是,既然2号强盗的BOSS是1号,那么5号去收服2号的BOSS 1号强盗 好了。
* 即r[1] = 5
*
* index: 1 2 3 4 5 6 7 8 9 10
* r: {5, 1, 4, 4, 5, 6, 7, 8, 9, 10}
*
* --------------------------------------------------------------------------------------
* 此后,凡是碰需要收服的强盗BOSS另有其人的,都是去找最顶层的BOSS
*
* 例如8号需要收服9号
* 9号的BOSS是6号
* 6号的BOSS是1号
* 那么。8号直接去收服1号即可
* PPPPPS:寻找BOSS的过程是递归的,在此过程中可以将寻找到的BOSS赋值给每一个经过的下属
* 这样下次找BOSS,可以直接一步到位
* 原本的BOSS寻找: 9号→6号→1号
* 经过BOSS的替换,而可以做到:9号→1号
* 术语貌似叫压缩路径
*
* 另一种情况,
* r[5] = 5
* r[2] = 5
*
* r[3] = 3
* r[6] = 3
*
* 6号本来的BOSS是3号,但是现在他有新的BOSS 2号强盗
* 即:2号-6号,那该怎么办呢?
* 答案是,让6号的BOSS 3号强盗 归顺5号强盗即可
* 即r[3] = 5
*
* 总结一下并查集的思路:
* 从n个集合中,寻找几个独立不相交的大集合。
* 将相交的小集合合并即可
* 合并的思路是,每次都寻找最顶层的祖先
* 例如因为B公司的业务对A公司当前有帮助(相交)
* A公司收购B公司(合并)
* 只需要让B公司的CEO归属A公司的CEO,不需要对每个职员都处理
* 但是每次B公司员工寻找B公司CEO时,可能需要辗转好几个上司、上司的上司等
* 此时可以优化B公司的员工跨过上司直接找到B公司的CEO。而B公司的CEO对A公司CEO负责。
*
*/
代码实现
public class UnionFind {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int[] robber = new int[11];
init(robber);
showArray(robber);
int employer, employee;
for (int i = 1; i < robber.length - 1; i++) {
employer = in.nextInt();
employee = in.nextInt();
unionRobber(robber, employer, employee);
}
showArray(robber);
int sum = 0;
for (int i = 1; i < robber.length; i++) {
if (robber[i] == i) {
sum++;
}
}
System.out.println(sum);
}
public static void init(int[] robber) {
for (int i = 1; i < robber.length; i++) {
robber[i] = i;
}
}
public static void showArray(int[] robber) {
for (int i = 1; i < robber.length; i++) {
System.out.print(robber[i] + " ");
}
System.out.println();
}
public static void unionRobber(int[] robber, int employer, int employee) {
//传进来的是小盗贼头目,可能有大BOSS呢,寻找他
int t1 = findBigBoss(robber, employer);
//可能有上司,让上司对大BOSS负责
int t2 = findBigBoss(robber, employee);
//如果我们找到了小盗贼的BOSS
//同时找到了小盗贼头目的BOSS
//并且不是同伙的,那么让小盗贼的BOSS纳入麾下
if (t1 != t2) {
robber[t2] = t1;
}
}
public static int findBigBoss(int[] robber, int pos) {
//如果他的BOSS是自己,不就是大BOSS嘛
if (robber[pos] == pos) {
return pos;
} else {
//寻找他的上级BOSS,寻找过程中压缩路径。即让所有下属直接对顶层BOSS负责
robber[pos] = findBigBoss(robber, robber[pos]);
return robber[pos];
}
}
}
结果
输出:
1 2 3 4 5 6 7 8 9 10
输入:
1 2
3 4
5 2
4 6
2 6
8 7
9 7
1 6
2 4
输出:
5 5 5 5 5 5 8 9 9 10
3
结尾一点
首先一开始看的时候还是有点糊涂的,然后就搜到了开头两篇文章,在了解了应用范围之后,忽然就通了。
大概这是抽象能力不足的表现。
复述的过程中,会感觉到对其思想产生了解,写代码能从整体考虑。
首先我要能找任意盗贼的上司
其次我要能把找到的上司告诉那个盗贼,如果不是同一个上司的话
然后就完了。