湖南大学 数据结构 并查集

最近很忙还接了一个“家教”单,又不想太糊弄,在备课的时候备课到并查集的部分,想起以前很难理解的并查集算法,就写了这篇博客。

并查集

1 待解决的问题

  • 合并(Union):把两个不相交的集合合并为一个集合。
  • 查询(Find):查询两个元素是否在同一个集合中。

2 与“树”章节的关联

树 --父节点表示法

在这里插入图片描述
在算法中数据结构与此类似,有些许不同的是,一般将根节点的父节点指向其自己,或者用
#define ROOT -1
然后根节点的父节点的索引号为ROOT

3 书上的描述

书上给出了一些等价对,然后让你判断某两个元素是否属于同一个集合

理解了这个问题需求,其实你也可以猜到,树的样子并不是那么重要,因此等价对处理的先后可能会造成画出来的树不一样,这是很正常的。

如何理解这类问题?

洛谷上有一道题,如下

某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

通过这个问题就很好了解了,原来是来找同门的啊!

4. 求解

理解了问题和问题的需求之后,算法思想如下:

  1. 一开始每个元素都在独立的只包含一个结点的树中,而他自己就是根节点。

  2. 通过使用函数differ可以检查一个等价对中的两个元素是否在同一棵树中。

  3. 如果不是就通过union函数归并两个等价类。归并时通常让节点个数少的指向结点个数多的。

那么differ函数与union函数实现?


bool ParPtrTree::differ(int a, int b) {

  int root1 =FIND(a);           // Find root ofnode a.找a的根

  int root2 =FIND(b);           // Find root of node b.找b的根

  return root1!= root2;         // Compare roots.比较两根

}
void ParPtrTree::UNION(int a, int b) {        // Merge subtrees.合并树

  int root1 =FIND(a);                          // Find root of node a

  int root2 =FIND(b);                         // Find root of node b

  if (root1 !=root2) array[root2] = root1; // Merge合并

}

// FIND with path compression 返回根的值

int ParPtrTree::FIND(int curr) const {

  if(array[curr] == ROOT) return curr;      // At root已经是根则返回

  array[curr] =FIND(array[curr]);

  return array[curr];

}

理解算法原理后,做应用就不难了

我们再将他与父指针表示法与其相结合

preview

例如这张图里,如果此时输入2 6 ,那么应该是拿1 4去比较谁的子节点多 ,“输家”会指向“赢家”
1 4之间的battle,会把1作为4的子节点插入,或者4作为1的子节点插入,而非直接对2 6进行操作。

5. 路径压缩

这也是一个小考点

在这里插入图片描述

比如这颗树,其实我们要判断的是这些节点是否是属于同一个集合,如果用这种方式,可能出现极端情况,因为在建树的过程中,树的最终形态严重依赖于输入数据本身的性质,比如数据是否排序,是否随机分布等等。比如在输入数据是有序的情况下,构造的BST会退化成一个链表。在我们这个问题中,也是会出现的极端情况的,比如这个H他的深度就很没有必要,首先做的一个改进是改进union的代码。

void union(int p, int q)  
{  
    int i = find(p);  
    int j = find(q);  
    if (i == j) return;  
    // 将小树作为大树的子树  
    if (sz[i] < sz[j]) { id[i] = j; sz[j] += sz[i]; }  
    else { id[j] = i; sz[i] += sz[j]; }  
    count--;  
}

可以发现,通过sz数组决定如何对两棵树进行合并之后,最后得到的树的高度大幅度减小了。这是十分有意义的,因为在Quick-Union算法中的任何操作,都不可避免的需要调用find方法,而该方法的执行效率依赖于树的高度。树的高度减小了,find方法的效率就增加了,从而也就增加了整个Quick-Union算法的效率。

那么find是否也可以做一个优化呢?

但是可以的

int find(int p)  
{  
    //溯源
    while (p != id[p])  
    {  
        id[p] = id[id[p]];  
        p = id[p];  
    }  
    return p;  
} 
//或者使用递归的方式
int find(int p)
{
    if(id[p]==p) return p;
    return id[p]=find(id[p]);
}

在这里插入图片描述

这棵树用之前的方法,查找(E,H) 会变成这样

在这里插入图片描述

而优化后的find使用后,会变成什么样呢?

在这里插入图片描述

习题练习

假设二叉树 BT 采用二叉链表存储结构,设计一个算法

void Parent(BinNode * BT,Elem x, BinNode *P)

求指定值为 x 的结点的父结点 P。提示:根结点的父为 NULL, 若在二叉树 BT 中未找到值为 x 的结点,P 也为 NULL

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值