第十一章
本章叙述了不同平衡树的构造性能问题
习题代码如下(部分代码引用书中源代码,源代码位置目录在第二章答案中介绍)
#11.1
#(1,A)
# \
# (2,B)
# \
# (3,C)
# \
# (4,D)
# \
# (5,E)
#11.2 详细图略
# 30
# / \
# 24 40
# / \ \
# 11 26 58
# \ /
# 13 48
#11.3 四种
# / / \ \
# / \ / \
#11.4 (1,2,3,4) 和 (3,2,1,4)
#11.5 (3,4,2,1) 和 (1,2,3,4)
#11.6
def text6(self,p,k):
while p.key()!=k:
if p.key()>k: # search the left subtree
if self.left(p) is None:
break
p=self.left(p)
elif p.key()<k: # search the right subtree
if self.right(p) is None:
break
p=self.right(p)
return p
#11.7 图11-12是自旋转两次 图11-14是自旋转一次
#11.8
# 62
# / \
# 50 78
# / \ \
# 44 54 88
# / \ /
# 17 48 52
#11.9
# 54
# / \
# 44 78
# / \ \
# 17 50 88
# / \
# 48 54
#11.10 在一颗用数组方式存储的二叉树中,这棵树的任意一个节点的位置通过父节点在数组中的索引计算得到的
# 如果一个父节点需要交换位置,那么特的孩子节点也需要交换到其他的位置
#11.11 用(T,D)表示每个节点、T:树的名称 D:树的深度
#删除前:
# (z,h+2)
# / \
# (t1,h) (y,h+1)
# / \
# (t2,h) (x,h)
# / \
# (t3,h-1) (t4,h-1)
#删除后:z出现不平衡
# (z,h+2)
# / \
# (t1,h-1) (y,h+1)
# / \
# (t2,h) (x,h)
# / \
# (t3,h-1) (t4,h-1)
#旋转:
# (y,h+2)
# / \
# (z,h+1) (x,h)
# / \ / \
# (t1,h-1) (t2,h) (t3,h-1) (t4,h-1)
#11.12
#删除前:
# (z,h+2)
# / \
# (t1,h) (y,h+1)
# / \
# (x,h) (t4,h-1)
# / \
# (t2,h-1) (t3,h-1)
#删除后:z出现不平衡
# (z,h+2)
# / \
# (t1,h-1) (y,h+1)
# / \
# (x,h) (t4,h-1)
# / \
# (t2,h-1) (t3,h-1)
#第一次旋转:
# (z,h+2)
# / \
# (t1,h-1) (x,h+1)
# / \
# (t2,h-1) (y,h)
# / \
# (t3,h-1) (t4,h-1)
#第二次旋转:
# (x,h+1)
# / \
# (z,h) (y,h)
# / \ / \
# (t1,h-1) (t2,h-1) (t3,h-1) (t4,h-1)
#11.13 在trindoe时,直接出现不平衡
#删除前:
# (z,h+2)
# / \
# (t1,h) (y,h+1)
# / \
# (x,h) (t4,h)
# / \
# (t2,h-1) (t3,h-1)
#删除后:
# (z,h+2)
# / \
# (t1,h-1) (y,h+1)
# / \
# (x,h) (t4,h)
# / \
# (t2,h-1) (t3,h-1)
#第一次旋转:在x处出现不平衡
# (z,h+2)
# / \
# (t1,h-1) (x,h+2)
# / \
# (t2,h-1) (y,h+1)
# / \
# (t3,h-1) (t4,h)
#11.14 只给出最终图形
#a:
# 18
# /
# 16
# /
# ···
# /
# 0
#b:
# 18
# /
# 16
# /
# ···
# /
# 0
# \
# 2
#c:略
#11.15 不知如何描述问题的答案,上了chegg网查询
#查询出的答案是关于splay树的存储,会让最大的元组在树的根部,其次每个元素都在父节点的左部(但是access有存取也有访问的意思)
#如果是顺序访问的话根据上一题画的图,大部分节点也会在其父节点的左孩子节点上
#11.16 不是(2,4)树需要满足两个条件:A 每个节点最多只能有四个自节点,最少两个自节点;B 每个外部节点的深度一样
#11.17 将k2存入w的父节点中
# (k1,k2,k3,k4) 需要将其分为两部分,第一部分一个节点,第二部分两个节点;
# 因为需要一个比第一部分大,又比第二部分小的值.所以只有k2,k3 能够满足大小关系
# 因为第一个部分一个节点,第二部分两个节点.所以需要将k2放入w中.
#11.18
#{1,2,3,4,5}
# 2
# / \
# 1 (3,4,5)
#{5,4,3,2,1}
# 4
# / \
# (1,2,3) 5
#11.19 当存在两个或者两个以上个3-node节点,就可以画出多个不同的红黑树对应相同的(2,4)树
#11.20
# 1):每个节点都是4-node
# (4,8,12 )
# / | | \
# (1,2,3)(5,6,7)(9,10,11)(13,14,15)
# 2):每个节点都是2-node 构成完全二叉树 略
#11.21
# 1): ( 5 , 16 , 22 )
# / | | \
# (1,2) (10,12) (18) (30,45,50)
# 2): (N,C) N: 键值 C: 颜色
# (16,b)
# / \
# (5,r) (22,r)
# / \ / \
# (2,b) (10,b) (18,b) (45,b)
# / \ / \
# (1,r) (12,r) (30,r) (50,r)
#11.22
# a):F,红黑树的任意以黑色节点为根节点的子树为红黑树
# b):T,当一个节点没有兄弟节点时,他的父节点只有一个节点,需要满足(只有一个子节点、没有子节点)的黑色深度相同
# 也就是说这个节点要和父节点有相同的黑色深度,所以这个节点必须为红色节点.
# c):T:多棵红黑树对应一颗(2,4)树,取决于红黑树中3-node节点个个数,(2,4)树中的一个3-node节点对应两种红黑的形式.
# d):F:c中已解释
#11.23
# 三种树结构,是对旋转的不同使用结果;在旋转结束后,每个节点映射到水平线上位置不发生改变;
# 中序遍历是从左向右进行遍历,所以不会改变中序遍历结果
#11.24
# 1): 100000 #按大小顺序插入
# 2): log(100000)
# 3): 100000 #按大小顺序插入
# 4): log(100000)
# 5): log(100000)
#11.25 (N,C,H) N:为键值 C:为颜色(b,r) H:为深度
# (4,b,4)
# / \
# (3,b,1) (7,r,3)
# / \
# (5,b,2) (8,b,1)
# \
# (6,r,1)
#11.26 在红黑树中,一个节点A如果只有一个叶子节点,那么这个叶子节点和节点A是要有相同的黑色高度的
# 所以叶子节点应该为红色,A应该为黑色
#11.27 在删除之前,p有两个叶子节点a,b;为了保证两个叶子节点黑色深度,当a是红色时,b一定时红色,当b时黑色时,a一定时黑色;
# 在删除之后,p有一个叶子节点,所以如果被保留的叶子节点是黑色时,就会因为需要节点p和它被保留的黑色子节点深度相同,破坏黑色深度
#11.28 在删除之前,p有两个叶子节点a,b,并且b有一个左子节点c,
# 此时保证了a,b,c三节点的黑色深度相同,就需要c为红色,所以a,b为黑色
# 删除b后,p还是拥有两个子节点
#11.29 红黑树和AVL树的高度都保持在log(n),所以直接将序列逐个插入就可以实现
# O(h*2^(h-1)的求和,1<=h<=log(n) )=O(n*(logn-1/2)-1)
#11.30 可以 伸展树的摊销运行时间为O(logn) 即使在最坏的情况下,搜索一个元素的时间需要O(n),但是摊销后还是log(n)
#11.31 将__setitem__方法修改即可
from TheCode.ch11.binary_search_tree import TreeMap
class BST1(TreeMap):
def setdefaut(self,k,v):
''' return k's value if k exist in the tree,else add (k,d)'''
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:
return p.element()._value
else:
item = self._Item(k, v)
if p.key() < k:
# inherited from LinkedBinaryTree
leaf = self._add_right(p, item)
else:
# inherited from LinkedBinaryTree
leaf = self._add_left(p, item)
# hook for balanced tree subclasses
try: # add try grammar for text the ADT
self._rebalance_insert(leaf)
except e:
pass
#t=BST1()
#for i in range(5):
# t.setdefaut(i,i)
#print(t.setdefaut(i,i+10))
#11.32
#树的旋转:
# | |
# y x
# / \ <==> / \
# x t3 t1 y
# / \ / \
# t1 t2 t2 t3
#在树中,通过每两个节点之间的位置关系,使二叉树有不同的形状,但都可以通过旋转的方式改变树的整体结构
#11.33
#当我们寻找一个节点的最大的lt时 A:如果这个节点有孩子节点,我们找到左孩子的最右节点,
# 如果没有孩子节点,向上寻找父节点,知道找到一个小于自己的父节点
#当我们寻找一个节点的最小的gt时 B:如果这个节点有孩子节点,我们找到右孩子的最左节点,
# 如果没有孩子节点,向上寻找父节点,知道找到一个大于自己的父节点
#当我们没有找到k时,可以将k看成没有孩子节点的情况,所以他的A、和B都在他的父节点上
#11.34
# after()时间复杂度保证在O(h)内,在最坏的情况下从树的根节点遍历到树的叶子节点;
# 但在find_range()方法中最坏的情况只发生一次,其余时间复杂度度为O(1)
#11.35
# 提要:在删除根节点或者只有一个孩子的节点时,可以直接删除,不需要寻找其他节点替换
# 将需要删除的节点分为两部分,start(第一个节点)和another(除第一个节点以外其余节点)
# A:在another中的第一个节点一定为整棵树的某个叶子节点或只有一个孩子的节点(大于start的最小节点)
# B:another中的第二个节点为A中找到节点的父节点(如果A中找到的是叶子节点)或者A中找到节点的右节点的最左节点(如果A只有一个孩子的节点,回到A)
# C:another中的第三个节点为A中找到节点的父节点的右节点(如果A中找到的是叶子节点),等等
# 在理想情况下:A和C为B的左右节点,先删除A和C然后在删除B,向上递归删除,
# 非理想情况下:也是从叶子结点开始向上删除(还是按照从下向上删除节点)
# 最后可能会存最上面的节点有左右孩子,所以需要使用小的最大的节点来替换这个节点来删除
# 助理root节点
#11.36
# 在AVL树中,每删除一个节点都需要向上查找是否出现不平衡,所以时间复杂度为O(slogN)
#11.37
#在每个节点加入以该节点为根的所有自节点个数
#还要修改对应的增加和删除,每次操作后都需要更新祖父节点中记录的值
#以下是查找算法
#Algorithm(star,stop):
# find first node: start<=node's<=stop
# s=sum of node's child num
# big=node'right {right node mark the max num}
# small=node'left {left node mark the min mum}
# repeat: {cut down the branch which beyond the range}
# if big!=None:
# if big<=stop:
# big=big.right
# else:
# sum-=sum of big's right child num and 1
# big=big.right
# if small!=None:
# if small>=start:
# small=small.left
# else:
# sum-=sum of small's left child num and 1
# small=small.right
# untill: big and small are None
# return sum
#11.38 需要修改_Node中存储子节点的个数
# 在_relink方法中修改
#11.39 分为三部分,前那两部分构成严格意义的log(n)的时间
# part1:寻找节点k {k深度的时间}
# part2:寻找比k节点小的最大节点 {从k的深度开始向下,直到树的根部}
# part3:被删除的节点可能会造成树高度不平衡,需要trinode {可能为1可能为log(n)}
#ps:trinode为rotation之后树的高度有所减小,树变得更广泛;rotation是一个只改变位置不改变高的的操作
#11.40
# 1.平衡因子通过计算获得
# 2.根据父节点平衡因子的正负决定左旋还是右旋
# 3.在rotate方法中,重新计算旋转改变的平衡因子,在_rebalance中计算改变增加节点改变的平衡因子
from TheCode.ch11.avl_tree import AVLTreeMap
class FactorAVL(AVLTreeMap):
'''
alter variable:
_height
alter method:
left_height
right_height
_recompute_height
_isbalanced
_tall_child
_rebalance
_rotate
'''
class _Node(AVLTreeMap._Node):
__slots__ = '_factor' # additional data member to store height
def __init__(self, element, parent=None, left=None, right=None):
super().__init__(element, parent, left, right)
self._factor = 0 # will be recomputed during balancing
def left_factor(self):
return self._left._factor if self._left is not None else 0
def right_factor(self):
return self._right._factor if self._right is not None else 0
#------------------------- positional-based utility methods --------------
def _recompute_factor(self, p,orient):
''' have aid of the orient parameter'''
if orient==None:
return
p._node._factor =p._node._factor+1 if orient=='l' else p._node._factor-1
def _isbalanced(self, p):
return p._node._factor!=2 or p._node._factor!=-2 # return False when factor== 2 or -2
def _tall_child(self, p, favorleft=False): # parameter controls tiebreaker
if p._node.left_factor() + (1 if favorleft else 0) > p._node.right_factor():
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):
orient=None # definition the orient of child,'l' if leftchild else 'r'
while p is not None:
old_height = p._node._height # trivially 0 if new node
self._recompute_factor(p,orient)
# imbalance detected!
if not self._isbalanced(p):
# perform trinode restructuring, setting p to resulting root,
# and recompute new local heights after the restructuring
p = self._restructure(self._tall_grandchild(p))
# adjust for recent changes
if p._node._height == old_height and orient==None: # has height changed?
p = None # no further changes needed
else:
# cheak the orients
if p is self.parent(p).left():
orient='l'
else:
orient='r'
# repeat with parent
p = self.parent(p)
def _rotate(self, p):
"""Rotate Position p above its parent.
Switches between these configurations, depending on whether p==a or p==b.
y y
/ \ / \
x t2 t0 x
/ \ / \
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
# grandparent (possibly None)
z = y._parent
if z is None:
self._root = x # x becomes root
x._parent = None
else:
# x becomes a direct child of z
self._relink(z, x, y == z._left)
# now rotate x and y, including transfer of middle subtree
if x == y._left:
# x._right becomes left child of y
self._relink(y, x._right, True)
# y becomes right child of x
self._relink(x, y, False)
#calculate the factor
y._factor=y._factor+min(-x._factor,0)+1
x._factor=x._factor+min(0,y._factor)-1
else:
# x._left becomes right child of y
self._relink(y, x._left, False)
# y becomes left child of x
self._relink(x, y, True)
#calculate the factor
y._factor=y._factor+max(-y._factor,0)+1
x._factor=x._factor+max(0,y._factor)+1
#text11.40
#t=FactorAVL()
#for i in range(20):
# t[i]=i
#for i in range(20):
# print(t[i])
#11.41 围绕树的增、删、改、查,对函数进行更改
#增:通过对比新增的节点大小关系,来更新最小值的指针
#删:通过判断删除的节点是否为指针指向节点,来更新指针,若为最小值,则进行中序遍历
#改:修改只会更改键值对应的内容,所以此部分不需要修改
#查:将与获取树最小节点有关的方法使用find_min代替
#11.42 AVL树不需要进行更改
from TheCode.ch11.avl_tree import AVLTreeMap
class LeftAVL(AVLTreeMap):
'''
add a filed to store the minium node,it's a position class;
control the minimum node when add node or delete node
'''
#-----------------------------add a filed----------------------------------
def __init__(self):
super().__init__()
self._mininum=None # a Position class
#-----------------------------control the mininum----------------------------------
def _add_root(self,k,v):
temp=super()._add_root(k,v)
if k==None or k<self._mininum.element()._key:
self._mininum=temp
return temp
def _add_left(self,k,v):
temp=super()._add_left(k,v)
if k==None or k<self._mininum.element()._key:
self._mininum=temp
return temp
def _add_right(self,k,v):
temp=super()._add_right(k,v)
if k==None or k<self._mininum.element()._key:
self._mininum=temp
return temp
'''def _rebalance_insert(self,p):
if self._mininum==None or p.element()._key<self._mininum.element()._key:
self._mininum=p
super()._rebalance_insert(p)'''
def __delitem__(self,k):
''' remove the node which values equal k'''
if k==self._mininum.element()._key:
self._muninum=self.after(self._mininum)
super().__delitem__(k)
def _subtree_first_position(self, p):
"""Return Position of first item in subtree rooted at p."""
return self._mininum
#text11.42
#t=LeftAVL()
#for i in range(20,10,-1):
# t[i]=i
# #print(t._mininum)
# print(t.first().element()._key)
#for i in range(11,21):
# del t[i]
# print('del',i)
# print(t.first().element()._key)
#11.43 直接在AVLTreeMap树上使用
from TheCode.ch11.avl_tree import AVLTreeMap
class AFAVL(AVLTreeMap):
'''
add a field to make after and before method worst-cost O(1)-time;
'''
#-----------------------------add a filed----------------------------------
class _Node(AVLTreeMap._Node):
__slots__ = '_before','_after' # additional data member to store height
def __init__(self, element, parent=None, left=None, right=None,before=None,after=None):
super().__init__(element, parent, left, right)
self._before=before
self._after=after
#-----------------------------alter add method----------------------------------
def _add_left(self,p,e):
''' inherit from linked_binary_tree'''
temp=super()._add_left(p,e)
node = self._validate(temp)
p=node._parent # get the parent node
node._after=p # tie the node
node._before=p._before
if p._before !=None: # if p is the mininum value node
node._before._after=node
node._after._before=node
return temp
def _add_right(self,p,e):
''' inherit from linked_binary_tree'''
temp=super()._add_right(p,e)
node = self._validate(temp)
p=node._parent # get the parent node
node._before=p # tie the node
node._after=p._after
if p._after != None: # if p is the maxnum value node
node._after._before=node
node._before._after=node
return temp
#-----------------------------alter delete method----------------------------------
def _delete(self,p):
''' inherit from linked_binary_tree'''
temp=super()._delete(p)
temp._after._before=temp._before # tie the node
temp._before._after=temp._after
def after(self,p):
node=self._validate(p) # inherited from LinkedBinaryTree
return self._make_position(node._after)
def before(self,p):
node=self._validate(p)
return self._make_position(node._before)
#text11.43
#t=AFAVL()
#for i in range(20):
# t[i]=i
# print(i)
#temp=t.first()
#for i in range(19):
# temp=t.after(temp)
# print(temp)
#print('#####')
#for i in range(20):
# temp=t.before(temp)
# print(temp)
#11.44 如上提代码,无需修改
#11.45 delete(p)方法需要找到比p小的最大节点,在找节点的过程中消耗了O(h),在修改过后,可以在O(1)的时间内找到该节点
#11.46 新增两个field存储当前节点左右自节点个数,通过计算节点数来得到索引
from TheCode.ch11.binary_search_tree import TreeMap
class SupplementTM(TreeMap):
'''
add two field
'''
#-----------------------------alter two field----------------------------------
class _Node(TreeMap._Node):
''' inherit from linked_binary_tree '''
__slots__='_left_num','_right_num'
def __init__(self,element, parent=None, left=None, right=None,_left_num=0,_right_num=0):
super().__init__(element, parent, left, right)
self._right_num=_right_num # add field for sum of right child num
self._left_num=_left_num # add field for sum of left child num
class Position(TreeMap.Position):
''' inherit from linked_binary_tree
add two methods for coding conveniently
'''
def left_num(self):
return self._node._left_num # return
def right_num(self):
return self._node._right_num
def _change_num(self,temp,num):
''' temp is the beginning node which is a Position class,
num is the int where need to change;
node's num on the route which from node p to root add num
'''
p=self.parent(temp)
while p is not None: # cycle to add the child num
if temp is self.left(p): # if p is left child ,parent._left_num+=1
p._node._left_num+=num
else: # else p is right child, parent._right_num+=1
p._node._right_num+=num
temp=p
p=self.parent(p)
#-----------------------------alter add method----------------------------------
def _add_left(self,p,e):
temp=super()._add_left(p,e)
self._change_num(temp,1) # node's num on the route which from node p to root add 1
def _add_right(self,p,e):
temp=super()._add_right(p,e)
self._change_num(temp,1) # node's num on the route which from node p to root add 1
#-----------------------------alter delete method----------------------------------
def _delete(self,p):
self._change_num(p,-1) # node's num on the route which from node p to root add -1
super()._delete(p)
#-----------------------------add method----------------------------------
def at_index(self,i,p=None):
''' recursion compare the child's num with i to get the Position
index range begin at 0
'''
if i >=self._size or i<0:
raise KeyError('invalid key')
if p==None:
p=self._root # recur from the root node
if i<p._left_num: # if index i in p's left subtree
return self.at_index(i,p._left)
elif i == p._left_num:
return self._make_position(p) # if index i is p
else:
return self.at_index(i-p._left_num-1,p._right) # if index i in p's right subtree
def index_of(self,p):
''' cycle compare the child parent with itself's position to get the index
p is a Position class
'''
num=p.left_num()
if p is self.root(): # if p is root
return num
temp=self.parent(p) # record the parent node
while temp !=None