数据结构(一)
并查集
- 原始版
第一步先初始化
int f[N];
inline void init(int n)
{
for(int i=1;i<=n;i++)
fa[i]=i;
}
假如有编号1,2,3,...,n,n个元素,我们用一个数组fa[]来储存每个元素的父节点(因为每个元素有且只有一个父节点,所以这是可行的),一开始都将父节点设为自己
第二步查询
int find(int x)
{
if(fa[x]==x)
return x;
else
return find(fa[x]);
}
使用递归的写法实现对代表元素的查询:一层一层的访问父节点,直至根节点(根节点的标志就是父节点是本身)。要判断两个元素是否属于同一个集合,只需要看他们的根节点是否相同即可
第三步合并
inline void merge(int i,int j)
{
fa[find(i)]=find(j);
}
先找到两个集合的代表元素,然后将前者的父节点设为后者即可,当然也可以将后者的父节点设为前者
- 进阶版,路径压缩
在每次查询时,把沿途的每个节点的父节点都设为根节点即可。在下次查询时就可以省事
!!!注意:只有在每次查找时才进行路径压缩,因此初始化和合并的部分代码都相同
//标准版
int find (int x)
{
if(x==fa[x])
return x;
else {
fa[x]=find (fa[x]);
return fa[x];
}
}
//精简版
int find (int x)
{
return x==f[a]?x:(fa[x]=find(fa[x]));
}
- 进阶版 按秩合并
按秩合并的原理就是应该把结构简单的树往结构复杂的树上合并,而不是相反,因为这样合并后到根节点距离变长的节点个数比较少
开一个rank[]数组记录每个根节点对应的树的深度(如果不是根节点,其 rank相当于它作为根节点的子树的深度)。一开始,把所有元素的rank(秩)设为1。合并时比较两个根节点,把rank较小者往rank较大者合并。
(注意:路径压缩和按秩合并如果一起使用,时间复杂度接近O(n),但可能会破坏rank的准确性)
初始化(按秩合并)
inline void init(int n)
{
for(int i=1;i<=n;i++)
{
fa[i]=i;
rank[i]=1;
}
}
合并
inline void merge(int i,int j)
{
int x=find(i),y=find(j);
if(rank[x]<=rank[y])
fa[x]=y;
else
fa[y]=x;
if(rank[x]==rank[y]&&x!=y)
rank[y]++;
}