平衡二叉树插入及删除操作的图形化展示,python调用graphviz自动生成svg图形

引言

平衡二叉树的基本性质为许多人所熟知,简单说来就是“左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树”。

平衡二叉树为了保证其平衡性,需要在插入或删除节点时动态地调整树节点,使其一直保持平衡。在网上查到一篇文献《平衡二叉树(AVL树)》。该文作者用python实现了平衡二叉树的基本操作。唯一有点遗憾的是他的输出结果是文本形式的,不方便直观地查看插入和删除操作的效果。

本文在原作者python代码的基础上做了一些改进,实现了图形化展示的效果。

基本工作原理

实现步骤
1.在进行二叉树遍历时,动态地生成graphviz图形源文件dot语言格式的顶点与边
以下为遍历时生成dot片断的代码

def traversal(node,tree):
    if not node:
        return  
    traversal(node.lchild,tree)
    #print(node.value, 'height=', node.height)
    tree.dotStr += 'node%s [label = "%s" ];\n'% (node.value,node.value)
    traversal(node.rchild,tree)
    if node.lchild:
    	tree.dotStr +='node%s:s-> node%s:n;\n'% (node.value,node.lchild.value)
    if node.rchild:
    	tree.dotStr +='node%s:s-> node%s:n;\n'% (node.value,node.rchild.value)

访问每一个节点时将生成类似于下面的dot文件格式的片断

node3 [label = "3" ];
node2:s-> node1:n;
node2:s-> node3:n;

对graphviz 的dot语言比较熟悉的人可以一眼看出。以上三句话定义了一个顶点,两条有向边。

2.将遍历得到的dot图形的片断与graph的基本定义片断组合成一个完整的dot格式的源文件
主要代码如下

dotHeader ='''
        digraph G
{       
        rankdir = TB;
        node [shape=circle];
        '''
        dotStr = dotHeader + self.dotStr +'}'
        dotFile =open('avlTree.dot','w')
        dotFile.write(dotStr)
        dotFile.close()

以下是由python自动生成的11个节点的平衡二叉树的dot源文件:avlTree.dot

digraph G
{       
        rankdir = TB;
        node [shape=circle];
        node1 [label = "1" ];
node2 [label = "2" ];
node3 [label = "3" ];
node2:s-> node1:n;
node2:s-> node3:n;
node4 [label = "4" ];
node5 [label = "5" ];
node6 [label = "6" ];
node7 [label = "7" ];
node6:s-> node5:n;
node6:s-> node7:n;
node8 [label = "8" ];
node10 [label = "10" ];
node18 [label = "18" ];
node99 [label = "99" ];
node18:s-> node10:n;
node18:s-> node99:n;
node8:s-> node6:n;
node8:s-> node18:n;
node4:s-> node2:n;
node4:s-> node8:n;
}
  1. 通过dot命令将上一步生成的图形源文件转换为svg源文件
    主要代码如下
#调用dot命令生成svg文件
os.system('dot -Tsvg avlTree.dot -o avlTree.html')

通过python的os模块执行命令行,由avlTree.dot文件生成了svg图形文件avlTree.html

4.在每插入或删除一个节点时,动态地生成代表平衡二叉树的svg图形文件,并在浏览器页面上显示。
实际效果如下:
在这里插入图片描述

完整的python代码

avlTree.py

# -*- 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.dotStr

    # insert node
    def insert(self, value):
        self.root = insert(self.root, value)

    # delete node
    def delete(self, value):
        self.root = delete(self.root, value)

    # 生成svg图形对应的文本串
    def svgStr(self):
        dotHeader ='''
        digraph G
{       
        rankdir = TB;
        node [shape=circle];
        '''
        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

