当前位置:   article > 正文

分步图解平衡二叉树的插入过程(Python实现)_平衡二叉树python

平衡二叉树python

一、基本概念:

平衡二叉树:是一种特殊的二叉排序树,它或者为空树,或者每个结点的左右子树都是平衡二叉树,也就是每个结点的左右子树的高度之差只能是-1,0,1三种情况。

平衡二叉树又称AVL树,是由苏联的Georgy Adelson-Velsky和E.M.Landis发明的,并以他们的名字命名。与之类似的还有红黑树、B树等。

平衡二叉树的平衡状况由平衡因子(Balance Factor,BF)来衡量。平衡因子定义为当前结点的左子树高度减去右子树的高度之差,其可能取值只有-1,0,1。叶结点的BF都是0。如果能维持平衡二叉树的结构,检索操作就能在O(log n)时间内完成。

最小不平衡子树:距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树。(不平衡即失衡,指BF超出合法值)

最小非平衡子树:包含插入结点位置,其根结点的BF是1或-1的最小子树。(非平衡指BF非0,但BF在合法值范围内)

(此处这两个概念如有错误,欢迎指正)

在下图中,原来结点4的BF=1,插入结点3后,结点4的BF=2。结点4是距离插入结点3最近的且BF绝对值大于1的结点,所以以结点4为根的子树就是最小不平衡子树。

二、插入操作的不同情况

1、如果在查找插入位置的过程中,所有途经结点的BF值均为0,那么插入新的结点后,不会导致这些途经结点失衡,只会让它们的BF值从0变为1或者-1。

如下图所示,插入结点3,查找时途经结点为5,4和2,其BF只是从原来的0都变为了1或-1,整棵树仍然是平衡的。

2、如下图,插入结点4.7。左图中最小非平衡子树以结点4为根结点a,结点4.7被插入到这棵子树较低的子树中,此时这棵子树还是平衡的,只需要修改这棵子树的从根结点直至插入结点路径上所有结点的BF值。

以上两种情况都不需要调整树的结构

3、需要调整树结构的情况又分为了四种情况:

(1)LL型调整:a的左子树较高,新结点插入在a的左子树的左子树。进行右旋转。

在下图的图a中,a是最小非平衡子树的根,b的BF一定是0(否则a就不是最小非平衡子树的根了)。结点2被插入到了a的左子树的左子树,需要进行LL型调整:将结点2-3-4-5-8看做一条可以转动的链子,将其向右旋转(顺时针)一个结点,然后将原来b结点的右子树,接到a结点的左子结点上,调整完成。

再说明一下插入结点的位置:插入结点不必像上图一样,必须插在某个结点的左子结点,也可以像下图一样,插在某个结点的右子结点,调整的方法还是一样的。这也是定义中说:新结点插入在a的左子树的‘左子树’,而不是左子树的左子结点的原因。

  1. @staticmethod
  2. def LL(a, b):
  3. a.left = b.right # 将b的右子树接到a的左子结点上
  4. b.right = a #将a树接到b的右子结点上
  5. a.bf = b.bf = 0 #调整a、b的bf值。
  6. return b

 

(2)RR型调整:a的右子树较高,新结点插入在a的右子树的右子树。进行左旋转。

 

RR型调整与LL型正好是对称的,操作步骤类似。在下图的图a中,a是最小非平衡子树的根,b的BF一定是0。结点9被插入到了a的右子树的右子树,需要进行RR型调整:同样地,将结点4-5-6-8-9看做一条可以转动的链子,将其向左旋转(逆时针)一个结点,然后将原来b结点的左子树,接到a结点的右子结点上,调整完成。

同样地,插入结点也可以插入在结点8的左子结点处,调整步骤是一样的。

  1. @staticmethod
  2. def RR(a, b):
  3. a.right = b.left
  4. b.left = a
  5. a.bf = b.bf = 0
  6. return b

(3)LR型调整:a的左子树较高,新结点插入在a的左子树的右子树。先进行左旋转,再进行右旋转。

