简单不相交集(课后小结-基本理论-复杂性)

/*
 *Copyright (c) 2007, cn.edu.seu.cose
 *File: disjoint.h
 *
 *Version: 1.1
 *Author: knightzzy
 *Date: 2007.5.6
 */
#ifndef _DISJOINT_H
#define _DISJOINT_H
#define MAXN 100
int rank[MAXN];
 
void InitSet (int n, int *p);
int Find (int i, int *p);
void Union (int i, int j, int *p);
 
#endif
 
/*
 *Copyright (c) 2007, cn.edu.seu.cose
 *File: disjoint.c
 *
 *Version: 1.1
 *Author: knightzzy
 *Date: 2007.5.6
 */
#include "disjoint.h"
void InitSet (int n, int *p)
{
       int i;
       for (i=0; i<n; i++)
       {
              p[i] = i;
              rank[i] = 0;
       }
}
/*
//Find实现的版本1
//带路径压缩的递归Find程序
int Find (int i, int *p)
{
       if (p[i] != i) p[i] = Find (p[i], p);
       return p[i];
}
*/
//Find实现的版本2
//带路径压缩的循环Find程序
int Find (int i, int *p)
{
       int r, s;
       //寻找根
       for (r=i; p[r]!=r; r=p[r])
              ;
       //从i到根的各节点直接指向根节点
       while (i != r)
       {
              s = p[i];
              p[i] = r;
              i = s;
       }
       return r;
}
//将含元素i,j的集合合并
void Union (int i, int j, int *p)
{
       int a, b;
       a = Find (i, p);
       b = Find (j, p);
       if (a == b) return;
       if (rank[a] > rank[b])
       {
              p[b] = a;
       }
       else
       {
              p[a] = b;
              if (rank[a] == rank[b])
              {
                     rank[b]++;
              }
       }
}
  
结论1:rank[i]是不会减少的,当某次合并后i不再为根,则从此rank[i]就不再改变。
结论2:对所有不为根的结点的i(满足p[i]!=i),都有rank[p[i]]>rank[i](因合并时rank大的作根)。
结论3:以i为根的集合中的元素个数>=2**rank[i](可以针对上述所给Union算法,用归纳法证明)
结论4:a)至多有n/2**r个秩(rank)为r的结点。b)没有一个结点的秩大于Log2(n)。
在Union算法中,除了2个Find以外,其余的工作量是O(1)。所以主要的工作量是花在Find指令的执行上。所以,根据平摊分析的聚集方法,O(n)条Unoin-Find指令的时间复杂度,主要取决于O(n)条Find指令(其余工作的总和为O(n))。
 
其时间复杂度的分析与Ackerman函数有关。
Ackerman函数(增长极快的函数):
F(i)=2**F(i-1), F(0)=1, F(1)=2, F(2)=4, F(3)=16, F(4)=65536, F(5)=2**65536
其广义逆函数G(r) 是增长极慢的函数。
把秩r按照G(r)分组,秩r属于第G(r)组。(e.g: 秩为5则属于第3组)
由结论4的b),r <= Log2(n)。所以最大组号不超过G([Log2(n)](取大)) <= G(n)-1,即最多有G(n)个组。
定义N(g):其秩在组号为g的组中结点的个数。注意:组号为g的组中的秩是从F(g-1)+1到F(g)。由结论4 a) 秩为r的结点至多有n/2**r个,故有
N(g)<=SUM(n/2**r)(其中r From F(g-1)+1 To F(g))
化解后有N(g)<=n/F(g)
这样,执行一条Find(i0)指令,若p[i0]=i1,p[i1]=i2,... ,p[i(m-1)]=im,而im是根,则执行路径压缩后,有p[ik]=im (0<=k<=m-1)。另外,由结论2(父结点的秩大于子结点的秩)有:rank[i0]< rank[i1]< rank[i2]<.<rank[i( m-1)] <rank[im]
 
用平摊分析的聚集方法,把O(n)条Find指令的耗费分为三类:
1) O(n)条Find指令的根费用,
2) O(n)条Find指令的组费用,
3) O(n)条Find指令的路径费用。
根费用:执行一条Find指令时,处理根及其儿子所需的费用。一条Find指令只会碰到一个根(及其儿子),故O(n)条Find指令的根费用为O(n),这样,根及其儿子的费用已全部计算在内。
 
非根(及其儿子)的结点只有两种可能性:
1、该结点的秩与其父结点的秩不在同一个秩组中;
2、该结点的秩与其父结点的秩在同一个秩组中。
前者的费用计为组费用,后者的费用计为路径费用。
 
组费用:若结点ik (0<=k<=m-2)与其父结点ik+1的秩不在同一个秩组中,则对ik收取一个组费用。因最多有G(n)个组,故一条Find指令最多只会碰到G(n)个结点、其秩与其父结点的秩不在同一个秩组中,每个点处理为常数时间,故O(n)条Find指令的组费用最多为O(n*G(n))。
 
路径费用:若结点ik (0<=k<=m-2)与其父结点ik+1的秩在同一个秩组中,则对ik收取一个路径费用。O(n)条Find指令的路径费用不是按一条条的Find指令来计算的,而是按在执行O(n)条Find指令中,结点ik总共被收取了多少路径费用来计算的。在O(n)条Find指令(其间可能夹有Union指令)的执行中,考虑任一个被收取路径费用的结点,(此时该结点的秩与其父结点的秩必定在同一个秩组中,且该结点不是根结点的儿子,否则应收取根费用而不是路径费用,)每收取一次路径费用,Find指令将其变为根结点的儿子(路径压缩),从而其新父结点(根)的秩比其原父结点的秩至少高1。当某条Union指令将其新父结点作成其它某根结点的儿子,则对该结点又可以收取路径费用(只要该结点的秩与其当前父结点的秩仍在同一个秩组中,且下面的某条Find指令执行时经过该结点)。该结点(设该结点的秩在组g中)可以收取路径费用的次数最多为F(g)-(F(g-1)+1)。(因为每次收取路径费用时必然执行路径压缩,使得该结点的秩与其新父结点的秩差至少增1。)故路径费用最多收取F(g)-(F(g-1)+1) 次之后,其父结点的秩与该结点的秩就不可能再在同一个秩组中,而之后对该结点也就不可能再收取路径费用。由于其秩在组号为g的组中结点的个数不超过n/F(g),故组g中的结点的收取的路径费用不超过[n/F(g)]*[F(g)-(F(g-1)+1)]< [n/F(g)]*F(g)=n。因总共只有G(n)个组,故所有结点在O(n)条Find指令的执行中,收取的路径费用不超过O(n*G(n))。
 
三项费用总加起来,有O(n)+ O(n*G(n))+O(n*G(n))=O(n*G(n))
于是可得结论:如果合并是是把小集合并入大集合,且执行Find指令时实施路径压缩,则执行O(n)条Unoin-Find指令的时间复杂度为O(n*G(n));
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值