# node in-order traversal(LDR)
def traversal(node,tree):
    if not node:
        return  
    traversal(node.lchild,tree)
    #print(node.value, 'height=', node.height)
    tree.dotStr += 'node%s [label = "%s" ];\n'% (node.value,node.value)
    traversal(node.rchild,tree)
    if node.lchild:
    	tree.dotStr +='node%s:s-> node%s:n;\n'% (node.value,node.lchild.value)
    if node.rchild:
    	tree.dotStr +='node%s:s-> node%s:n;\n'% (node.value,node.rchild.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(root, value):
    if not root:
        return Node(value)
    if value < root.value:
        root.lchild = insert(root.lchild, value)
    elif value > root.value:
        root.rchild = insert(root.rchild, value)
    return checkBalance(root)

# check whether the tree is balanced
def checkBalance(root):
    if not root:
        return None
    if abs(heightDiffL2R(root)) < 2:  # the tree is balance
        updateHeight(root)
    elif heightDiffL2R(root) == 2:  # situation L
        if heightDiffL2R(root.lchild) == -1:  # situation LR
            root.lchild = rotateR2L(root.lchild)
            root = rotateL2R(root)
        else:  # situation LL
            root = rotateL2R(root)
    elif heightDiffL2R(root) == -2:  # situation R
        if heightDiffL2R(root.rchild) == 1:  # situation RL
            root.rchild = rotateL2R(root.rchild)
            root = rotateR2L(root)
        else:  # situation RR
            root = rotateR2L(root)
    return root

# 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
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
    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
    updateHeight(node)
    updateHeight(rightChild)
    return rightChild

# 删除指定节点
def delete(root, value):
    if not root:
        return None
    if root.value > value:
        root.lchild = delete(root.lchild, value)
    elif root.value < value:
        root.rchild = delete(root.rchild, value)
    else:
        if root.lchild and root.rchild:  # degree of the node is 2
            target = transferDeleteNode(root)
            root = delete(root, target)
            root.value = target
        else:  # degree of the node is [0|1]
            root = root.lchild if root.lchild else root.rchild
    return checkBalance(root)

# 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, 6, 9, 10]
    arr = [10, 3, 4, 5, 2, 1, 8, 6, 99, 7,18]

    println('根据如下数据')
    print(arr)
    println('生成平衡二叉树:')

    T = Tree()

    println('开始插入节点 ......')
    for i in arr:
        println('插入节点 [ %d ]之后的平衡二叉树:'%i)
        T.insert(i)
        T.traversal()
        print(T.svgStr())
    
    
    println('开始删除节点 ......')
    for i in arr[::-1]:    	
        println('删除节点 [ %d ]之后的平衡二叉树:'%i)
        T.delete(i)        
        T.traversal()
        print(T.svgStr())
        

补充说明

先将以上所示的python代码保存在某个目录下,命名为avlTree.py

为了检查图形效果,有两种方式可以实现

方法1,在终端窗口执行如下的命令: python avlTree.py >res.html
将会在avlTree.py所在的目录下生成res.html文件,双击此html文件,将会在浏览器中打开,就可以看到图形化的效果。
在此之前应确保python.exe所在的路径已经加入到操作系统的环境变量path中了。

方法2,如果每次修改了python源文件后都要在命令行中执行一次命令,并不是很方便。一个更巧妙的办法用apache服务器执行python脚本,直接在浏览器中查看图形化的效果。apache一般与php配合使用。这里是python脚本,该怎么办呢?
其实很简单,写一个一句话的php脚本就可以了
avlTree.py.php

passthru('python avlTree.py');

这个php脚本什么别的也不干,就是调用python.exe执行avlTree.py这个脚本。

关于将本来在命令行执行的脚本转为在浏览器上执行的详细过程,可以参见我以前的一篇文章《命令行执行php改成通过浏览器执行,自动实现图形菜单功能

假定你已经将上述两个文件 avlTree.py与 avlTree.py.php放在了某个目录的avl目录下,并在apache中修改了配置,将一个虚拟目录指向了这个目录。那么在浏览器的地址栏输入如下的内容:
http://localhost:8080/graphWork/avl/
应该就可以看到如下图所示的效果
在这里插入图片描述
用鼠标点击" avlTree.py.php"应该就可以看到图形效果:
在这里插入图片描述
可能有的人觉得方法2太麻烦,还不如方法1简便,但实际上apache的配置是一次性的,如果在文本编辑器中修改了python源代码。比如要在树中多插入一个节点[20],只需修改python源代码,将第178行的

 arr = [10, 3, 4, 5, 2, 1, 8, 6, 99, 7,18]

修改为

 arr = [10, 3, 4, 5, 2, 1, 8, 6, 99, 7,18,20]

然后用鼠标点击浏览器页面上的" avlTree.py.php",就可以立即看到图形化的效果,不用再转去命令行执行python命令。
如果要不断地修改python源文件并查看修改后的效果,方法2的简便性就体现出来了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值