在下图的图a中,a是最小非平衡子树的根,b的BF一定是0,c的BF也一定是0。结点4.1被插入到了a的左子树的右子树(图b中4.1插入到了c结点的左子树,当然也可以插到c结点的右子树,其调整过程都是一样的),需要进行LR型调整。

图c中,首先将c结点的左右子树分别摘下来,然后将结点4.5-4-3-2看做一条可以转动的链子,对其进行左旋转(逆时针)一个结点,就得到了图d,然后再将结点2-3-4-4.5-5-8-9看做一条转动的链子,将其进行右旋转(顺时针)一个结点,就得到了图e。

最后将原来c结点的左子树接到b结点的右子结点上,将原来c结点的右子树接到a结点的左子结点上,调整完成。

  1. @staticmethod
  2. def LR(a,b):
  3. c = b.right
  4. a.left, b.right = c.right, c.left
  5. c.left, c.right = b, a
  6. if c.bf == 0: #c本身就是插入点
  7. a.bf = b.bf = 0
  8. elif c.bf == 1: #插在c的左子树
  9. a.bf = -1
  10. b.bf = 0
  11. else: #插在c的右子树
  12. a.bf = 0
  13. b.bf = 1
  14. c.bf = 0
  15. return c

 

(4)RL型调整:a的右子树较高,新结点插入在a的右子树的左子树。先进行右旋转,再进行左旋转。

 

RL型调整与LR型正好是对称的,操作步骤类似。在下图的图a中,a是最小非平衡子树的根,b的BF一定是0,c的BF也一定是0。结点5.5被插入到了a的右子树的左子树(图b中5.5插入到了c结点的左子树,当然也可以插到c结点的右子树,其调整过程都是一样的),需要进行RL型调整。

图c中,首先将c结点的左右子树分别摘下来,然后将结点7-9-10-11看做一条可以转动的链子,对其进行右旋转(顺时针)一个结点,就得到了图d,然后再将结点3-4-5-7-9-10-11看做一条转动的链子,将其进行左旋转(逆时针)一个结点,就得到了图e。

最后将原来c结点的左子树接到a结点的右子结点上,将原来c结点的右子树接到b结点的左子结点上,调整完成。

  1. @staticmethod
  2. def RL(a, b):
  3. c = b.left
  4. a.right, b.left = c.left, c.right
  5. c.left, c.right = a, b
  6. if c.bf == 0:
  7. a.bf = b.bf = 0
  8. elif c.bf == 1:
  9. a.bf = 0
  10. b.bf = -1
  11. else:
  12. a.bf = 1
  13. b.bf = 0
  14. c.bf = 0
  15. return c

平衡二叉树的插入操作的复杂度是O(log n)

 

最后:用平衡二叉树来实现一个字典类

首先,AVL树结点类需要增加一个bf域。

  1. class AVLNode(BinTNode):
  2. def __init__(self, data):
  3. BinTNode.___init__(self,data)
  4. self.bf = 0

其次,AVL数是一种二叉排序树,所以可以直接继承二叉排序树的方法。

  1. class DictAVL(DictBinTree):
  2. def __init__(self, data):
  3. DictBinTree.___init__(self)

