平衡二叉树旋转操作的图形化展示,python调用graphviz自动生成svg图形

引言

在前一篇文章《平衡二叉树插入及删除操作的图形化展示,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()
    
        
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值