前几次发了3篇文章,练练手。今天才是正式接触博客园。
由于培训班进度有点慢,也是为了打好基础,今天开始正式自学。
今天学并查集:
(1)并查集是什么?
说白了,并查集是一种数据结构,有以下功能。
1.检查a和b是否为一组。(same操作)
2.合并a与b的组。(unite操作)
比如:
此时的并查集每个点都是单独的一组。
1 2 3 4 5 6 7 8 9........
然后,合并了1和3所在的一组。
【13】 2 4 5 6 ......
合并2和4所在的组。
【1,3】 【2,4】 5 6 7 ......
合并1和4所在的组(这里的1的所在的组并不是1单个数,是【1,3】这个大组)
【1,2,3,4】 5 6 7 ......
查询1和2是否在一组。返回1。
(2)并查集的本质
其实,并查集的大概思路就是对于每一组,选出一个老大,查询时,看看两个数的老大是否相同就可以了。合并时,让一个老大臣服于另一个老大,这样,两个组的人都有共同的老大了。
那具体如何实现呢?
一个组可以理解成一棵树,那老大呢?理解成跟就可以了。
(3)并查集的实现。
由于是树,会有退化这种情况,需要避免。(让高度大的树的根成为高度小的树的根的父亲,有效避免退化)这是第一个优化的地方。
第二个,由于每次合并,需要查一下根,下一次又查一下根。很浪费时间,只要让求好根的点直接连向它的根即可。(称为路径压缩)。
加入这两种优化后并查集效率极高,所有操作都是O(a(n)),a(n)这个函数比logn还快!!。
以下是一种实现方法:
#include <bits/stdc++.h>
#define ll long long
#define MP make_pair
#define pa pair<int,int>
#define fift_que pa,vector<pa >,greater<pa >
#define p_qw priority_queue<fift_que >
using namespace std;
int a,b;
typedef unsigned long long ull;
typedef unsigned int ui;
const int N=1e6+1;
//并查集
int dad[1000006];//节点的父亲
int rank[1000006];//树高
void init(int n){
memset(rank,0,sizeof(rank));
for(int i=0;i<=n;i++)dad[i]=i;
}//初始化
int find(int x){
//找根
if(dad[x]==x)return x;
else return dad[x]=find(dad[x]);//路径压缩
}
void unite(int x,int y){//合并
int lx=find(x),ly=find(y);
if(rank[lx]<rank[ly])dad[lx]=ly;
else{//避免退化
dad[ly]=lx;
if(rank[lx]==rank[ly])rank[lx]++;
}
}//查询
bool same(int x,int y){
return find(x)==find(y);
}
int main(){
init(N);
//1 2 3 4 5 6 7 8 9......
unite(1,2);
//[1,2] 3 4 5 6 7 8 9......
unite(3,4);
//[1,2] [3,4] 5 6 7 8 9......
cout<<same(1,4)<<" ";
//1---[1,2] 无4 返回0
unite(2,3);
//[1,2,3,4] 5 6 7 8 9......
cout<<same(1,4)<<" ";
//1---[1,2,3,4] 有4,返回1
unite(6,9);
//[1,2,3,4] 5 [6,9] 7 8 ......
return 0;
}