插入操作的实现:

  1. def insert(self, key, value):
  2. a = p = self.root
  3. if a is None: #如果根结点为空,则直接将值插入到根结点
  4. self.root = AVLNode(Assoc(key, value))
  5. return
  6. a_father, p_father = None #a_father用于最后将调整后的子树接到其子结点上
  7. while p is not None: #通过不断的循环,将p下移,查找插入位置,和最小非平衡子树
  8. if key == p.data.key: #如果key已经存在,则直接修改其关联值
  9. p.data.value = value
  10. return
  11. if p.bf != 0: #如果当前p结点的BF=0,则有可能是最小非平衡子树的根结点
  12. a_father, a, = p_father, p
  13. p_father = p
  14. if key < p.data.key:
  15. p = p.left
  16. else:
  17. p = p.right
  18. #上述循环结束后,p_father已经是插入点的父结点,a_father和a记录着最小非平衡子树
  19. node = AVLNode(Assoc(key, value))
  20. if key < p_father.data.key:
  21. p_father.left = node
  22. else:
  23. p_father.right = node
  24. #新结点已插入,a是最小非平衡子树的根结点
  25. if key < a.data.key: #新结点在a的左子树
  26. p = b = a.left
  27. d = 1 #d记录新结点被 插入到a的哪棵子树
  28. else:
  29. p = b = a.right #新结点在a的右子树
  30. d = -1
  31. #在新结点插入后,修改b到新结点路径上各结点的BF值。调整过程的BF值修改都在子函数中操作
  32. while p != node:
  33. if key < p.data.key:
  34. p.bf = 1
  35. p = p.left
  36. else:
  37. p.bf = -1
  38. p = p.right
  39. if a.bf == 0: #如果a的BF原来为0,那么插入新结点后不会失衡
  40. a.bf = d
  41. return
  42. if a.bf == -d: #如果新结点插入在a较低的子树里
  43. a.bf = 0
  44. return
  45. #以上两条if语句都不符合的话,说明新结点被插入在较高的子树里,需要进行调整
  46. if d == 1: #如果新结点插入在a的左子树
  47. if b.bf == 1: #b的BF原来为0,如果等于1,说明新结点插入在b的左子树
  48. b = DictAVL.LL(a, b)
  49. else: #新结点插入在b的右子树
  50. b = DictAVL.LR(a, b)
  51. else: #新结点插入在a的右子树
  52. if b.bf == -1: #新结点插入在b的右子树
  53. b = DictAVL.RR(a, b)
  54. else: ##新结点插入在b的左子树
  55. b = DictAVL.RL(a, b)
  56. #将调整后的最小非平衡子树接到原树中,也就是接到原来a结点的父结点上
  57. if a_father is None: #判断a是否是根结点
  58. self.root = b
  59. else:
  60. if a_father == a:
  61. a_father.left = b
  62. else:
  63. a_father.right = b

 

