B树插入操作的图形化展示,python调用graphviz自动生成svg图形

48 篇文章 5 订阅
10 篇文章 0 订阅

引言

在前一篇文章《平衡二叉树插入及删除操作的图形化展示,python调用graphviz自动生成svg图形》用图形化的方式展示了平衡二叉树在插入或删除时图形的变化。现在来直观演示一下B树的插入过程

B树节点中的元素有序排列,并且个数有上限,当超过上限时,节点将会发生分裂,如果B树的根节点发生分烈,B树的高度就会加1。

B树通过分裂的方式,保证所有的叶子节点都在同一个层次上,实际上保证了的B树的平衡性。这是一个很巧妙的设计,我们接下来将用图形化演示的方式来揭示其奥妙所在。

在网上查到一篇文献《关于Btree》。该文作者用python实现了B树的基本操作。但是他的输出结果是文本形式的,不方便直观地查看B树的动态变化效果。本文在原作者python代码的基础上做了一些改进,实现了图形化展示的效果。

图形化显示的细节,还请参看以前的文章《平衡二叉树插入及删除操作的图形化展示,python调用graphviz自动生成svg图形
对svg图形做美化处理的细节,还请参看以前的文章《用jquery对graphviz生成的svg图形做后处理,改变字体,颜色,连线形状等

图形化效果

下面是插入一个随机选定的key序列[10, 17, 9, 33, 33, 50, 36, 41, 31, 30, 13, 6, 37, 45, 20, 4, 35, 11, 2, 40] 生成一棵3度B树的图示,请留意对节点分裂的提示
在这里插入图片描述
其它序列的插入过程的图形效果,可以将代码下载到本地后自已手动调整源码,然后在浏览器上查看图形效果。当然前提是你的电脑上已经安装好了python与graphviz。

完整的python代码

BTree.py

# -*- coding:utf-8 -*- 
# B树的图形化展示代码,
# 原作者:thursdayhawk https://blog.51cto.com/thuhak/1261783
# 修改成用graphviz图形化显示,修改者:littleZhuHui

import os
from random import randint,choice
from bisect import bisect_left
from collections import deque

idSeed = 0

#生成一个全局ID                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
def  getGID():
    global idSeed
    idSeed+=1
    return idSeed

class InitError(Exception):
    pass

class ParaError(Exception):
    pass

# 定义键值对
class KeyValue(object):
    __slots__=('key','value')
    def __init__(self,key,value):
        self.key=key
        self.value=value
    def __str__(self):
        return str((self.key,self.value))
    def __cmp__(self,key):
        if self.key>key:
            return 1
        elif self.key==key:
            return 0
        else:
            return -1

#B树节点            
class BtreeNode(object):

    def __init__(self,t,parent=None):
        if not isinstance(t,int):
            raise InitError,'degree of Btree must be int type'
        if t<2:
            raise InitError,'degree of Btree must be equal or greater then 2'
        else:
            self.keyList=[]
            self.childList=[]
            self.parent=parent
            self.__degree=t
            #每个节点有一个唯一的整数值做为id,方便用graphviz绘图
            self.id = getGID()

    def __str__(self):
        return str([k.key for k in self.keyList])

    @property
    def degree(self):
        return self.__degree

    def isleaf(self):
        return len(self.childList)==0

    #遍历节点对应的子树,深度优先遍历,返回树上所有节点上的所有(key,value)组成的列表
    # 注意 keyList 中存放的不是整数,而是 KeyValue 对象,但是这个KeyValue对象是可以与整数比较大小的,见KeyValue的定义
    def traversal(self):
        result=[]

        def get_value(n):
            if n.childList==[]:
                result.extend(n.keyList)
            else:
                for i,k in enumerate(n.keyList):
                    get_value(n.childList[i])
                    result.append(k)
                #childList比keyList多一个,要处理一下最右孩子指针
                get_value(n.childList[-1])

        get_value(self)

        return result

    #显示树上全部的节点,广度优先遍历
    def show(self,tree):
        q=deque()
        h=0
        q.append([self,h])

        #生成childList对应的dot格式的文本串
        def childListDotStr(n):
            dotStr ='{'
            if n.childList==[]:
                return '{}'
            else:
                for i,k in enumerate(n.keyList):
                    dotStr +='<f%s>#%s|'%(n.childList[i].id,n.childList[i].id)
                #childList比keyList多一个,要处理一下最右孩子指针               
                dotStr +='<f%s>#%s}'%(n.childList[-1].id,n.childList[-1].id)
            return dotStr

        #生成childList对应的dot格式的文本串
        def childListEdgeStr(n):
            dotStr =''
            if n.childList==[]:
                return ''
            else:
                for i,k in enumerate(n.keyList):
                    dotStr +='node%s:f%s:s--node%s:e:n;\n'% (n.id,n.childList[i].id,n.childList[i].id)
                #childList比keyList多一个,要处理一下最右孩子指针               
                dotStr +='node%s:f%s:s--node%s:e:n;\n'% (n.id,n.childList[-1].id,n.childList[-1].id)
            return dotStr

        while True:
            try:
                node,height=q.popleft()
            except IndexError:
                return
            else:
                #print 'id:',node.id,[k.key for k in node.keyList],'level:',height
                #print 'id:',node.id,str(node),'level:',height
                tree.dotStr += 'node%s [label = "{<e> #%s|%s| %s}" ];\n'% (node.id,node.id,str(node),childListDotStr(node))
                tree.dotStr += childListEdgeStr(node)
                if node.childList==[]:
                    continue
                else:
                    if height==h:
                        h+=1
                    q.extend([[n,h] for n in node.childList])

    #取得节点对应的子树上的最大值(key的最大值)
    def getmax(self):
        n=self
        while not n.isleaf():
            n=n.childList[-1]
        return (n.keyList[-1],n)

    #取得节点对应的子树上的最小值(key的最小值)
    def getmin(self):
        n=self
        while not n.isleaf():
            n=n.childList[0]
        return (n.keyList[0],n)

