第11章 搜索树 基本知识及python实现

Date: 2019-07-13

目录:

11.1 二叉搜索树(11.1.1 遍历二叉搜索树 11.1.2 搜索 11.1.3 插入和删除  11.1.4 python实现  11.1.5 二叉搜索树的性能 )

11.2 平衡搜索树  平衡搜索树的python框架(平衡操作的钩子 旋转和重组的非公开方法  创建树节点工厂)

11.3 AVL树 AVL树的定义 高度平衡属性 (11.3.1 更新操作:插入 删除)AVL树的性能(11.3.2 python实现)

11.4 伸展树 (11.4.1 伸展:zig-zig型 + zig-zag型 + zig型  11.4.2 何时进行伸展  11.4.3 python实现

11.5 (2,4)树 (11.5.1 多路搜索树:定义+多路搜索树+主要的多路径搜索树数据结构 11.5.2 (2,4)树的操作:插入 删除 ) (2,4)树的性能

11.6 红黑树 (11.6.1 红黑树的操作:插入和删除 )红黑树的性能 11.6.2 python实现

说明:这一章节的内容超级多,其中还有很多是利用图/树进行解释的,所以在此仅仅只能介绍一个大概,和展示一部分的实现代码,想要更加详细的了解这一部分内容的伙伴们,可以看Goodrich的相应书籍哈。

11.1 二叉搜索树

树型数据结构的一个重要用途是用作搜索树,在此我们使用搜索树结构来有效的实现有序映射。映射M的三种基本方法:

M[k]:在映射M中,如果存在与键k相关联的值v,返回v;否则,抛出KeyError。用__getitem__方法来实现。

M[k]=v:在映射M中,将键k与值v相滚关联,如果映射中已经包含键等于k的项,则用v替换现有值。用__setitem__方法来实现。

del M[k]:从映射M里删除键等于k的项;如果M中没有这样的项,则引发KeyError。用__delitem__实现。

有序映射ADT还有许多附加功能,以保证迭代器按照一定的顺序输出键,并且支持额外的搜索,如find_gt(k),find_range(start,stop).

在此处的二叉搜索树是每个节点p存储一个键值对(k,v)的二叉树,使得:

* 存储在p的左子树的键都小于k

* 存储在p的右子树的键都大于k

11.1.1 遍历二叉搜索树

二叉搜索树的中序遍历是按照键增加的顺序进行的

中序遍历可以在线性时间内被执行,当对二叉搜索树进行中序遍历时,根据以上定理我们可以在线性时间内产生一个映射中所有键的有序迭代。支持以下的方法:

first()返回一个包含最小键的节点,如果树为空,则返回None
last()返回一个包含最大键的节点,如果树为空,则返回None
before()返回比节点p的键小的所有节点中键最大的节点(中序遍历中在p之前的最后被访问的点),如果p是第一个节点,则None
after()返回比节点p的键大的所有节点中键最小的节点(中序遍历中在p之后的最先被访问的点),如果p是最后一个节点,则None

二叉搜索树的‘第一个'位置可以从根开始,并且只要左子树存在就继续搜索左子树。左子树搜索完毕之后访问根节点,然后递归搜索右子树。

11.1.2 搜索

二叉搜索树的结构特性产生的最重要的结果是它的同名搜素算法(二叉搜索算法)。将一个二叉搜索树中通过把它表示成决策树的形式定位一个特定的键。于此,每个节点p的问题就是期望键k是否小于、等于或大于存储在节点p的键,这表示为p.key()。如果答案是小于,那么继续搜素左子树;如果答案是等于,那么搜素成功终止;如果答案是大于,则继续搜素右子树,最后得到空子树,就表明没有搜素到。

二叉树搜索的总的运行时间是O(h).

11.1.3 插入和删除

删除一个节点要比插入一个节点更复杂,因为删除的位置可能在树中的任何位置(相比之下,插入总是在搜索路径的最后位置)

11.1.4 python实现

下面是一个基于二叉搜索树的TreeMap类的实现

from ..ch08.linked_binary_tree import LinkedBinaryTree
from ..ch10.map_base import MapBase

class TreeMap(LinkedBinaryTree, MapBase):
  """Sorted map implementation using a binary search tree."""

  #---------------------------- override Position class ----------------------------
  # 重载前面提出的Position类
  class Position(LinkedBinaryTree.Position):
    def key(self):
      """Return key of map's key-value pair."""
      return self.element()._key

    def value(self):
      """Return value of map's key-value pair."""
      return self.element()._value

  #------------------------------- nonpublic utilities -------------------------------
  def _subtree_search(self, p, k):  # 1 子树查找k
    """Return Position of p's subtree having key k, or last node searched."""
    if k == p.key():                                   # found match
      return p                                         
    elif k < p.key():                                  # search left subtree
      if self.left(p) is not None:
        return self._subtree_search(self.left(p), k)   
    else:                                              # search right subtree
      if self.right(p) is not None:
        return self._subtree_search(self.right(p), k)
    return p                                           # unsucessful search

  def _subtree_first_position(self, p):   # 2 返回p子树的最小键(左)
    """Return Position of first item in subtree rooted at p."""
    walk = p
    while self.left(walk) is not None:                 # keep walking left
      walk = self.left(walk)
    return walk

  def _subtree_last_position(self, p):    # 3 返回p子树的最大键(右)
    """Return Position of last item in subtree rooted at p."""
    walk = p
    while self.right(walk) is not None:                # keep walking right
      walk = self.right(walk)
    return walk
  
  #--------------------- public methods providing "positional" support ------------------
  def first(self):   # 4 返回第一个
    """Return the first Position in the tree (or None if empty)."""
    return self._subtree_first_position(self.root()) if len(self) > 0 else None

  def last(self):    # 5 返回最后一个
    """Return the last Position in the tree (or None if empty)."""
    return self._subtree_last_position(self.root()) if len(self) > 0 else None

  def before(self, p):   # 6 返回p之前的一个键
    """Return the Position just before p in the natural order.

    Return None if p is the first position.
    """
    self._validate(p)                            # inherited from LinkedBinaryTree
    if self.left(p):
      return self._subtree_last_position(self.left(p))
    else:
      # walk upward
      walk = p
      above = self.parent(walk)
      while above is not None and walk == self.left(above):
        walk = above
        above = self.parent(walk)
      return above

  def after(self, p):   # 7 返回p之后的一个键
    """Return the Position just after p in the natural order.

    Return None if p is the last position.
    """
    self._validate(p)                            # inherited from LinkedBinaryTree
    if self.right(p):
      return self._subtree_first_position(self.right(p))
    else:
      walk = p
      above = self.parent(walk)
      while above is not None and walk == self.right(above):
        walk = above
        above = self.parent(walk)
      return above

  def find_position(self, k):   # 8 
    """Return position with key k, or else neighbor (or None if empty)."""
    if self.is_empty():
      return None
    else:
      p = self._subtree_search(self.root(), k)
      self._rebalance_access(p)                  # hook for balanced tree subclasses
      return p
      
  #--------------------- public methods for (standard) map interface --------------------
  def __getitem__(self, k):  # 9 
    """Return value associated with key k (raise KeyError if not found)."""
    if self.is_empty():
      raise KeyError('Key Error: ' + repr(k))
    else:
      p = self._subtree_search(self.root(), k)
      self._rebalance_access(p)                  # hook for balanced tree subclasses
      if k != p.key():
        raise KeyError('Key Error: ' + repr(k))
      return p.value()

  def __setitem__(self, k, v):  # 10
    """Assign value v to key k, overwriting existing value if present."""
    if self.is_empty():
      leaf = self._add_root(self._Item(k,v))     # from LinkedBinaryTree
    else:
      p = self._subtree_search(self.root(), k)
      if p.key() == k:
        p.element()._value = v                   # replace existing item's value
        self._rebalance_access(p)                # hook for balanced tree subclasses
        return
      else:
        item = self._Item(k,v)
        if p.key() < k:
          leaf = self._add_right(p, item)        # inherited from LinkedBinaryTree
        else:
          leaf = self._add_left(p, item)         # inherited from LinkedBinaryTree
    self._rebalance_insert(leaf)                 # hook for balanced tree subclasses


def delete(self, p):   # 11 
    """Remove the item at given Position."""
    self._validate(p)                            # inherited from LinkedBinaryTree
    if self.left(p) and self.right(p):           # p has two children
      replacement = self._subtree_last_position(self.left(p))
      self._replace(p, replacement.element())    # from LinkedBinaryTree
      p =  replacement
    # now p has at most one child
    parent = self.parent(p)
    self._delete(p)                              # inherited from LinkedBinaryTree
    self._rebalance_delete(parent)               # if root deleted, parent is None


  def __delitem__(self, k):  # 12
    """Remove item associated with key k (raise KeyError if not found)."""
    if not self.is_empty():
      p = self._subtree_search(self.root(), k)
      if k == p.key():
        self.delete(p)                           # rely on positional version
        return                                   # successful deletion complete
      self._rebalance_access(p)                  # hook for balanced tree subclasses
    raise KeyError('Key Error: ' + repr(k))

  def __iter__(self):   # 13
    """Generate an iteration of all keys in the map in order."""
    p = self.first()
    while p is not None:
      yield p.key()
      p = self.after(p)

  #--------------------- public methods for sorted map interface ---------------------
  def __reversed__(self):  # 14
    """Generate an iteration of all keys in the map in reverse order."""
    p = self.last()
    while p is not None:
      yield p.key()
      p = self.before(p)

  def find_min(self):   # 15
    """Return (key,value) pair with minimum key (or None if empty)."""
    if self.is_empty():
      return None
    else:
      p = self.first()
      return (p.key(), p.value())

  def find_max(self):  # 16
    """Return (key,value) pair with maximum key (or None if empty)."""
    if self.is_empty():
      return None
    else:
      p = self.last()
      return (p.key(), p.value())

  def find_le(self, k):  # 17
    """Return (key,value) pair with greatest key less than or equal to k.

    Return None if there does not exist such a key.
    """
    if self.is_empty():
      return None
    else:
      p = self.find_position(k)
      if k < p.key():
        p = self.before(p)
      return (p.key(), p.value()) if p is not None else None

  def find_lt(self, k):   # 18
    """Return (key,value) pair with greatest key strictly less than k.

    Return None if there does not exist such a key.
    """
    if self.is_empty():
      return None
    else:
      p = self.find_position(k)
      if not p.key() < k:
        p = self.before(p)
      return (p.key(), p.value()) if p is not None else None

  def find_ge(self, k):  # 19
    """Return (key,value) pair with least key greater than or equal to k.

    Return None if there does not exist such a key.
    """
    if self.is_empty():
      return None
    else:
      p = self.find_position(k)                   # may not find exact match
      if p.key() < k:                             # p's key is too small
        p = self.after(p)
      return (p.key(), p.value()) if p is not None else None

  def find_gt(self, k):  # 20
    """Return (key,value) pair with least key strictly greater than k.

    Return None if there does not exist such a key.
    """
    if self.is_empty():
      return None
    else:
      p = self.find_position(k)
      if not k < p.key():                   
        p = self.after(p)
      return (p.key(), p.value()) if p is not None else None
  
  def find_range(self, start, stop):  # 21
    """Iterate all (key,value) pairs such that start <= key < stop.

    If start is None, iteration begins with minimum key of map.
    If stop is None, iteration continues through the maximum key of map.
    """
    if not self.is_empty():
      if start is None:
        p = self.first()
      else:
        # we initialize p with logic similar to find_ge
        p = self.find_position(start)
        if p.key() < start:
          p = self.after(p)
      while p is not None and (stop is None or p.key() < stop):
        yield (p.key(), p.value())
        p = self.after(p)

  #--------------------- hooks used by subclasses to balance a tree ---------------------
  def _rebalance_insert(self, p):   # 22
    """Call to indicate that position p is newly added."""
    pass

  def _rebalance_delete(self, p):   # 23
    """Call to indicate that a child of p has been removed."""
    pass

  def _rebalance_access(self, p):  # 24
    """Call to indicate that position p was recently accessed."""
    pass

  #--------------------- nonpublic methods to support tree balancing -----------------
  # 下面是,为平衡搜素树的子类提供非公开的实用程序(_relink,_rotate,_restructure函数) 

  def _relink(self, parent, child, make_left_child):  # 25
    """Relink parent node with child node (we allow child to be None)."""
    if make_left_child:                           # make it a left child
      parent._left = child
    else:                                         # make it a right child
      parent._right = child
    if child is not None:                         # make child point to parent
      child._parent = parent

  def _rotate(self, p):    # 26
    """Rotate Position p above its parent.

    Switches between these configurations, depending on whether p==a or p==b.

          b                  a
         / \                /  \
        a  t2             t0   b
       / \                     / \
      t0  t1                  t1  t2

    Caller should ensure that p is not the root.
    """
    """Rotate Position p above its parent."""
    x = p._node
    y = x._parent                                 # we assume this exists
    z = y._parent                                 # grandparent (possibly None)
    if z is None:            
      self._root = x                              # x becomes root
      x._parent = None        
    else:
      self._relink(z, x, y == z._left)            # x becomes a direct child of z
    # now rotate x and y, including transfer of middle subtree
    if x == y._left:
      self._relink(y, x._right, True)             # x._right becomes left child of y
      self._relink(x, y, False)                   # y becomes right child of x
    else:
      self._relink(y, x._left, False)             # x._left becomes right child of y
      self._relink(x, y, True)                    # y becomes left child of x

  def _restructure(self, x):   # 27
    """Perform a trinode restructure among Position x, its parent, and its grandparent.

    Return the Position that becomes root of the restructured subtree.

    Assumes the nodes are in one of the following configurations:

        z=a                 z=c           z=a               z=c  
       /  \                /  \          /  \              /  \  
      t0  y=b             y=b  t3       t0   y=c          y=a  t3 
         /  \            /  \               /  \         /  \     
        t1  x=c         x=a  t2            x=b  t3      t0   x=b    
           /  \        /  \               /  \              /  \    
          t2  t3      t0  t1             t1  t2            t1  t2   

    The subtree will be restructured so that the node with key b becomes its root.

              b
            /   \
          a       c
         / \     / \
        t0  t1  t2  t3

    Caller should ensure that x has a grandparent.
    """
    """Perform trinode restructure of Position x with parent/grandparent."""
    y = self.parent(x)
    z = self.parent(y)
    if (x == self.right(y)) == (y == self.right(z)):  # matching alignments
      self._rotate(y)                                 # single rotation (of y)
      return y                                        # y is new subtree root
    else:                                             # opposite alignments
      self._rotate(x)                                 # double rotation (of x)     
      self._rotate(x)
      return x                                        # x is new subtree root

11.1.5 二叉搜索树的性能

几乎所有的操作都有一个最坏的运行时间,他取决于树的高度h,这是因为,大多数操作都依赖于沿树的特定路径中每个节点的恒定工作量,且最大路径长度与树的高度成正比。

操作运行时间
k in TO(h)
T[k], T[k]=vO(h)
T.delete(p),del T[k]O(h)
T.find_position(k)O(h)
T.first(),T.last(),T.find_min(),T.find_max()O(h)
T.before(p),T.after(p)O(h)
T.find_lt(k),T.find_le(k),T.find_gt(k),T.find_ge(k)O(h)
T.find_range(start,,stop)O(s+h)
iter(T),reverse(T)O(n)

从上面的性能分析结果来看,只有在树的高度比较小的情况下,二叉搜索树T才是实现n个实体的映射的高效算法。通常来说,通过一系列随机的插入或删除键操作生成的n个键的二叉搜索树的期望复杂度是O(logn)。

11.2 平衡搜索树

平衡指的是:对于树中每一个节点p,其相应的左右两边的子树高度相差不超过1。

在上面结尾处,注意到:如果假设有一系列的插入和删除操作,标准二叉搜索树的基本映射操作的运行时间是O(logn).但是由于某些操作系列可能会生成高度与n成比例的不平衡树,这种树的时间复杂度是O(n)。因此在接下来的四部分中,探讨了4中能提供更强壮能保证的搜索树算法平衡二叉搜素树、AVL树、伸展树、红黑树),平衡二叉搜索树的主要操作是旋转,而后面三种是基于少量操作对标准二叉搜索树进行扩展去重新调整树并降低树的高度。

