文章来自
《并查集的简介及其C/C++代码的实现》 http://blog.csdn.net/stpeace/article/details/46506861
《并查集的“并优化”(leader合并)和“查优化”(路径压缩)》 http://blog.csdn.net/stpeace/article/details/46594053
当年, 我在一个公司实习, 某次, 在一次算法交流的过程中, 我第一次听到了并查集这个看似高大上的概念, 也再一次感觉到了自己的无知。
对于一个非计算机专业的人来说, 你跟他说并查集, 就有点像你对着计算机专业的人说Gibbs现象或者FFT一样, 你懂的。 后来, 某公司的招聘笔试题目中, 又出现并查集, 让我们一起来看看这个题目吧:
假如已知有 n 个人和 m 对好友关系 (存于数字 r) 。 如果两个人是直接或间接的好友 (好友的好友的好友...) , 则认为他们属于同一个朋友圈,请写程序求出这 n 个人里一共有多少个朋友圈。 假如:n = 5 , m = 3 , r = {{1 , 2} , {2 , 3} , {4 , 5}},表示有 5 个人,1 和 2 是好友,2 和 3 是好友,4 和 5 是好友,则 1、2、3 属于一个朋友圈,4、5 属于另一个朋友圈,结果为 2 个朋友圈。
其实, 这是个并查集的问题, 比较简单。
下面, 我们来写个并查集的程序玩玩, 加深对并查集的理解:
- // taoge的并查集
- #include <iostream>
- using namespace std;
- #define N 1000
- int leader[N + 1] = {0}; // 先搞一个充分大的数组
- // 初始化
- void setLeader()
- {
- int i = 1;
- for(i = 1; i <= N; i++)
- {
- leader[i] = i; // 初始化时, 将自己初始化为自己的领导
- }
- }
- // 查找领导, 看看究竟是谁(实际上, 还可以进行路径压缩优化)
- int findLeader(int n)
- {
- int r = n;
- while(leader[r] != r)
- {
- r = leader[r]; // 没找到的话, 一直往上找
- }
- return r;
- }
- // 将两个领导带领的团队融合, 从此, leaderX和leaderY建立了新的统一战线, 是一个大家庭团队了
- void uniteSet(int leaderX, int leaderY)
- {
- leader[leaderX] = leaderY; // leader[leaderY] = leaderX;
- }
- // 输入数组, 每一行表示一个集合关系, 比如第一行表示3和4属于一个集合团队
- int input[] =
- {
- 3, 4,
- 4, 2,
- 7, 6,
- 5, 1,
- 3, 9,
- 11, 8,
- 6, 10,
- 9, 13,
- 11, 12,
- };
- // 测试数组, 测试每行的两个整数是否属于同一个大的家庭团队
- int test[] =
- {
- 3, 2,
- 9, 4,
- 7, 10,
- 6, 7,
- 13, 4,
- 8, 12,
- 6, 9,
- 4, 7,
- 11, 10,
- 1, 2,
- 12, 13,
- 7, 13,
- };
- int main()
- {
- int numberOfSets = 13; // 总共有13个元素, 即1, 2, 3, 4, ...., 13
- // 初始化领导
- setLeader();
- int i = 0;
- int j = 0;
- int n = sizeof(input) / sizeof(input[0]) / 2;
- for(j = 0; j < n; j++)
- {
- int u = input[i++];
- int v = input[i++];
- // 找领导
- u = findLeader(u);
- v = findLeader(v);
- // 领导不相等, 则融合着两个团队, 合二为一
- if(u != v)
- {
- uniteSet(u, v);
- numberOfSets--;
- }
- }
- i = 0;
- n = sizeof(test) / sizeof(test[0]) / 2;
- for(j = 0; j < n; j++)
- {
- int u = test[i++];
- int v = test[i++];
- // 找领导
- u = findLeader(u);
- v = findLeader(v);
- // 如果领导不相同, 则不属于一个团队; 如果两个领导相同, 则肯定属于一个团队
- if(u != v)
- {
- cout << "NO" << endl;
- }
- else
- {
- cout << "YES" << endl;
- }
- }
- // 其实, 经合并后, 最后的集合是4个:
- // {3, 4, 2, 9, 13}, {7, 6, 10,}, {5, 1}, {11, 8, 12}
- cout << numberOfSets << endl;
- return 0;
- }
YES
YES
YES
YES
YES
YES
NO
NO
NO
NO
NO
NO
4
其实, 并查集很简单, 无非就是查查并并的操作。 不过, 并查集的思想, 确实很优秀。 要说明的是, 上述代码其实可以优化, 比如路径压缩等。
如果大家觉得上述程序不太好理解, 那就请参考:http://blog.csdn.net/dellaserss/article/details/7724401这篇博文,那篇博文是转载的, 写的通俗易通, 形象生动,可读性强。 最后, 我把那篇文章的一个图借鉴过来, 欣赏一下, 挺有意思的(在此, 特别感谢下图的原作者):
OK, 本文先到此为止。
在博文http://blog.csdn.net/stpeace/article/details/46506861中, 我们已经详细地了解了并查集, 不过, 那个程序略显粗糙, 下面我们考虑来优化一下。
先给出没有优化的代码吧:
- // taoge的并查集
- #include <iostream>
- using namespace std;
- #define N 1000
- int leader[N + 1] = {0}; // 先搞一个充分大的数组
- // 初始化
- void setLeader()
- {
- int i = 1;
- for(i = 1; i <= N; i++)
- {
- leader[i] = i; // 初始化时, 将自己初始化为自己的领导
- }
- }
- // 查找领导, 看看究竟是谁(实际上, 还可以进行路径压缩优化)
- int findLeader(int n)
- {
- int r = n;
- while(leader[r] != r)
- {
- r = leader[r]; // 没找到的话, 一直往上找
- }
- return r;
- }
- // 将两个领导带领的团队融合, 从此, leaderX和leaderY建立了新的统一战线, 是一个大家庭团队了
- void uniteSet(int leaderX, int leaderY)
- {
- leader[leaderX] = leaderY; // leader[leaderY] = leaderX;
- }
- // 输入数组, 每一行表示一个集合关系, 比如第一行表示3和4属于一个集合团队
- int input[] =
- {
- 3, 4,
- 4, 2,
- 7, 6,
- 5, 1,
- 3, 9,
- 11, 8,
- 6, 10,
- 9, 13,
- 11, 12,
- };
- // 测试数组, 测试每行的两个整数是否属于同一个大的家庭团队
- int test[] =
- {
- 3, 2,
- 9, 4,
- 7, 10,
- 6, 7,
- 13, 4,
- 8, 12,
- 6, 9,
- 4, 7,
- 11, 10,
- 1, 2,
- 12, 13,
- 7, 13,
- };
- int main()
- {
- int numberOfSets = 13; // 总共有13个元素, 即1, 2, 3, 4, ...., 13
- // 初始化领导
- setLeader();
- int i = 0;
- int j = 0;
- int n = sizeof(input) / sizeof(input[0]) / 2;
- for(j = 0; j < n; j++)
- {
- int u = input[i++];
- int v = input[i++];
- // 找领导
- u = findLeader(u);
- v = findLeader(v);
- // 领导不相等, 则融合着两个团队, 合二为一
- if(u != v)
- {
- uniteSet(u, v);
- numberOfSets--;
- }
- }
- i = 0;
- n = sizeof(test) / sizeof(test[0]) / 2;
- for(j = 0; j < n; j++)
- {
- int u = test[i++];
- int v = test[i++];
- // 找领导
- u = findLeader(u);
- v = findLeader(v);
- // 如果领导不相同, 则不属于一个团队; 如果两个领导相同, 则肯定属于一个团队
- if(u != v)
- {
- cout << "NO" << endl;
- }
- else
- {
- cout << "YES" << endl;
- }
- }
- // 其实, 经合并后, 最后的集合是4个:
- // {3, 4, 2, 9, 13}, {7, 6, 10,}, {5, 1}, {11, 8, 12}
- cout << numberOfSets << endl;
- return 0;
- }
YES
YES
YES
YES
YES
YES
NO
NO
NO
NO
NO
NO
4
实际上, 在findLeader的时候, 我们可以进行路径压缩, 这是“查优化”的关键点。而在并的过程中, 也可以进行“并优化”, 不过, “并优化”的作用不太明显, 如下:
- // taoge的并查集
- #include <iostream>
- using namespace std;
- #define N 1000
- int leader[N + 1] = {0}; // 先搞一个充分大的数组
- // 初始化
- void setLeader()
- {
- int i = 1;
- for(i = 1; i <= N; i++)
- {
- leader[i] = i; // 初始化时, 将自己初始化为自己的领导
- }
- }
- // 查找领导, 看看究竟是谁
- int findLeader(int n)
- {
- int r = n;
- while(leader[r] != r)
- {
- r = leader[r]; // 没找到的话, 一直往上找
- }
- // "查优化"的本质是路径压缩, 最终使得所有员工的直接上司均为该组的leader
- int i = n;
- int j = 0;
- while(i != r)
- {
- j = leader[i];
- leader[i] = r;
- i = j;
- }
- return r;
- }
- // 将两个领导带领的团队融合, 从此, leaderX和leaderY建立了新的统一战线, 是一个大家庭团队了
- void uniteSet(int leaderX, int leaderY)
- {
- // 我个人认为:"并优化"的作用不是很大
- if(leaderX < leaderY)
- {
- leader[leaderX] = leaderY;
- }
- else
- {
- leader[leaderY] = leaderX;
- }
- }
- // 输入数组, 每一行表示一个集合关系, 比如第一行表示3和4属于一个集合团队
- int input[] =
- {
- 3, 4,
- 4, 2,
- 7, 6,
- 5, 1,
- 3, 9,
- 11, 8,
- 6, 10,
- 9, 13,
- 11, 12,
- };
- // 测试数组, 测试每行的两个整数是否属于同一个大的家庭团队
- int test[] =
- {
- 3, 2,
- 9, 4,
- 7, 10,
- 6, 7,
- 13, 4,
- 8, 12,
- 6, 9,
- 4, 7,
- 11, 10,
- 1, 2,
- 12, 13,
- 7, 13,
- };
- int main()
- {
- int numberOfSets = 13; // 总共有13个元素, 即1, 2, 3, 4, ...., 13
- // 初始化领导
- setLeader();
- int i = 0;
- int j = 0;
- int n = sizeof(input) / sizeof(input[0]) / 2;
- for(j = 0; j < n; j++)
- {
- int u = input[i++];
- int v = input[i++];
- // 找领导
- u = findLeader(u);
- v = findLeader(v);
- // 领导不相等, 则融合着两个团队, 合二为一
- if(u != v)
- {
- uniteSet(u, v);
- numberOfSets--;
- }
- }
- i = 0;
- n = sizeof(test) / sizeof(test[0]) / 2;
- for(j = 0; j < n; j++)
- {
- int u = test[i++];
- int v = test[i++];
- // 找领导
- u = findLeader(u);
- v = findLeader(v);
- // 如果领导不相同, 则不属于一个团队; 如果两个领导相同, 则肯定属于一个团队
- if(u != v)
- {
- cout << "NO" << endl;
- }
- else
- {
- cout << "YES" << endl;
- }
- }
- // 其实, 经合并后, 最后的集合是4个:
- // {3, 4, 2, 9, 13}, {7, 6, 10,}, {5, 1}, {11, 8, 12}
- cout << numberOfSets << endl;
- return 0;
- }
YES
YES
YES
YES
YES
YES
NO
NO
NO
NO
NO
NO
4