• 算法导论笔记:14数据结构的扩张


    一:概述

             一些工程应用只会使用教科书式的标准数据结构,但是也会有些应用需要对现有的数据结构进行少许的创新和改造只有很少的情况会创造全新的数据结构。

     

    二:动态顺序统计

             顺序统计:n个元素中第i个顺序统计量,就是具有第i小关键字的元素。对于一个无序的集合,可以在O(n)的时间内得到任意的顺序统计量。

     

             利用红黑树,可以在O(lg n)的时间内确定任意顺序统计量,同时也能在O(lg n)时间内确定元素的秩,红黑树中,元素的秩代表中序遍历时输出的位置。

     

             支持快速顺序统计操作的数据结构,是在红黑树的基础上进行了少许的改造,形成了顺序统计树。顺序统计树就是红黑树,只不过树种每个节点,除了具有红黑树的key, color, left, right, p等五个性之外,还附加了另外的性质:size,它表示以x为根的子树(包括x本身)的内(不计哨兵)节点数。并且定义哨兵的size0,所以有下面的性质:

             x.size = x.left.size + x.right.size + 1

             下图就是一颗顺序统计树,每个节点中,上边是key,下边是size:

            

    1:确定给定秩的元素

             利用size信息,可以在O(lg n)时间内确定给定秩的元素:OS-SELECT。它返回以x为根的子树中,指向第i小的关键字元素的指针,代码如下:

             OS-SELECT(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)

             该算法中,r表示以x为根的子树中,节点x的秩。如果i小于r,则需要在左子树中找第i小的元素,如果i大于r,则需要在右子树中找第i-r小的元素。

             OS-SELECT的总时间与树的高度成正比,所以该算法的时间复杂度为O(lg n)。

     

    2:确定一个元素的秩

             给定元素x的指针,可以用OS-RANK返回元素x在树中的秩,代码如下:

             OS-RANK(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

                      returnr

             在该算法中,r表示以y为根的子树中,x的秩。如果y为父节点的左孩子,则x在y为根的子树中的秩也是在y.p为根的子树中的秩。如果y为父节点的右孩子,则xy.p为根的子树中的秩,还需要加上y.p为根的子树中,左分支的节点数和y.p本身所占的位置,才是xy.p为根的子树中秩。所以,最后y==root时,r就是在整个树中的秩。

             该算法的时间复杂度为O(lgn)。

     

             3:对子树规模的维护

             利用size属性,可以很快的计算出顺序统计量,但是,如果在统计数的改变过程中,不能在合理的时间内维护size属性,那么添加size属性将是无意义的,比如,在插入或者删除的过程中,如果维护元素size属性的时间超过了O(lg n),则size变得无意义。下面就说明,插入和删除操作,会在O(lg n)时间内完成size的维护。

             插入操作,包括两个阶段,第一阶段从根节点开始沿树下降,将新节点插入到叶节点中。第二阶段是沿树上升,做一些变色或者旋转操作维护红黑树的性质。在第一阶段中,为了维护size的属性,可以将从根节点到新插入的叶子节点的路径上的每个节点的size加1即可。这个操作的时间复杂度为O(lg n)。在第二阶段,只有旋转操作才会导致size的属性变化,而红黑树的插入操作的旋转次数最多为2次。旋转操作是一种局部操作,只会使得2个节点的size属性失效,比如左旋代码中,增加下面两行就能维护size属性:

    y.size = x.size

    x.size = x.left.size + x.right.size +1

             左旋操作如下图:

             右旋操作也类似,所以对于插入操作来说,增加size属性后,插入的时间复杂度还是O(lg n)。

             删除操作也是分两个阶段,第一阶段找到y,要么将y删除,要么将y上移,所以遍历一条从节点y所在位置到根节点的路径,将每个节点的size减1即可。第二阶段是为了维护红黑树性质的变色和旋转操作,因删除操作最多需要3次旋转,因而,同插入操作一样,删除操作的时间复杂度也是O(lg n)。

     

    三:如何扩张数据结构

             对基本的数据结构进行扩张,以支持一些附加功能,在算法设计中是很常见的,下面是扩张一种数据结构的基本步骤:

             a:选择一种基本数据结构;

             b:确定数据结构中需要维护的附加信息;

             c:检验基础数据结构上的基本修改操作是否能维护附加信息;

             d:设计新的操作。

            

             比如在利用红黑树进行顺序统计的工作中,a:选择了红黑树作为基本数据结构。b:在红黑树节点中添加了size属性。c:保证插入和删除操作仍能在O(lg n)时间内维护size属性。d:设计顺序统计的操作。

     

             当红黑树作为基本数据结构时,可以证明,某些类型的附加信息总是可以用插入和删除操作进行有效的维护:如果f是红黑树元素的附加属性,假设对于任意节点xf的值仅仅依赖于x, x.left, x.right的信息,还可能包括x.left.fx.right.f。那么在插入和删除操作中,可以在O(lg n)时间内维护f的属性。这是因为f属性的变动,只会影响到x的祖先,所以修改x.f,只需要更新x.p.f, x.p.p.f……。如此沿树向上即可。

     

    四:区间树

             区间[a,b]便于表示占用一连续时间的事件,例如查询由时间区间构成的数据库,找出给定时间内发生了什么事。

             可以用对象i表示区间[a,b], i.low = a;i.high = b。对于两个区间i和j来说,如果:i.low <= j.high并且j.low <= i.high,则表示两个区间i和j重叠。

             区间树是一种红黑树,树中的元素表示一个区间,该树支持的操作有:插入,删除,查找,其中,查找操作是返回一个指向x的指针,x与给定的区间i重叠。如果x不存在,则返回T.nil。

             下图为一些列的区间:

             为了支持查找重叠区间的操作,:

             a:选择红黑树作为基本数据结构,每个节点表示一个区间int,节点的关键字为区间的低点,也就是x.int.low。所以该数据结构中,中序遍历就能按照low的次序列出各区间。

             b:附加信息为max,max表示了以x为根的子树中,所有区间的端点high的最大值。

             c:x的max值:x.max =max(x.int.high, x.left.max, x.right.max), 所以符合上面的定理,因而插入和删除操作能在O(lgn)时间内完成。

             d:插入和删除操作与红黑树的一样,这里只需要设计SEARCH操作。它可以找出树中,与给定区间i重叠的节点x,如果x不存在,返回T.nil。代码如下:

             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

             该算法能保证,如果树中存在与i重叠的区间,该区间一定能被找到。该算法在循环中,判断x.left != T.nil and x.left.max >= i.low,如果不满足x.left.max >= i.low,则说明左子树中的节点的high值,都小于i的low值,所以左子树中不存在与i重叠的区间,因而在右子树中。

             如果满足了x.left.max >= i.low属性,则有可能左右子树中都会包含与i重叠的区间。首先在左子树中找,如果左子树中没有的话,右子树中一定也不存在,因为树的关键字为low值,左子树的low值肯定比右子树小。

  • 相关阅读:
    【刷题】BZOJ 1061 [Noi2008]志愿者招募
    【比赛】NOIP2017 列队
    react_app 项目开发 (6)_后台服务器端-node
    react_app 项目开发 (5)_前后端分离_后台管理系统_开始
    react_app 项目开发 (3)_单页面设计_react-router4
    react_app 项目开发 (2)_axios_pubsub-js
    react_app 项目开发
    React_基本原理_ajax
    React_生命周期
    组件化
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247230.html
Copyright © 2020-2023  润新知