#------------------ node 定义结束 ----------------------------------------
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
#B树类
class Btree(object):

    def __init__(self,t):
        self.__degree=t
        self.__root=BtreeNode(t)

    @property
    def degree(self):
        return self.__degree

    def traversal(self):
        """
        use dfs to search a btree's node
        """
        return self.__root.traversal()

    def show(self):
        """
        use bfs to show a btree's node and its height
        """
        self.dotStr=''
        self.__root.show(self)
        print(self.svgStr())

    # 生成svg图形对应的文本串
    def svgStr(self):
        dotHeader ='''
        graph G
{       
        rankdir = TB;
        node [shape=record];
        '''
        dotStr = dotHeader + self.dotStr +'}'
        dotFile =open('BTree.dot','w')
        dotFile.write(dotStr)
        dotFile.close()
        #调用dot命令生成svg文件
        os.system('dot -Tsvg BTree.dot -o BTree.html')
        #取出svg图形文件的内容
        svgFile =open('BTree.html','r')
        svgStr = svgFile.read()
        svgFile.close()        
        return svgStr
    
    #将一个key_value对象插入B树
    def insert(self,key_value):

        #节点中元素个数不能超过2*度数-1
        full=self.degree*2-1
        mid=full/2+1

        #将节点n分裂
        def split(n):
            new_node=BtreeNode(self.degree,parent=n.parent)

            #新节点是原节点列表的后半部分
            new_node.keyList=n.keyList[mid:]
            new_node.childList=n.childList[mid:]
            
            #重置新节点的所有孩子节点的父指针
            for c in new_node.childList:
                c.parent=new_node

            if n.parent is None:
                #如果n是B树的根节点

                #构造一个新的根节点
                newroot=BtreeNode(self.degree)

                #新的根节点的keyList中添加原节点的"中点"
                newroot.keyList=[n.keyList[mid-1]]

                #新的根节点将拥有n与n分裂出来的兄弟,做为其两个孩子
                newroot.childList=[n,new_node]

                #重置n与n分裂出来的兄弟的父指针
                n.parent=new_node.parent=newroot

                #将新的根节点做为B树的根节点
                self.__root=newroot
            else:
                #如果n并不是B树的根节点

                #找到n节点在其父节点的孩子列表中的下标
                i=n.parent.childList.index(n)

                #n的父节点的keyList中添加原节点的"中点"
                n.parent.keyList.insert(i,n.keyList[mid-1])

                #n的父节点的孩子列表中增加对新节点的孩子指针
                n.parent.childList.insert(i+1,new_node)

            #原节点只保留原来的列表的前半部分
            n.keyList=n.keyList[:mid-1]
            n.childList=n.childList[:mid]

            #返回新(旧)节点的共同的父节点
            return n.parent

        #在节点n所对应的子树上进行插入(此函数可能会递归调用)
        def insert_node(n):

            #如果节点n中的元素个数已经满了,将节点n分列,从新的子树的根节点开始进入插入(递归调用)
            if len(n.keyList)==full:
                println('#%s 号节点元素个数已经有 %s 个,应该分裂为两个节点'%(n.id,full))
                insert_node(split(n))
            else:
                #如果节点n的keyList为空,则直接将key_value对象加入keyList(keyList不仅仅是存放了key,其实还存放了value)
                #只有叶节点的keyList才有可能为空,
                if n.keyList==[]:
                    n.keyList.append(key_value)
                else:
                    if n.isleaf():
                        #如果是叶子节点,这时元素个数又未满,则有序插入keyList
                        # 实际的插入操作永远发生在叶节点,只有在叶节点未满或为空时才会发生实际的插入操作
                        p=bisect_left(n.keyList,key_value)  #locate insert point in ordered list keyList
                        n.keyList.insert(p,key_value)
                    else:
                        #如果是非叶节点,找到应该插入的子树,对子树进行插入操作(递归调用)
                        p=bisect_left(n.keyList,key_value)
                        insert_node(n.childList[p])

        #从B树的根节点开始插入操作
        insert_node(self.__root)

#------------------ Btree 定义结束 ----------------------------------------


def println(str):
    print('\n<br>')
    print(str)
    print('\n<br>')

#固定序列
def fixTestList():
    testlist=[]
    keyList =[10, 17, 9, 33, 33, 50, 36, 41, 31, 30, 13, 6, 37, 45, 20, 4, 35, 11, 2, 40]
    for key in keyList:        
        value=choice('abcdefghijklmn')
        testlist.append(KeyValue(key,value))
    println('待插入序列')
    println(str([k.key for k in testlist]))
    return testlist;


#测试插入操作                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
def testInsert():    
    
    #B树阶数
    degree = 3
    #节点中元素个数上限
    full=degree*2-1
    
    #构造一棵空树
    mybtree=Btree(degree)

    println('%s度B树的插入过程,节点中元素不应超过 %s 个'%(degree,full))

    testlist = fixTestList()
    for x in testlist:
        mybtree.insert(x)        
        println('插入键 %s 之后的B树'%x.key)
        mybtree.show()

if __name__=='__main__':
    testInsert()

由于还对graphviz生成了svg图形做美化处理,如果想得到与美化svg相关的代码,还请参看以前的文章《用jquery对graphviz生成的svg图形做后处理,改变字体,颜色,连线形状等》。

用本文提供的新的BTree.py文件替换该篇文章中的BTree.py按该篇文章所说的环境拱建过程进行操作,应该就可以看到完整的效果了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值