并查集是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并和查询问题。经典的应用有:判断连通性、最小生成树 Kruskal 算法、最近公共祖先(Least Common Ancestors, LCA)等。并查集在算法竞赛中也十分常见:一是简单且高效,二是应用很直观,三是容易和其他数据结构和算法结合。
例题:
有一个简单粗暴的方法:暴力求排列 + 检查连通性:
- 用递归暴力列出所有可能的排列:从 12 个数中选 5 个数。
- 判断这 5 个数是否连通.
#include<bits/stdc++.h>
using namespace std;
int star[]={1,2,3,4,6,7,8,9,11,12,13,14};
int num=0; //统计排列的个数
void Perm(int begin,int end){
if(begin == 5) //得到一个5个数的排列
num++; //统计排列个数
else
for(int i = begin;i <= end;i++) {
swap(star[begin],star[i]);
Perm(begin+1,end);
swap(star[begin],star[i]);
}
}
int main(){
Perm(0,11); //求从第0个数到第11个数的全排列。
cout << "total arrange=" << num/120 <<"\n";
//注意5个数的排列不需要有序,所以除以120,这120个都是重复的。
//例如: 1 2 3 4 5和2 3 4 1 5这种数是重复的,除去5!
return 0;
}
检查连通性(一个排列的 5 个数,检查每个数是否与其它的数相连)
可以先考虑 2 个数的连通。有一个好用的小技巧:在原图中向上为 -4 ,向下为+4 ,向左为 -1,向右为 +1,但是遇到 “3,4,5,7,8” 这种 4+1=5,这种情况不符合,所以我重构了一下原图,如下:
这样,向上为 -5,向下为 +5,向左为 -1,向右为 +1。经过这个转换,差值为 1 的两个数一定在同一行,差值为 5 的两个数一定在同一列。
int b[]={-1,1,-5,+5}; //上下左右4个方向。
for(int i=0;i<=4;i++) // 第i个数和第j个数是否相邻。
for(int j=0;j<=4;j++)
for(int k=0;k<=3;k++) // k是上下左右4个方向
if(star[i]+b[k]==star[j]) // i和j在k方向上连通。
......
关于判断 5 个数是否连通,我决定用前面学到的 BFS 来处理: 比如现在有一个排列{2, 3, 4, 8, 9}(见下图中的位置),我用 BFS 队列的步骤是:
- 2 进队列:当前队列是 (2);
- 2 的邻居进队列:当前队列是 (2, 3);
- 弹出 2:当前队列是 (3);
- 3 的邻居进队列:当前队列是 (3, 4, 8);
- 弹出 3 ;当前队列是 (4, 8)
- 4 的邻居进队列:当前队列是 (4,8,9)
- 弹出 4:当前队列是 (8, 9)
- 8 没有没处理过的邻居了。
- 弹出 8:当前队列是 (9)
- 弹出 9;
- 队列空。
如果 5 个数都进过队列,那么它们就是连通的。
下面是完整代码:
#include<bits/stdc++.h>
using namespace std;
int star[]={1,2,3,4,6,7,8,9,11,12,13,14};
int num=0; //统计排列的个数
int b[]={-1,1,-5,+5};//上下左右4个方向。
bool bfs(void){
//star[0]~star[4]这前5个数是递归出来的5个数。用BFS判断它们是否连通
bool status[5]={false};
//这5个数的状态,判断其中某个数是否已经用队列处理过
int p=0;//进队列的个数。如果5个数都进过队列