算法导论 第十四章:数据结构的扩张 笔记(动态顺序统计、如何扩张数据结构、区间树)

版权声明:站在巨人的肩膀上学习。 https://blog.csdn.net/zgcr654321/article/details/83149728

动态顺序统计:

一棵顺序统计量树T通过简单地在红黑树的每个结点存入附加信息而成。在一个结点x内,除了包含通常的红黑树的域key[x] ,
color[x] , p[x], left[x]和right[x] , 还包括域size[x]。

这个域中包含以结点x为根的子树的(内部)结点数(包括x本身),即子树的大小。如果定义哨兵为0, 也就是设置size[nil[T]] 为0,则有等式
size[x] = size [left[x]]+ size[right[x]]+1

如:

检索具有给定排序的元素:

过程 OS-SELECT(x, i) 返回一个指向以 x 为根的子树中包含第 i 小关键字的结点的指针。为找出顺序统计树 T 中的第 i 小关键字,调用过程 OS-SELECT(T.root, i) 。

伪代码:

OS-SELECT(x, i)
    r = x.left.size + 1
    if i == r
        then return x
    else if i < r
        return OS-SELECT(x.left, i)
    else return OS-SELECT(x.right, i)

值size[left[x]]表示在对以x为根的子树进行中序退历时排在x之前的结点个数。这样, size[left[x]+1]即为以x为根的子树中x的排序。

对含n个元素的动态集合, OS-SELECT 的运行时间为O(lgn)。

OS-SELECT 的第1行计算以x为根的子树中结点x的排序r 。如果i=r, 结点x 就是第i小元素,返回第3行的x。如果i<r, 则第t小元素就在x的左子树中,故在第5行中对left[x]进行递归调用。如果i>r, 则第i 小元素在x的右子树中。因为在对以x为根的子树进行中序遍历时,共有r个元素在x的右子树前,故在以x为根的子树中第i小元素即为以right[x]为根的子树中第(i-r)小元素。第6行进一步递归地确定这个元素。

确定一个元素的秩:

给定指向一顺序统计树T中结点x的指针,过程 OS-RANK 返回在对T进行中序遍历后得到的线性序中x的位置。

伪代码:

OS-RANK(T, x)
    r = x.left.size + 1
    y = x
    while y != T.root
        if y == y.p.right
            r = r + y.p.left.size + 1
        y = y.p
    return r

x的秩可以视为在对树的中序遍历中,排在x之前的结点个数再加1(x本身)。在最坏情况下,对含n个结点的顺序统计树, OS-RANK 的运行时间为 O(lgn)。

对子树规模的维护:

插入操作的修改:

1、新节点的插入过程中,需要对从根到将要插入的位置过程中遍历的结点的size加1; 

2、维护红黑树的左旋和右旋函数需要在最后添加以下语句: 

 //左旋
    y.size = x.size;
    x.size = x.left.size + x.right.size +1;
 //右旋
    x.size = y.size;
    y.size = y.left.size + y.right.size +1;

删除操作的修改:

1、如果要删除的结点z少于两个孩子,则从z到根T的过程遍历的结点size减1;如果要删除的结点z多于两个孩子,则从z的后继y处向上到T的过程中,遍历的结点size减1;  

2、也是同样在左右旋过程中,添加以上的语句; 

插入操作和删除操作运行时间都是O(lgn)。

如何扩张数据结构:

对一种数据结构的扩张过程可分为四个步骤:

选择基础数据结构;

确定要在基础数据结构中添加哪些信息;

验证可用基础数据结构上的基本修改操作来维护这些新添加的信息;

设计新的操作。

对红黑树的扩张:

选择数据结构(红黑树);

决定附加信息(计算节点子树大小);

验证数据结构不受修改操作影响(插入删除后需旋转);

在新的结构上进行新的操作。

动态有序统计:(SELECT在动态集中返回第i小的数,RANK在有序集中返回排名为i的元素)