平衡搜索树的python框架已经在上面的上面的代码中进行了展示:_relink、_rotate、_restructure这三个函数便是相应的框架。

11.3 AVL 树

高度平衡性:对于T中每一个位置p,p的孩子的高度相差最多为1.

任何满足高度平衡属性的二叉搜索树被称为AVL树。高度平衡属性所带来的一个直接结果是AVL树子树本身就是一棵AVL树。

命题:一棵存有n个节点的AVL树的高度是O(logn)。

11.3.1 更新操作(插入和删除)

插入:在树的叶子节点处插入一个节点后,如果仍然满足AVL树的定义,无需管;如果违反了AVL树的定义,则找到相应的不符合的节点,然后将其子树进行判断并移动和旋转重组。

删除:同理,删除一个叶子节点之后,若不再满足高度平衡属性,则进行相应的重组。

AVL树的性能分析:

操作运行时间
k in TO(logn)
T[k], T[k]=vO(logn)
T.delete(p),del T[k]O(logn)
T.find_position(k)O(logn)
T.first(),T.last(),T.find_min(),T.find_max()O(logn)
T.before(p),T.after(p)O(logn)
T.find_lt(k),T.find_le(k),T.find_gt(k),T.find_ge(k)O(logn)
T.find_range(start,,stop)O(s +  logn)
iter(T),reverse(T)O(logn)

