算法导论 第21章 用于不相交集合的数据结构

不相交集合的定义和操作

    不相交集合的数据结构保持一组动态集合,每个集合通常有一个代表(也是该集合的成员),用以标识该集合。显然,由于这组动态集合是不相交的,那么集合代表(标识)自然也是独一无二的。对于不相交集合,我们希望其能够支持以下操作:

    1、MAKE-SET(x):创建一个集合,只含有一个元素x,那么该集合的代表自然就是x;

    2、FIND-SET(x):返回元素x所在的集合,即该集合的标识(代表);

    3、UNION(x,y):合并x和y分别所属的集合,合并后的集合的代表是x(或者y)所属集合的代表。


不相交集合的链表表示

    这个不讨论了,比较简单。


习题21.1-3

   2|E|,|V| - k

习题 21.2-4

   时间O(n),UNION操作每次都是将长度为1的链表链到较长链表上的,指针修改时间均为O(1)。

习题 21.2-5

   回顾单链表的链接。我们只需要将较短的集合链表链到较长集合链表的头结点的下一位置,即所谓头插法。


不相交集合森林

    我们直接给出源代码,算法较简单,就不说了。采用了按秩(此处秩是指节点高度的一个上界,并不是节点数量)合并和路径压缩。

#include<iostream>
#include<vector>

using namespace std;

class UFS
{//默认从1开始
private:
	vector<size_t> parent;
	vector<size_t> rank;
public:
	explicit UFS(size_t size) :parent(size + 1), rank(size + 1){}
	void makeSet(size_t i)
	{
		parent[i] = i;
		rank[i] = 0;
	}
	void Union(size_t x, size_t y)
	{
		size_t left = findSet(x), right = findSet(y);
		if (rank[left] < rank[right])
			parent[left] = right;
		else
		{
			parent[right] = left;
			if (rank[left] == rank[right])
				++rank[left];
		}
	}
	size_t findSet(size_t x)
	{
		if (x == parent[x]) return x;
		parent[x] = findSet(parent[x]);
		return parent[x];
	}
};

习题 21.3-2

size_t findSet(size_t x)
	{
		size_t p = x;
		while (p != parent[p])
			p = parent[p];
		while (x != p)
		{
			size_t y = parent[x];
			parent[x] = p;
			x = y;
		}
		return parent[x];
	}

习题 21.3-3

    1、首先进行n此的MAKE-SET,创建n个只含有一个元素的集合;

    2、接着进行n-1次的UNION操作,那么可以得到一棵树,由于采用按秩合并,那么该树的高度为O(lgn),以上两步的时间为O(n);

    3、最后,再进行m-2n+1次的FIND-SET操作,每次的时间为O(lgn),假设操作足够多,至少m>=3n,那么至少得有n-1次FIND-SET操作,又n<=m/3,所以操作总时间为O(mlgn)。


习题 21.3-4

    采用记账方法,可得时间为O(m)。

    1、对每个MAKE-SET操作赋予两块钱的报酬,一块钱支付创建集合的花销,另外一块钱存着,留作后用;

    2、对每个LINK操作赋予一块钱的报酬,刚好用于支付该操作的代价;

    3、同样的,对于每个FIND-SET操作,我们也只赋予一块钱支付;若只采用路径压缩,在递归调用中,对于每一个节点,最多有一次被重新设置parent,这时候我们用之前存着的一块钱来支付代价,刚好足够。

   由于每个操作的代价最多为2,那么m个操作序列,最多的总代价也就是2m,因此总时间为O(m)。另外,上述分析中,我们只采用了路径压缩,没有采用按秩合并。


思考题21-1

   (a)E1~E6分别为:4,3,2,6,8,1.

   (b)采用循环不变式:对第i次迭代,即数字i,前面的1,2...i-1均找到了所处的集合j',并当j<m+1时,都已经正确的设置了extracted[j']和将集合j'往后合并了,若等于m+1,则不处理;现在对于i,依然如此执行。

    初始:i为1,前面没有数字,成立;

    保持:确定数字i处于集合j,算法3~6行,若j<m+1,则j不可能会是已经被设置的,因为每次集合合并都会往后合并,那么设置extracted[j] = i,并且找到下一个集合将j合并过去;若j = m+1,则不作处理,因为没有第m+1次extraction。

    终止:i = n + 1.根据循环不变式可知:i之前,也就是1~n均已找到所属集合j(可能为m+1),并正确设置了extracted(除了为m+1时),由此证明该算法正确。

    (c)对于诸insert集合用一个链表链起来,集合j被合并,则将其删除。

      1、确定i属于哪个集合,采用FIND-SET;

      2、取得下一个可用集合,用链表的next域;

      3、合并则使用UNION;

    在此之前的MAKE-SET最多n次,那么UNION最多n-1次,根据循环,还有n次FIND-SET,那么时间显然为O(na(n))。



思考题 21-3 Tarjan的脱机最小公共祖先算法






  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值