引言
在前一篇文章《平衡二叉树插入及删除操作的图形化展示,python调用graphviz自动生成svg图形》用图形化的方式展示了平衡二叉树在插入或删除时图形的变化。
平衡二叉树在插入或删除节点时可能会打破原有的平衡,所以在插入或删除之后需要进行调整操作。这类操作可以归纳为4种情形
1.子树左边比右边高 2 层 ,并且左子树不低于右子树,只需将子树向右旋转即可
2.子树左边比右边高 2 层 ,并且左子树低于右子树,需先将左子树向左旋转,然后将子树向右旋转
3.子树左边比右边矮 2 层 ,并且右子树不低于左子树,只需将子树向左旋转即可
4.子树左边比右边矮 2 层 ,并且右子树低于左子树,需先将右子树向右旋转,然后将子树向左旋转
说实话,刚接触平衡二叉树的人,看到上面一会儿左,一会儿右的表述,不懵圈的可能很少吧。但如果对照实例,直观地查看向左旋转或向右旋转的实际效果,就要好理解得多了。
因此本文在前一篇文章的基础上对python代码做了进一步地改进,希望以图文结合的方式展示平衡二叉树各种旋转操作的直观效果。对细节感兴趣的朋友可以将完整代码复制下来后自已动手,尝试插入不同的节点序列,然后进行观察。结合源代码与图形效果,相信一定可以对平衡二叉树的旋转操作有一个透彻的理解。
实现环境的建立还请参看前一篇文章
图形化效果
下面是插入一个倒序的节点序列[12,11, 10,9,8,7,6,5,4,3, 2, 1]的图示,由于总是在左子树上进行插入,所以只会有一种向右旋转进行调整的情形。
为了查看一下需要使用两种旋转组合进行调整的效果,下面换了一个节点序列:[10, 3, 4, 5, 2, 1, 8, 6, 99, 7, 18]
实际效果如下:
更多的图形效果,可以将代码下载到本地后自已手动调整源码,然后在浏览器上查看图形效果。当然前提是你的电脑上已经安装好了python与graphviz。
完整的python代码
# -*- coding:utf-8 -*-
# 平衡二叉树的图形化展示代码,
# 原作者:zhipingChen https://www.jianshu.com/p/655d83f9ba7b
# 修改成用graphviz图形化显示,修改者:littleZhuHui
import os
# tree node definition
class Node(object):
def __init__(self, value, height=0, lchild=None, rchild=None):
self.lchild = lchild
self.rchild = rchild
self.value = value
self.height = height
# tree definition
class Tree(object):
def __init__(self, root=None):
self.root = root
# node in-order traversal(LDR)
def traversal(self):
self.dotStr=''
traversal(self.root,self)
print(self.svgStr())
# insert node
def insert(self, value):
self.root = insert(self.root, value,self)
# delete node
def delete(self, value):
self.root = delete(self.root, value,self)
# 生成svg图形对应的文本串
def svgStr(self):
dotHeader ='''
digraph G
{
rankdir = TB;
node [shape=record];
'''
dotStr = dotHeader + self.dotStr +'}'
dotFile =open('avlTree.dot','w')
dotFile.write(dotStr)
dotFile.close()
#调用dot命令生成svg文件
os.system('dot -Tsvg avlTree.dot -o avlTree.html')
#取出svg图形文件的内容
svgFile =open('avlTree.html','r')
svgStr = svgFile.read()
svgFile.close()
return svgStr
# 遍历子树,生成dot格式的顶点或边
def traversal(node,tree):
if not node:
return
#print(node.value, 'height=', node.height)
#tree.dotStr += 'node%s [label = "{<e> %s | <p> height: %i }" ];\n'% (node.value,node.value,node.height)
tree.dotStr += 'node%s [label = "{<e> %s }" ];\n'% (node.value,node.value)
if node.lchild:
traversal(node.lchild,tree)
tree.dotStr +='node%s:e:s-> node%s:e:n;\n'% (node.value,node.lchild.value)
else:
tree.dotStr += 'node%slc [label = "{<e> %s }" style=dotted];\n'% (node.value,'^')
tree.dotStr +='node%s:e:s-> node%slc:e:n;\n'% (node.value,node.value)
if node.rchild:
traversal(node.rchild,tree)
tree.dotStr +='node%s:e:s-> node%s:e:n;\n'% (node.value,node.rchild.value)
else:
tree.dotStr += 'node%src [label = "{<e> %s }" style=dotted];\n'% (node.value,'^')
tree.dotStr +='node%s:e:s-> node%src:e:n;\n'% (node.value,node.value)
def println(str):
print('\n<br>')
print(str)
print('\n<br>')
# insert node
'''
the recursive insert operation has a flaw,
that the binary tree will recursively updates the height of the parent node
even if the inserted element already exists.
'''
def insert(subTreeRoot, value,tree):
if not subTreeRoot:
return Node(value)
#println('value:%d,subTreeRoot value:%d '%(value,subTreeRoot.value))
if value < subTreeRoot.value:
#println('insert at left ')
subTreeRoot.lchild = insert(subTreeRoot.lchild, value,tree)
elif value > subTreeRoot.value:
#println('insert at right ')
subTreeRoot.rchild = insert(subTreeRoot.rchild, value,tree)
return checkBalance(subTreeRoot,tree)
# check whether the tree is balanced
# 检查子树是否平衡,传入子树的根节点,如果子树不平衡,则调节使之平衡,返回平衡之后的子树根节点
def checkBalance(subTreeRoot,tree):
if not subTreeRoot:
return None
if abs(heightDiffL2R(subTreeRoot)) < 2: # the tree is balance
updateHeight(subTreeRoot)
elif heightDiffL2R(subTreeRoot) == 2: # situation L
println('子树[ %s ]左边比右边高 2 层'%subTreeRoot.value)
if heightDiffL2R(subTreeRoot.lchild) == -1: # situation LR
println('子树[ %s ]左边比右边矮 1 层,需先将[ %s ]向左旋转,然后将[ %s ]向右旋转'%(subTreeRoot.lchild.value,subTreeRoot.lchild.value,subTreeRoot.value))
tree.traversal()
subTreeRoot.lchild = rotateR2L(subTreeRoot.lchild)
tree.traversal()
subTreeRoot = rotateL2R(subTreeRoot)
else: # situation LL
println('子树[ %s ]左边不低于右边,只需将[ %s ]向右旋转即可'%(subTreeRoot.lchild.value,subTreeRoot.value))
tree.traversal()
subTreeRoot = rotateL2R(subTreeRoot)
elif heightDiffL2R(subTreeRoot) == -2: # situation R
println('子树[ %s ]左边比右边矮 2 层'%subTreeRoot.value)
if heightDiffL2R(subTreeRoot.rchild) == 1: # situation RL
println('子树[ %s ]左边比右边高 1 层,需先将[ %s ]向右旋转,然后将[ %s ]向左旋转'%(subTreeRoot.rchild.value,subTreeRoot.rchild.value,subTreeRoot.value))
tree.traversal()
subTreeRoot.rchild = rotateL2R(subTreeRoot.rchild)
tree.traversal()
subTreeRoot = rotateR2L(subTreeRoot)
else: # situation RR
println('子树[ %s ]左边不高于右边,只需将[ %s ]向左旋转即可'%(subTreeRoot.rchild.value,subTreeRoot.value))
tree.traversal()
subTreeRoot = rotateR2L(subTreeRoot)
return subTreeRoot
# get the height difference between left-child and right-child
# 左了树与右子树高度之差,可以为负数
def heightDiffL2R(node):
if node.lchild and node.rchild:
return node.lchild.height - node.rchild.height
if node.lchild:
return node.lchild.height + 1
if node.rchild:
return -(node.rchild.height + 1)
return 0
# update the height of the node
# 更新子树根节点的高度,叶节点高度为0,不要认为是1
def updateHeight(root):
if root.lchild and root.rchild:
root.height = max(root.lchild.height, root.rchild.height) + 1
elif root.lchild:
root.height = root.lchild.height + 1
elif root.rchild:
root.height = root.rchild.height + 1
else:
root.height = 0
# rotate from left to right with the left-child node as the axis
def rotateL2R(node):
leftChild = node.lchild
leftChild.rchild, node.lchild = node, leftChild.rchild
println('以[ %s ]为轴,将[ %s ]从左向右旋转,结果如下'%(leftChild.value,node.value))
updateHeight(node)
updateHeight(leftChild)
return leftChild
# rotate from right to left with the right-child node as the axis
def rotateR2L(node):
rightChild = node.rchild
rightChild.lchild, node.rchild = node, rightChild.lchild
println('以[ %s ]为轴,将[ %s ]从右向左旋转,结果如下'%(rightChild.value,node.value))
updateHeight(node)
updateHeight(rightChild)
return rightChild
# 删除指定节点
def delete(subTreeRoot, value,tree):
if not subTreeRoot:
return None
if subTreeRoot.value > value:
subTreeRoot.lchild = delete(subTreeRoot.lchild, value,tree)
elif subTreeRoot.value < value:
subTreeRoot.rchild = delete(subTreeRoot.rchild, value,tree)
else:
if subTreeRoot.lchild and subTreeRoot.rchild: # degree of the node is 2
target = transferDeleteNode(subTreeRoot)
subTreeRoot = delete(subTreeRoot, target,tree)
subTreeRoot.value = target
else: # degree of the node is [0|1]
subTreeRoot = subTreeRoot.lchild if subTreeRoot.lchild else subTreeRoot.rchild
return checkBalance(subTreeRoot,tree)
# find the maximum node or the minimum node in the tree
# 当子树根节点被删除后,适合代替这个节点的候选者有两个:左子树中的最大者或者右子树中的最小者
# 比较左右子树的深度,选深度较大的子树中的侯选者
def transferDeleteNode(node):
if node.rchild.height > node.lchild.height:
target = node.rchild
while target.lchild:
target = target.lchild
else:
target = node.lchild
while target.rchild:
target = target.rchild
return target.value
if __name__ == '__main__':
#arr = [5, 3, 4, 0, 2, 1, 8, 6, 9, 7,7]
#arr = [1, 2,3, 4, 5, 6, 7, 8, 9, 10,11,12]
arr = [12,11, 10,9,8,7,6,5,4,3, 2, 1]
arr = [10, 3, 4, 5, 2, 1, 8, 6, 99, 7,18]
#arr = [10,3,4]
println('根据如下数据')
print(arr)
println('生成平衡二叉树:')
T = Tree()
println('开始插入节点 ......')
for i in arr:
println('<br>------------ 插入节点 [ %d ]之后的二叉树如下图所示 ------------------------'%i)
T.insert(i)
T.traversal()
# println('开始删除节点 ......')
# for i in arr[::-1]:
# println('删除节点 [ %d ]之后的平衡二叉树:'%i)
# T.delete(i)
# T.traversal()