11.3.2 python 实现

from .binary_search_tree import TreeMap

class AVLTreeMap(TreeMap):
  """Sorted map implementation using an AVL tree."""

  #-------------------------- nested _Node class --------------------------
  class _Node(TreeMap._Node):
    """Node class for AVL maintains height value for balancing.

    We use convention that a "None" child has height 0, thus a leaf has height 1.
    """
    __slots__ = '_height'         # additional data member to store height

    def __init__(self, element, parent=None, left=None, right=None):
      super().__init__(element, parent, left, right)
      self._height = 0            # will be recomputed during balancing
      
    def left_height(self):    # 计算左子树的高度
      return self._left._height if self._left is not None else 0
 
    def right_height(self):    # 计算右子树的高度
      return self._right._height if self._right is not None else 0

  #------------------------- positional-based utility methods -------------------------
  def _recompute_height(self, p):  # 计算节点高度
    p._node._height = 1 + max(p._node.left_height(), p._node.right_height())

  def _isbalanced(self, p):    # 判断是否仍是满足高度平衡属性
    return abs(p._node.left_height() - p._node.right_height()) <= 1

  def _tall_child(self, p, favorleft=False): # parameter controls tiebreaker
    if p._node.left_height() + (1 if favorleft else 0) > p._node.right_height():
      return self.left(p)
    else:
      return self.right(p)

  def _tall_grandchild(self, p):
    child = self._tall_child(p)
    # if child is on left, favor left grandchild; else favor right grandchild
    alignment = (child == self.left(p))
    return self._tall_child(child, alignment)

  def _rebalance(self, p):
    while p is not None:
      old_height = p._node._height                          # trivially 0 if new node
      if not self._isbalanced(p):                           # imbalance detected!
        # perform trinode restructuring, setting p to resulting root,
        # and recompute new local heights after the restructuring
        p = self._restructure(self._tall_grandchild(p))
        self._recompute_height(self.left(p))                
        self._recompute_height(self.right(p))                           
      self._recompute_height(p)                             # adjust for recent changes
      if p._node._height == old_height:                     # has height changed?
        p = None                                            # no further changes needed
      else:
        p = self.parent(p)                                  # repeat with parent

  #---------------------------- override balancing hooks ----------------------------
  def _rebalance_insert(self, p):
    self._rebalance(p)

  def _rebalance_delete(self, p):
    self._rebalance(p)