完整的代码如下:

  1. class StackUnderflow(ValueError):
  2. pass
  3. class SStack():
  4. def __init__(self):
  5. self.elems = []
  6. def is_empty(self):
  7. return self.elems == []
  8. def top(self): #取得栈里最后压入的元素,但不删除
  9. if self.elems == []:
  10. raise StackUnderflow('in SStack.top()')
  11. return self.elems[-1]
  12. def push(self, elem):
  13. self.elems.append(elem)
  14. def pop(self):
  15. if self.elems == []:
  16. raise StackUnderflow('in SStack.pop()')
  17. return self.elems.pop()
  18. class Assoc: #定义一个关联类
  19. def __init__(self, key, value):
  20. self.key = key #键(关键码)
  21. self.value = value #值
  22. def __lt__(self, other):#Python解释器中遇到比较运算符<,会去找类里定义的__lt__方法(less than)
  23. return self.key < other.key
  24. def __le__(self, other): #(less than or equal to)
  25. return self.key < other.key or self.key == other.key
  26. def __str__(self):
  27. return 'Assoc({0},{1})'.format(self.key, self.value) #key和value分别替换前面{0},{1}的位置。
  28. class BinTNode:
  29. def __init__(self, dat, left = None, right = None):
  30. self.data = dat
  31. self.left = left
  32. self.right = right
  33. class DictBinTree:
  34. def __init__(self, root = None):
  35. self.root = root
  36. def is_empty(self):
  37. return self.root is None
  38. def search(self, key):#检索是否存在关键码key
  39. bt = self.root
  40. while bt is not None:
  41. entry = bt.data
  42. if key < entry.key:
  43. bt = bt.left
  44. elif key > entry.key:
  45. bt = bt.right
  46. else:
  47. return entry.value
  48. return None
  49. def insert(self, key, value):
  50. bt = self.root
  51. if bt is None:
  52. self.root = BinTNode(Assoc(key, value))
  53. return
  54. while True:
  55. entry = bt.data
  56. if key < entry.key: #如果小于当前关键码,转向左子树
  57. if bt.left is None: #如果左子树为空,就直接将数据插在这里
  58. bt.left = BinTNode(Assoc(key,value))
  59. return
  60. bt = bt.left
  61. elif key > entry.key:
  62. if bt.right is None:
  63. bt.right = BinTNode(Assoc(key,value))
  64. return
  65. bt = bt.right
  66. else:
  67. bt.data.value = value
  68. return
  69. def print_all_values(self):
  70. bt, s = self.root, SStack()
  71. while bt is not None or not s.is_empty(): #最开始时栈为空,但bt不为空;bt = bt.right可能为空,栈不为空;当两者都为空时,说明已经全部遍历完成了
  72. while bt is not None:
  73. s.push(bt)
  74. bt = bt.left
  75. bt = s.pop() #将栈顶元素弹出
  76. yield bt.data.key, bt.data.value
  77. bt = bt.right #将当前结点的右子结点赋给bt,让其在while中继续压入栈内
  78. def entries(self):
  79. bt, s = self.root, SStack()
  80. while bt is not None or not s.is_empty():
  81. while bt is not None:
  82. s.push(bt)
  83. bt = bt.left
  84. bt = s.pop()
  85. yield bt.data.key, bt.data.value
  86. bt = bt.right
  87. def print_key_value(self):
  88. for k, v in self.entries():
  89. print(k, v)
  90. def delete(self, key):
  91. #以下这一段用于找到待删除结点及其父结点的位置。
  92. del_position_father, del_position = None, self.root #del_position_father是待删除结点del_position的父结点
  93. while del_position is not None and del_position.data.key != key: #通过不断的比较,找到待删除结点的位置
  94. del_position_father = del_position
  95. if key < del_position.data.key:
  96. del_position = del_position.left
  97. else:
  98. del_position = del_position.right
  99. if del_position is None:
  100. print('There is no key')
  101. return
  102. if del_position.left is None: #如果待删除结点只有右子树
  103. if del_position_father is None: #如果待删除结点的父结点是空,则说明待删除结点是根结点
  104. self.root = del_position.right #则直接将根结点置空
  105. elif del_position is del_position_father.left: #如果待删除结点是其父结点的左结点
  106. del_position_father.left = del_position.right #***改变待删除结点父结点的左子树的指向
  107. else:
  108. del_position_father.right = del_position.right
  109. return
  110. #如果既有左子树又有右子树,或者仅有左子树时,都可以用直接前驱替换的删除结点的方式,只不过得到的二叉树与原理中说明的不一样,但是都满足要求。
  111. pre_node_father, pre_node = del_position, del_position.left
  112. while pre_node.right is not None: #找到待删除结点的左子树的最右结点,即为待删除结点的直接前驱
  113. pre_node_father = pre_node
  114. pre_node = pre_node.right
  115. del_position.data = pre_node.data #将前驱结点的data赋给删除结点即可,不需要改变其原来的连接方式
  116. if pre_node_father.left is pre_node:
  117. pre_node_father.left = pre_node.left
  118. if pre_node_father.right is pre_node:
  119. pre_node_father.right = pre_node.left
  120. def build_dictBinTree(entries):
  121. dic = DictBinTree()
  122. for k,v in entries:
  123. dic.insert(k,v)
  124. return dic
  125. class AVLNode(BinTNode):
  126. def __init__(self, data):
  127. BinTNode.___init__(self,data)
  128. self.bf = 0
  129. class DictAVL(DictBinTree):
  130. def __init__(self, data):
  131. DictBinTree.___init__(self)
  132. @staticmethod
  133. def LL(a, b):
  134. a.left = b.right # 将b的右子树接到a的左子结点上
  135. b.right = a #将a树接到b的右子结点上
  136. a.bf = b.bf = 0 #调整a、b的bf值。
  137. return b
  138. @staticmethod
  139. def RR(a, b):
  140. a.right = b.left
  141. b.left = a
  142. a.bf = b.bf = 0
  143. return b
  144. @staticmethod
  145. def LR(a,b):
  146. c = b.right
  147. a.left, b.right = c.right, c.left
  148. c.left, c.right = b, a
  149. if c.bf == 0: #c本身就是插入点
  150. a.bf = b.bf = 0
  151. elif c.bf == 1: #插在c的左子树
  152. a.bf = -1
  153. b.bf = 0
  154. else: #插在c的右子树
  155. a.bf = 0
  156. b.bf = 1
  157. c.bf = 0
  158. return c
  159. @staticmethod
  160. def RL(a, b):
  161. c = b.left
  162. a.right, b.left = c.left, c.right
  163. c.left, c.right = a, b
  164. if c.bf == 0:
  165. a.bf = b.bf = 0
  166. elif c.bf == 1:
  167. a.bf = 0
  168. b.bf = -1
  169. else:
  170. a.bf = 1
  171. b.bf = 0
  172. c.bf = 0
  173. return c
  174. def insert(self, key, value):
  175. a = p = self.root
  176. if a is None: #如果根结点为空,则直接将值插入到根结点
  177. self.root = AVLNode(Assoc(key, value))
  178. return
  179. a_father, p_father = None #a_father用于最后将调整后的子树接到其子结点上
  180. while p is not None: #通过不断的循环,将p下移,查找插入位置,和最小非平衡子树
  181. if key == p.data.key: #如果key已经存在,则直接修改其关联值
  182. p.data.value = value
  183. return
  184. if p.bf != 0: #如果当前p结点的BF=0,则有可能是最小非平衡子树的根结点
  185. a_father, a, = p_father, p
  186. p_father = p
  187. if key < p.data.key:
  188. p = p.left
  189. else:
  190. p = p.right
  191. #上述循环结束后,p_father已经是插入点的父结点,a_father和a记录着最小非平衡子树
  192. node = AVLNode(Assoc(key, value))
  193. if key < p_father.data.key:
  194. p_father.left = node
  195. else:
  196. p_father.right = node
  197. #新结点已插入,a是最小非平衡子树的根结点
  198. if key < a.data.key: #新结点在a的左子树
  199. p = b = a.left
  200. d = 1 #d记录新结点被 插入到a的哪棵子树
  201. else:
  202. p = b = a.right #新结点在a的右子树
  203. d = -1
  204. #在新结点插入后,修改b到新结点路径上各结点的BF值。调整过程的BF值修改都在子函数中操作
  205. while p != node:
  206. if key < p.data.key:
  207. p.bf = 1
  208. p = p.left
  209. else:
  210. p.bf = -1
  211. p = p.right
  212. if a.bf == 0: #如果a的BF原来为0,那么插入新结点后不会失衡
  213. a.bf = d
  214. return
  215. if a.bf == -d: #如果新结点插入在a较低的子树里
  216. a.bf = 0
  217. return
  218. #以上两条if语句都不符合的话,说明新结点被插入在较高的子树里,需要进行调整
  219. if d == 1: #如果新结点插入在a的左子树
  220. if b.bf == 1: #b的BF原来为0,如果等于1,说明新结点插入在b的左子树
  221. b = DictAVL.LL(a, b)
  222. else: #新结点插入在b的右子树
  223. b = DictAVL.LR(a, b)
  224. else: #新结点插入在a的右子树
  225. if b.bf == -1: #新结点插入在b的右子树
  226. b = DictAVL.RR(a, b)
  227. else: ##新结点插入在b的左子树
  228. b = DictAVL.RL(a, b)
  229. #将调整后的最小非平衡子树接到原树中,也就是接到原来a结点的父结点上
  230. if a_father is None: #判断a是否是根结点
  231. self.root = b
  232. else:
  233. if a_father == a:
  234. a_father.left = b
  235. else:
  236. a_father.right = b
  237. if __name__=="__main__":
  238. #LL调整
  239. entries = [(5,'a'),(2.5,'g'),(2.3,'h'),(3,'b'),(2,'d'),(4,'e'),(3.5,'f')]
  240. dic = build_dictBinTree(entries)
  241. dic.print_key_value()
  242. print('after inserting')
  243. dic.insert(1, 'i')
  244. dic.print_key_value()
  245. #LR调整
  246. entries = [(2.5,'g'),(3,'b'),(4,'e'),(3.5,'f')]
  247. dic = build_dictBinTree(entries)
  248. dic.print_key_value()
  249. print('after inserting')
  250. dic.insert(3.2, 'i') #LL
  251. dic.print_key_value()

 

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/306097
推荐阅读
相关标签
  

闽ICP备14008679号