对于一个无序的集合,能够在O(n)的时间内确定任何的顺序统计量,我们要修改红黑树,使得在O(lgn)时间内确定任何的顺序统计量。

顺序统计树: 在每个结点上存储附加信息的一棵红黑树;

附加属性x.size = x.left.size + x.right.size + 1;

维护size时间复杂度:O(1)。所以插入、删除,包括维护size属性,都只需要O(lgn)时间。

OS-SELECT( x, i ) // 过程返回一个指针,指向以x为根的子树中包含第i小关键字的结点 
    r= x.left.size + 1 
    if i == r 
        return x 
    else if i < r 
        return OS-SELECT( x.left, i ) 
    else 
        return OS-SELECT( x.right, i - r ) 

OS-RANK( T, x ) // 过程返回T中序遍历对象的线性序中x的位置 
    r= x.left.size + 1 
    y= x 
    while y != T.root 
        if y == y.p.right 
            r += y.p.left.size + 1 
        y = y.p 
    return r 

区间树:

在这里,我们要扩展红黑树以支持由区间构成的动态集合上的操作(假设区间都是闭区间)。一个闭区间是一个实数的有序对[ t1, t2],其中 t1 <= t2 。我们可以把一个区间[ t1, t2 ]表示成一个对象,其各个域为 i.low = t1 (低端点), i.high = t2 (高端点)。任意两个区间 i 和 i' 都满足 区间三分法 ,即:

i 和 i' 重叠( i.low <= i'.high ,且 i'.low <= i.high );

i 在 i' 左边( i.high < i'.low );

i 在 i' 右边( i'.high < i.low );

区间树是一种对动态集合进行维护的红黑树,该集合中的每个元素 x 都包含一个区间 x.int 。区间树支持下列操作:

INTERVAL-INSERT(t, X) :将包含区间域 int 的元素 x 插入到区间树 T 中。

INTERVAL-DELETE(T, x) :从区间树 T 中删除元素 x 。

INTERVAL-SEARCH(T, i) :返回一个指向区间树 T 中元素 x 的指针,使 x.int 与 i 重叠;否则返回 T.nil 。

如:

步骤1:基础数据结构

基础数据结构为红黑树,其中每个结点 x 包含一个区间域 x.int , x 的关键字为区间的低端点 x.int.low 。

步骤2:附加信息

每个结点还要包含一个值 x.max ,即以 x 为根的子树中所有区间的端点的最大值。

步骤3:维护信息

必须验证对含n个结点的区间树的插入和删除能在 O (lgn)时间内完成。给定区间 x.int 和 x 的子结点的 max 值,可以确定 

x.max =max( x.int.high ,x.left.max , x.right.max )。

步骤4:设计新操作

唯一需要的新操作是 INTERVAL-SEARCH 。

它用来找出树T中覆盖区间i的那个结点。如果树中覆盖i的区间不存在,则返回指向哨兵nil[T] 的指针。

伪代码:

INTERVAL-SEARCH(T, i)
    x = T.root
    while x != T.nil and i does not overlap x.int
        if x.left != T.nil and x.left.max >= i.low
            x = x.left
        else x = x.right
    return x

分支if执行时,如果在以x.left为根的子树中,没有与i重叠的区间,则树的其他部分也不会包含与i重叠的区间。

分析:

有x.left.max >= i.low,根据max属性定义,在x的左子树中必定存在某区间i',满足:i'.high = x.left.max >= i.low,如果i与i'不重叠,则i.high < i'.low <= i''.low,其中i''为右子树中任意区间,所以右子树也不包含与i重叠的区间。

如:

修改后的红黑树:

修改红黑树得到的区间树:

从图可以看出,对区间树以每个节点的左端点值进行中序遍历即可得到有序的序列。

猜你喜欢

转载自blog.csdn.net/zgcr654321/article/details/83149728