11.4 伸展树

这种结构从概念上完全不同于本章中的其他平衡搜索树,因为伸展树在树的高度上没有一个严格的数的对数上界。事实上,伸展树无需依赖额外的高度,平衡或与此树节点关联的其他辅助数据。伸展树的效率取决于某一位置移动到根的操作(称为伸展),每次在插入、删除或者甚至搜索都要从最底层的位置p开始

11.4.1 伸展

我们通过一系列的重组将x移动到T的根来对x进行扩展。进行特定的重组是很重要的,因为将节点x移动到根节点T仅仅通过一些序列的重组是不够的。我们将x向上移动执行特定的操作取决于x、其父节点y和x的祖父节点z的相对位置。先考虑如下三种情况:

* zig-zig型:节点x和父节点y都在树的左边或者都在树的右边:移动节点x,使y节点为x节点的一个孩子,z节点为y节点的一个孩子。(x为祖父节点)

* zig-zag型:节点x和节点y一个是左孩子,一个是右孩子:移动节点x,使其拥有孩子节点y和z.(x为y和z的父节点)

* zig型:x没有祖父节点:将x提升到y之上,使得节点y为节点x的孩子节点。(x为y的父节点)

上面三种最后都使得x节点最后变为伸展树的根节点。

11.4.2 何时进行伸展

