并查集的简介及其C/C++代码的实现及优化

文章来自

并查集的简介及其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 个朋友圈。

       其实, 这是个并查集的问题, 比较简单。 


       下面, 我们来写个并查集的程序玩玩, 加深对并查集的理解:

[cpp]  view plain copy
  1. // taoge的并查集  
  2.   
  3. #include <iostream>  
  4. using namespace std;  
  5.   
  6. #define N 1000  
  7. int leader[N + 1] = {0}; // 先搞一个充分大的数组  
  8.   
  9. // 初始化  
  10. void setLeader()  
  11. {  
  12.     int i = 1;  
  13.     for(i = 1; i <= N; i++)  
  14.     {  
  15.         leader[i] = i; // 初始化时, 将自己初始化为自己的领导  
  16.     }  
  17. }  
  18.   
  19. // 查找领导, 看看究竟是谁(实际上, 还可以进行路径压缩优化)  
  20. int findLeader(int n)   
  21. {  
  22.     int r = n;  
  23.     while(leader[r] != r)  
  24.     {  
  25.         r = leader[r]; // 没找到的话, 一直往上找  
  26.     }  
  27.   
  28.     return r;  
  29. }  
  30.   
  31. // 将两个领导带领的团队融合, 从此, leaderX和leaderY建立了新的统一战线, 是一个大家庭团队了  
  32. void uniteSet(int leaderX, int leaderY)  
  33. {  
  34.     leader[leaderX] = leaderY;  // leader[leaderY] = leaderX;  
  35. }  
  36.   
  37. // 输入数组, 每一行表示一个集合关系, 比如第一行表示3和4属于一个集合团队  
  38. int input[] =   
  39. {  
  40.     3, 4,  
  41.     4, 2,  
  42.     7, 6,   
  43.     5, 1,  
  44.     3, 9,  
  45.     11, 8,  
  46.     6, 10,  
  47.     9, 13,  
  48.     11, 12,  
  49. };  
  50.   
  51. // 测试数组, 测试每行的两个整数是否属于同一个大的家庭团队  
  52. int test[] =  
  53. {  
  54.     3, 2,  
  55.     9, 4,  
  56.     7, 10,  
  57.     6, 7,  
  58.     13, 4,  
  59.     8, 12,  
  60.   
  61.     6, 9,  
  62.     4, 7,  
  63.     11, 10,  
  64.     1, 2,  
  65.     12, 13,  
  66.     7, 13,  
  67. };  
  68.   
  69.   
  70. int main()  
  71. {  
  72.     int numberOfSets = 13; // 总共有13个元素, 即1, 2, 3, 4, ...., 13  
  73.   
  74.     // 初始化领导  
  75.     setLeader();  
  76.   
  77.     int i = 0;  
  78.     int j = 0;  
  79.     int n = sizeof(input) / sizeof(input[0]) / 2;  
  80.     for(j = 0; j < n; j++)  
  81.     {  
  82.         int u = input[i++];  
  83.         int v = input[i++];  
  84.           
  85.         // 找领导  
  86.         u = findLeader(u);  
  87.         v = findLeader(v);  
  88.   
  89.         // 领导不相等, 则融合着两个团队, 合二为一  
  90.         if(u != v)  
  91.         {  
  92.             uniteSet(u, v);  
  93.             numberOfSets--;  
  94.         }  
  95.     }  
  96.   
  97.     i = 0;  
  98.     n = sizeof(test) / sizeof(test[0]) / 2;  
  99.     for(j = 0; j < n; j++)  
  100.     {  
  101.         int u = test[i++];  
  102.         int v = test[i++];  
  103.           
  104.         // 找领导  
  105.         u = findLeader(u);  
  106.         v = findLeader(v);  
  107.   
  108.         // 如果领导不相同, 则不属于一个团队; 如果两个领导相同, 则肯定属于一个团队  
  109.         if(u != v)  
  110.         {  
  111.             cout << "NO" << endl;  
  112.         }  
  113.         else  
  114.         {  
  115.             cout << "YES" << endl;  
  116.         }  
  117.     }  
  118.   
  119.   
  120.     // 其实, 经合并后, 最后的集合是4个:  
  121.     // {3, 4, 2, 9, 13}, {7, 6, 10,}, {5, 1}, {11, 8, 12}  
  122.     cout << numberOfSets << endl;  
  123.   
  124.     return 0;  
  125. }  
      结果为:

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中, 我们已经详细地了解了并查集, 不过, 那个程序略显粗糙, 下面我们考虑来优化一下。


       先给出没有优化的代码吧:

[cpp]  view plain copy
  1. // taoge的并查集  
  2.   
  3. #include <iostream>  
  4. using namespace std;  
  5.   
  6. #define N 1000  
  7. int leader[N + 1] = {0}; // 先搞一个充分大的数组  
  8.   
  9. // 初始化  
  10. void setLeader()  
  11. {  
  12.     int i = 1;  
  13.     for(i = 1; i <= N; i++)  
  14.     {  
  15.         leader[i] = i; // 初始化时, 将自己初始化为自己的领导  
  16.     }  
  17. }  
  18.   
  19. // 查找领导, 看看究竟是谁(实际上, 还可以进行路径压缩优化)  
  20. int findLeader(int n)   
  21. {  
  22.     int r = n;  
  23.     while(leader[r] != r)  
  24.     {  
  25.         r = leader[r]; // 没找到的话, 一直往上找  
  26.     }  
  27.   
  28.     return r;  
  29. }  
  30.   
  31. // 将两个领导带领的团队融合, 从此, leaderX和leaderY建立了新的统一战线, 是一个大家庭团队了  
  32. void uniteSet(int leaderX, int leaderY)  
  33. {  
  34.     leader[leaderX] = leaderY;  // leader[leaderY] = leaderX;  
  35. }  
  36.   
  37. // 输入数组, 每一行表示一个集合关系, 比如第一行表示3和4属于一个集合团队  
  38. int input[] =   
  39. {  
  40.     3, 4,  
  41.     4, 2,  
  42.     7, 6,   
  43.     5, 1,  
  44.     3, 9,  
  45.     11, 8,  
  46.     6, 10,  
  47.     9, 13,  
  48.     11, 12,  
  49. };  
  50.   
  51. // 测试数组, 测试每行的两个整数是否属于同一个大的家庭团队  
  52. int test[] =  
  53. {  
  54.     3, 2,  
  55.     9, 4,  
  56.     7, 10,  
  57.     6, 7,  
  58.     13, 4,  
  59.     8, 12,  
  60.   
  61.     6, 9,  
  62.     4, 7,  
  63.     11, 10,  
  64.     1, 2,  
  65.     12, 13,  
  66.     7, 13,  
  67. };  
  68.   
  69.   
  70. int main()  
  71. {  
  72.     int numberOfSets = 13; // 总共有13个元素, 即1, 2, 3, 4, ...., 13  
  73.   
  74.     // 初始化领导  
  75.     setLeader();  
  76.   
  77.     int i = 0;  
  78.     int j = 0;  
  79.     int n = sizeof(input) / sizeof(input[0]) / 2;  
  80.     for(j = 0; j < n; j++)  
  81.     {  
  82.         int u = input[i++];  
  83.         int v = input[i++];  
  84.           
  85.         // 找领导  
  86.         u = findLeader(u);  
  87.         v = findLeader(v);  
  88.   
  89.         // 领导不相等, 则融合着两个团队, 合二为一  
  90.         if(u != v)  
  91.         {  
  92.             uniteSet(u, v);  
  93.             numberOfSets--;  
  94.         }  
  95.     }  
  96.   
  97.     i = 0;  
  98.     n = sizeof(test) / sizeof(test[0]) / 2;  
  99.     for(j = 0; j < n; j++)  
  100.     {  
  101.         int u = test[i++];  
  102.         int v = test[i++];  
  103.           
  104.         // 找领导  
  105.         u = findLeader(u);  
  106.         v = findLeader(v);  
  107.   
  108.         // 如果领导不相同, 则不属于一个团队; 如果两个领导相同, 则肯定属于一个团队  
  109.         if(u != v)  
  110.         {  
  111.             cout << "NO" << endl;  
  112.         }  
  113.         else  
  114.         {  
  115.             cout << "YES" << endl;  
  116.         }  
  117.     }  
  118.   
  119.   
  120.     // 其实, 经合并后, 最后的集合是4个:  
  121.     // {3, 4, 2, 9, 13}, {7, 6, 10,}, {5, 1}, {11, 8, 12}  
  122.     cout << numberOfSets << endl;  
  123.   
  124.     return 0;  
  125. }  
       结果为:

YES
YES
YES
YES
YES
YES
NO
NO
NO
NO
NO
NO
4


       实际上, 在findLeader的时候, 我们可以进行路径压缩, 这是“查优化”的关键点。而在并的过程中, 也可以进行“并优化”, 不过, “并优化”的作用不太明显, 如下:

[cpp]  view plain copy
  1. // taoge的并查集  
  2.   
  3. #include <iostream>  
  4. using namespace std;  
  5.   
  6. #define N 1000  
  7. int leader[N + 1] = {0}; // 先搞一个充分大的数组  
  8.   
  9. // 初始化  
  10. void setLeader()  
  11. {  
  12.     int i = 1;  
  13.     for(i = 1; i <= N; i++)  
  14.     {  
  15.         leader[i] = i; // 初始化时, 将自己初始化为自己的领导  
  16.     }  
  17. }  
  18.   
  19. // 查找领导, 看看究竟是谁  
  20. int findLeader(int n)   
  21. {  
  22.     int r = n;  
  23.     while(leader[r] != r)  
  24.     {  
  25.         r = leader[r]; // 没找到的话, 一直往上找  
  26.     }  
  27.   
  28.     // "查优化"的本质是路径压缩, 最终使得所有员工的直接上司均为该组的leader  
  29.     int i = n;  
  30.     int j = 0;  
  31.     while(i != r)  
  32.     {  
  33.         j = leader[i];  
  34.         leader[i] = r;  
  35.         i = j;  
  36.     }  
  37.   
  38.     return r;  
  39. }  
  40.   
  41. // 将两个领导带领的团队融合, 从此, leaderX和leaderY建立了新的统一战线, 是一个大家庭团队了  
  42. void uniteSet(int leaderX, int leaderY)  
  43. {  
  44.     // 我个人认为:"并优化"的作用不是很大  
  45.     if(leaderX < leaderY)  
  46.     {  
  47.         leader[leaderX] = leaderY;  
  48.     }  
  49.     else  
  50.     {  
  51.         leader[leaderY] = leaderX;  
  52.     }  
  53. }  
  54.   
  55. // 输入数组, 每一行表示一个集合关系, 比如第一行表示3和4属于一个集合团队  
  56. int input[] =   
  57. {  
  58.     3, 4,  
  59.     4, 2,  
  60.     7, 6,   
  61.     5, 1,  
  62.     3, 9,  
  63.     11, 8,  
  64.     6, 10,  
  65.     9, 13,  
  66.     11, 12,  
  67. };  
  68.   
  69. // 测试数组, 测试每行的两个整数是否属于同一个大的家庭团队  
  70. int test[] =  
  71. {  
  72.     3, 2,  
  73.     9, 4,  
  74.     7, 10,  
  75.     6, 7,  
  76.     13, 4,  
  77.     8, 12,  
  78.   
  79.     6, 9,  
  80.     4, 7,  
  81.     11, 10,  
  82.     1, 2,  
  83.     12, 13,  
  84.     7, 13,  
  85. };  
  86.   
  87.   
  88. int main()  
  89. {  
  90.     int numberOfSets = 13; // 总共有13个元素, 即1, 2, 3, 4, ...., 13  
  91.   
  92.     // 初始化领导  
  93.     setLeader();  
  94.   
  95.     int i = 0;  
  96.     int j = 0;  
  97.     int n = sizeof(input) / sizeof(input[0]) / 2;  
  98.     for(j = 0; j < n; j++)  
  99.     {  
  100.         int u = input[i++];  
  101.         int v = input[i++];  
  102.           
  103.         // 找领导  
  104.         u = findLeader(u);  
  105.         v = findLeader(v);  
  106.   
  107.         // 领导不相等, 则融合着两个团队, 合二为一  
  108.         if(u != v)  
  109.         {  
  110.             uniteSet(u, v);  
  111.             numberOfSets--;  
  112.         }  
  113.     }  
  114.   
  115.     i = 0;  
  116.     n = sizeof(test) / sizeof(test[0]) / 2;  
  117.     for(j = 0; j < n; j++)  
  118.     {  
  119.         int u = test[i++];  
  120.         int v = test[i++];  
  121.           
  122.         // 找领导  
  123.         u = findLeader(u);  
  124.         v = findLeader(v);  
  125.   
  126.         // 如果领导不相同, 则不属于一个团队; 如果两个领导相同, 则肯定属于一个团队  
  127.         if(u != v)  
  128.         {  
  129.             cout << "NO" << endl;  
  130.         }  
  131.         else  
  132.         {  
  133.             cout << "YES" << endl;  
  134.         }  
  135.     }  
  136.   
  137.   
  138.     // 其实, 经合并后, 最后的集合是4个:  
  139.     // {3, 4, 2, 9, 13}, {7, 6, 10,}, {5, 1}, {11, 8, 12}  
  140.     cout << numberOfSets << endl;  
  141.   
  142.     return 0;  
  143. }  
       结果同样是:

YES
YES
YES
YES
YES
YES
NO
NO
NO
NO
NO
NO
4



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值