路径压缩
前面的并查集的复杂度实际上有些极端情况会很慢。比如树的结构正好是一条链,那么最坏情况下,每次查询的复杂度达到了 O(n)。
路径压缩 的思想是,我们只关心每个结点的父结点,而并不太关心树的真正的结构。
这样我们在一次查询的时候,可以把查询路径上的所有结点的 father[i] 都赋值成为根结点。只需要在我们之前的查询函数上面很小的改动。
int get(int x) { if (father[x] == x) { // x 结点就是根结点 return x; } return father[x] = get(father[x]); // 返回父结点的根结点,并另当前结点父结点直接为根结点 }
下面图片是路径压缩前后的对比。
路径压缩在实际应用中效率很高,其一次查询复杂度平摊下来可以认为是一个常数。并且在实际应用中,我们基本都用带路径压缩的并查集。
带权并查集
所谓带权并查集,是指结点存有权值信息的并查集。并查集以森林的形式存在,而结点的权值,大多是记录该结点与祖先关系的信息。比如权值可以记录该结点到根结点的距离。
例题
在排队过程中,初始时,一人一列。一共有如下两种操作:
- 合并:令其中的两个队列 A,BA,B 合并,也就是将队列 AA 排在队列 BB 的后面
- 查询:询问某个人在其所在队列中排在第几位
例题解析
我们不妨设 size[] 为集合中的元素个数,dist[] 为元素到队首的距离,合并时,dist[A.root] 需要加上 size[B.root]
(每个元素到队首的距离应该是到根路径上所有点的 dist[] 求和)
size[B.root] 需要加上 size[A.root](每个元素所在集合的元素个数只需查询该集合中根的 size[x.root])
1) 初始化:
void init() { for(int i = 1; i <= n; i++) { father[i] = i, dist[i] = 0, size[i] = 1; } }
2) 查找:查找元素所在的集合,即根结点。
int get(int x) { if(father[x] == x) { return x; } int y = father[x]; father[x] = get(y); dist[x] += dist[y]; // x 到根结点的距离等于 x 到之前父亲结点距离加上之前父亲结点到根结点的距离 return father[x]; }
路径压缩的时候,不需考虑 size[],但 dist[] 需要更新成到整个集合根的距离。
3) 合并
将两个元素所在的集合合并为一个集合。
通常来说,合并之前,应先判断两个元素是否属于同一集合,这可用上面的“查找”操作实现。
void merge(int a, int b) { a = get(a); b = get(b); if(a != b) { // 判断两个元素是否属于同一集合 father[a] = b; dist[a] = size[b]; size[b] += size[a]; } }
通过小小的改动,我们就可以查询并查集这一森林中,每个元素到祖先的相关信息。
看个题目
我们可以很容易的统计俩张卡片是否在同一个队列中,用并查集就可以了。
关键是怎么计算,在一个队列中的俩个卡片之间卡片数目,只要维护一下每个卡片到队列头的卡片数目就好了。
在计算同一队列中的俩个卡片之间卡片数目,只要把俩个卡片到队列头的卡片数目做差就可以了。
#include<iostream> #include<vector> #include<queue> #include<algorithm> #include<memory.h> #include<cmath> #define INF 0x3f3f3f3f using namespace std; int father[30100]; int son[30100]; //树的深度 int sizes[30100]; //到根结点的距离 int dists[30100]; int n,m,p,k,a,b,c,x,y,ans=0; void init(int n) { for(int i=1;i<=n;i++) { father[i]=i; son[i]=i; sizes[i]=1; dists[i]=0; } } int get(int x) { if(father[x]==x) return x; int y=father[x]; father[x]=get(y); dists[x]+=dists[y]; return father[x]; } int get_son(int x) { if(son[x]==x) return x; else return son[x]=get_son(son[x]); } void unite(int x,int y) { x=get(x); y=get(y); if(x!=y) { father[x]=y; dists[x]=sizes[y]; sizes[y]+=sizes[x]; } } int same(int x,int y) { return get(x)==get(y); } int main() { cin>>n; init(30050); char c; for(int i=0;i<n;i++) { cin>>c>>a>>b; if(c==‘C‘) { if(!same(a,b)) cout<<-1<<endl; else cout<<abs(dists[a]-dists[b])-1<<endl; } else if(c==‘M‘) { unite(a,b); } } return 0; }