** 当搜索键k时,如果在位置p处找到k,则伸展p;否则,在搜索失败的位置伸展叶子节点

**当插入键k时,我们将伸展新插入的内部节点k

** 当删除键k,在位置p进行伸展,其中p是被移除节点的父节点。

11.4.3 Python实现(伸展树的实现)

from .binary_search_tree import TreeMap

class SplayTreeMap(TreeMap):
  """Sorted map implementation using a splay tree."""

  #--------------------------------- splay operation --------------------------------
  def _splay(self, p):
    while p != self.root():
      parent = self.parent(p)
      grand = self.parent(parent)
      if grand is None:
        # zig case
        self._rotate(p)
      elif (parent == self.left(grand)) == (p == self.left(parent)):
        # zig-zig case
        self._rotate(parent)             # move PARENT up
        self._rotate(p)                  # then move p up
      else:
        # zig-zag case
        self._rotate(p)                  # move p up
        self._rotate(p)                  # move p up again

  #---------------------------- override balancing hooks ----------------------------
  def _rebalance_insert(self, p):
    self._splay(p)

  def _rebalance_delete(self, p):
    if p is not None:
      self._splay(p)                     

  def _rebalance_access(self, p):
    self._splay(p)

11.4.4 伸展树的性能分析(没看懂,略)

11.5 (2,4)树

(2,4)树是多路搜索树这种通用数据结构的一个特殊的例子。

多路搜素树:令w为有序树的一个节点。如果w有d个孩子,则称w是d_node.我们将一棵多路搜素树定义为有以下属性的有序树T:

* T的每个内部节点至少有两个孩子。也就是说每个内部节点是一个d_node,其中d>=2.

* T的每个内部节点d_node w,其孩子c_1,c_2,……,c_d按顺序存储d-1个键-值对(k_1,v_1),(k_2,v_2),……,(k_d,v_d)

* 通常定义k_0=-\inftyk_d=+\infty.每个条目(k,v)存储在一个以c_i为根的w的子树一个节点上,其中i=1,2,\cdots,d,k_{i-1}\leq k \leq k_i

命题:一棵有n个节点的多路搜素树有n+1外部节点。

(2,4)树的操作:插入和删除

(2,4)树的性能:

在有序映射ADT方面,(2,4)树的渐近性能和AVL树是一样的,并且对于大多数操作都是具有对数边界的。有n个键值对的(2,4)树的时间复杂度的分析基于以下几点:

* 存储n个节点的(2,4)树的高度是O(logn)

* 分裂、交换或合并操作需要O(1)时间

* 搜素、插入或删除一个节点需要访问O(logn)个节点

11.6 红黑树

虽然AVL树和(2,4)树都具有很多很好的特性,但他们也有一些缺点。例如:AVL树删除后可能需要要执行的多重组操作(旋转);(2,4)树在插入和删除之后可能需要进行许多分裂或融合操作。

在本部分介绍的红黑树没有操作之后的后续补充操作的缺点。

从形式上来讲,红黑树是一棵带有红色和黑色节点的二叉搜索树,具有下面的属性:

* 根属性:根节点是黑色的

* 红色属性:红色节点的子节点是黑色的,也就是说,从每个叶子到根的所有路径上不能出现连续的两个红色节点。

* 深度属性:具有零个或一个子节点的所有节点都具有相同的黑色深度(黑色祖先节点的数量)

命题:有n个条目的红黑树的高度是O(logn).

11.6.1 红黑树的操作:插入和删除

插入之后可能会出现’双红色‘问题,分为两种情况讨论:具体太多了,略,详细见书。

删除之后也需要讨论删除后的三种情况:

红黑树的性能:

根据有序映射ADYT,红黑树的渐进性能与AVL树或(2,4)树的渐近性能相同,对于大多数操作保证了对数时间界限.红黑树的主要优点在于插入或删除只需要常数步的调整操作(这相当于AVL树或(2,4)树,在最坏的情况下,两者的每个映射结构的调整均需要对数倍的时间操作)。也就是说,在红黑树的插入或删除操作中,搜索一次需要对数级时间,并且可能需要对数倍的级联向上重新着色操作。对于单个的映射操作有一个常数数量的旋转或调整操作。

命题:在一棵存储n个项目的红黑树中插入一个项可在O(logn)的时间内完成,并且需要O(logn)的重新着色且至多需要一次的trinode重组

命题:在一棵存储n个项目的红黑树中删除一个项可在O(logn)的时间内完成,并且需要O(logn)的重新着色且最多需要两次调整操作。

11.6.2 python实现

from .binary_search_tree import TreeMap

class RedBlackTreeMap(TreeMap):
  """Sorted map implementation using a red-black tree."""

  #-------------------------- nested _Node class --------------------------
  class _Node(TreeMap._Node):
    """Node class for red-black tree maintains bit that denotes color."""
    __slots__ = '_red'     # add additional data member to the Node class

    def __init__(self, element, parent=None, left=None, right=None):
      super().__init__(element, parent, left, right)
      self._red = True     # new node red by default

  #------------------------- positional-based utility methods -------------------------
  # we consider a nonexistent child to be trivially black
  def _set_red(self, p): p._node._red = True
  def _set_black(self, p): p._node._red = False
  def _set_color(self, p, make_red): p._node._red = make_red
  def _is_red(self, p): return p is not None and p._node._red
  def _is_red_leaf(self, p): return self._is_red(p) and self.is_leaf(p)

  def _get_red_child(self, p):
    """Return a red child of p (or None if no such child)."""
    for child in (self.left(p), self.right(p)):
      if self._is_red(child):
        return child
    return None
  
  #------------------------- support for insertions -------------------------
  def _rebalance_insert(self, p):
    self._resolve_red(p)                         # new node is always red

  def _resolve_red(self, p):
    if self.is_root(p):
      self._set_black(p)                         # make root black
    else:
      parent = self.parent(p)
      if self._is_red(parent):                   # double red problem
        uncle = self.sibling(parent)
        if not self._is_red(uncle):              # Case 1: misshapen 4-node
          middle = self._restructure(p)          # do trinode restructuring
          self._set_black(middle)                # and then fix colors
          self._set_red(self.left(middle))
          self._set_red(self.right(middle))
        else:                                    # Case 2: overfull 5-node
          grand = self.parent(parent)            
          self._set_red(grand)                   # grandparent becomes red
          self._set_black(self.left(grand))      # its children become black
          self._set_black(self.right(grand))
          self._resolve_red(grand)               # recur at red grandparent
      
  #------------------------- support for deletions -------------------------
  def _rebalance_delete(self, p):
    if len(self) == 1:                                     
      self._set_black(self.root())  # special case: ensure that root is black
    elif p is not None:
      n = self.num_children(p)
      if n == 1:                    # deficit exists unless child is a red leaf
        c = next(self.children(p))
        if not self._is_red_leaf(c):
          self._fix_deficit(p, c)
      elif n == 2:                  # removed black node with red child
        if self._is_red_leaf(self.left(p)):
          self._set_black(self.left(p))
        else:
          self._set_black(self.right(p))

  def _fix_deficit(self, z, y):
    """Resolve black deficit at z, where y is the root of z's heavier subtree."""
    if not self._is_red(y): # y is black; will apply Case 1 or 2
      x = self._get_red_child(y)
      if x is not None: # Case 1: y is black and has red child x; do "transfer"
        old_color = self._is_red(z)
        middle = self._restructure(x)
        self._set_color(middle, old_color)   # middle gets old color of z
        self._set_black(self.left(middle))   # children become black
        self._set_black(self.right(middle))
      else: # Case 2: y is black, but no red children; recolor as "fusion"
        self._set_red(y)
        if self._is_red(z):
          self._set_black(z)                 # this resolves the problem
        elif not self.is_root(z):
          self._fix_deficit(self.parent(z), self.sibling(z)) # recur upward
    else: # Case 3: y is red; rotate misaligned 3-node and repeat
      self._rotate(y)
      self._set_black(y)
      self._set_red(z)
      if z == self.right(y):
        self._fix_deficit(z, self.left(z))
      else:
        self._fix_deficit(z, self.right(z))

通过重写嵌套_Node类的定义引入一个附加的布尔值来表示节点的当前颜色。我们的构造函数有意将新节点的颜色设置为红色,以符合插入节点的方法。另外添加了几个附加的实用功能,帮助设置节点的颜色和查询各种条件。

当一个元素已作为一个叶子节点被插入树中,_rebalance_insert的钩子将被调用,使我们有机会修改树的结构。新节点默认情况下是红色的,所以我们只需要寻找新节点的情况,新节点是根(在这种情况下,新节点应该是黑色的),或者有一个双重红色问题,因为新节点的父节点可能是红色的。

删除之后的再平衡也遵循11.6.1节所描述的情况分析。一个额外的挑战是,在_rebalance_delete被调用时,旧节点已经从树中移除。在移除节点的父节点上调用钩子。某些情况下分析取决于知道关于被去除节点的属性。幸运的是,我们可以根据红黑树的信息将进行逆向处理,。特别的是,如果p表示移除节点的父节点,他必须是:
* 如果p没有子节点,移除的节点是红叶子节点

*如果p有一个子节点,已删除节点是一个黑叶,造成空缺,除非剩下的一个子节点是一个红色的叶子

* 如果p有两个子节点,则移除的是一个被升级并且有红色子节点的黑色节点

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值