B+树中处理重复键值,python调用graphviz自动生成svg图形

前面的一篇文章《B+树插入操作的图形化展示,python调用graphviz自动生成svg图形》展示了B+树的插入过程。

当插入重复的键值到B+树(B树)时,会因为相等的键值分裂为左右孩子而出现违反B+树(B树)约定的情形。有必要对重复键值做特别的处理。

先看下面这个例子,特别用一个极端情形加以说明。
B+树中处理重复键值,python调用graphviz自动生成svg图形_第1张图片
当插入全是2的序列后,由于未对重复键值做处理,在节点发生分裂时,相同的键值变成了左右孩子。

解决方案

只在B+树的节点中存放不重复的键值,对于重复的键值则添加一个溢出表,在溢出表中存放与该键值相关的附加信息。
针对原先的keyValue类

# 定义键值对
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

做如下改进

# 定义键值对(为了处理重复键值,对kv类做改进,使得一个key可以对应多个value)
class KeyValue(object):
    __slots__=('key','valueList')
    def __init__(self,key,value):
        self.key=key
        self.valueList=[value]        
    def __str__(self):
        return '%s,%s'%(self.key,str([v for v in self.valueList]))
    def __cmp__(self,key):
        if self.key>key:
            return 1
        elif self.key==key:
            return 0
        else:
            return -1
    def appendValue(self,value):
        self.valueList.insert(0,value)
    @property
    def value(self): #默认value就是list中的第0个value       
        return self.valueList[0]
    @property
    def vLen(self):     
        return len(self.valueList)

其中最核必的一点:原来的key和一个value值对应,而现在key可以和多个value值对应,用list存放与key对应的多个附加信息。

并且在插入过程中,原来是只要检查到了叶子节点,就插入新值,而现在会检查在叶子节点上是否找到了要插入的值,如果找到,则并不执行插入操作,而是执行keyValue对象的appendValue方法,将value值加入到keyValue对象的valueList。

p=bisect_left(n.kvList,key_value)
                if len(n.kvList) > p and n.kvList[p]==key_value:
                    #发现了重复键值
                    n.kvList[p].appendValue(key_value.value)
                else:
                    n.kvList.insert(p,key_value)
                if n.isfull():
                    tipInfo(' #%s 号叶子节点已满, 分裂该节点 '%(n.id))
                    split_leaf(n)
                else:
                    return

上述的重复键值序列[2, 2, 2, 2, 2, 2] ,用改进过后的算法处理,图形效果如下:
B+树中处理重复键值,python调用graphviz自动生成svg图形_第2张图片
这里采用红色的上标指明当前键值重复的次数。

下面再换一个序列
B+树中处理重复键值,python调用graphviz自动生成svg图形_第3张图片
可以看到键值33重复出现了2次,但在节点中只插入了一次,键值33的附加信息存放在keyValue的valueList当中。

为了在图形中用上标显示键值的重复次数,所用的后处理代码,请参考文章《用jquery对graphviz生成的svg图形做后处理(续篇一)在SVG文本中显示上标或下标》

下面是改进后的,可以处理重复键值的代码

完整的python代码

# -*- coding:utf-8 -*- 
# B+树的序列化
# 原作者:thursdayhawk http://wiki.jikexueyuan.com/project/python-actual-combat/tutorial-11.html
# 修改成用graphviz图形化显示,添加了序列化为json格式文件的功能,修改者:littleZhuHui

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

#显示信息的级别,控制不同等级的信息的可见性
infoLevel = 1

# id值为1的节点是 superRoot,是所有操作的起点,
#因此固定为1,而0表示找不到有效的节点
idSeed = 1

#存放已加载节点的hash表,由节点id映射到节点对象
nodePool ={
     }

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

#------------------ node 定义开始 ----------------------------------------------

class InitError(Exception):
    pass

class LoadNodeError(Exception):
    pass

class ParaError(Exception):
    pass

# 定义键值对(为了处理重复键值,对kv类做改进,使得一个key可以对应多个value)
class KeyValue(object):
    __slots__=('key','valueList')
    def __init__(self,key,value):
        self.key=key
        self.valueList=[value]        
    def __str__(self):
        return '%s,%s'%(self.key,str([v for v in self.valueList]))
    def __cmp__(self,key):
        if self.key>key:
            return 1
        elif self.key==key:
            return 0
        else:
            return -1
    def appendValue(self,value):
        self.valueList.insert(0,value)
    @property
    def value(self): #默认value就是list中的第0个value       
        return self.valueList[0]
    @property
    def vLen(self):     
        return len(self.valueList)

# 定义可按下标进行控制访问的childList类
# 不直接使用列表而用自定义对象表示childList ,主要原因有两点
# 按需加载,孩子节点序列化时存放的节点Id值,当实际访问时加载为对象
# 访问控制,当修改了孩子节点指针的值之后,可以有机会做更多的处理(可以实现事件钩子功能)
class ChildList(object):
    def __init__(self,listVal=[]): 
        self._childList = listVal

    def __getitem__(self,i):          
        if type(self._childList[i])==type(0): #需要加载
            self._childList[i] = loadNode(self._childList[i])
        return self._childList[i]
       
    def __setitem__(self,i,value): 
        self._childList[i] = value

    def index(self,value): 
        return self._childList.index(value)

    def insert(self,i,value): 
        return self._childList.insert(i,value)

    def remove(self,value): 
        return self._childList.remove(value)

    def pop(self): 
        return self._childList.pop()
       
# 内部节点
class Bptree_InterNode(object):

    def __init__(self,M,id=-1):
        if not isinstance(M,int):
            raise InitError,'M must be int'
        if M<=3:
            raise InitError,'M must be greater then 3'
        else:
            self.M=M
            self.childList=ChildList()
            self.indexList=[]
            self._bro=-1
            self._par=None
            #每个节点有一个唯一的整数值做为id,方便用graphviz绘图
            if id < 0:
                self.id = getGID()
            else:
                self.id = id

    def isleaf(self):
        return False

    def isfull(self):
        return len(self.indexList)>=self.M-1

    def isempty(self):
        return len(self.indexList)<=(self.M+1)/2-1

    @property
    def bro(self):
        if type(self._bro)==type(0): #需要加载
            if self._bro > 1 : # 确实对应着一个节点,1是超级根节点,不算在内
                self._bro = loadNode(self._bro)
        return self._bro

    @bro.setter
    def bro(self,value):
        #可在此处做一些额外的处理
        self._bro=value

    @property
    def par(self):
        if type(self._par)==type(0): #需要加载
            if self._bro > 1 :
                self._par = loadNode(self._par)
        return self._par

    @par.setter
    def par(self,value):
        #可在此处做一些额外的处理
        self._par=value

#叶子节点       
class Bptree_Leaf(object):

    def __init__(self,L,id=-1):
        if not isinstance(L,int):
            raise InitError,'L must be int'
        else:
            self.L=L
            #self.childList='
\nleaf has no child list
\n'
self.kvList=[] self._bro=0 self._par=None #每个节点有一个唯一的整数值做为id,方便用graphviz绘图 if id < 0: self.id = getGID() else: self.id = id def isleaf(self): return True def isfull(self): return len(self.kvList)>self.L def isempty(self): return len(self.kvList)<=(self.L+1)/2 @property def bro(self): if type(self._bro)==type(0): #需要加载 if self._bro > 1 : self._bro = loadNode(self._bro) return self._bro @bro.setter def bro(self,value): #可在此处做一些额外的处理 self._bro=value @property def par(self): if type(self._par)==type(0): #需要加载 if self._bro > 1 : self._par = loadNode(self._par) return self._par @par.setter def par(self,value): #可在此处做一些额外的处理 self._par=value #------------------ node 定义结束 ---------------------------------------------- #------------------ B+ 树 定义开始 ---------------------------------------- #B+树类 class Bptree(object): def __init__(self,M,L): if L>M: raise InitError,'L must be less or equal then M' else: self.M=M self.L=L self.__root=Bptree_Leaf(L) self.__leaf=self.__root #在树上查找 def search(self,mi=None,ma=None): result=[] node=self.__root leaf=self.__leaf if mi is None and ma is None: raise ParaError,'you need to setup searching range' elif mi is not None and ma is not None and mi>ma: raise ParaError,'upper bound must be greater or equal than lower bound' def search_key(n,k): if n.isleaf(): p=bisect_left(n.kvList,k) return (p,n) else: p=bisect_right(n.indexList,k) return search_key(n.childList[p],k) if mi is None: while True: for kv in leaf.kvList: if kv<=ma: result.append(kv) else: return result if leaf.bro==None: return result else: leaf=leaf.bro elif ma is None: index,leaf=search_key(node,mi) result.extend(leaf.kvList[index:]) while True: if leaf.bro==None: return result else: leaf=leaf.bro result.extend(leaf.kvList) else: if mi==ma: i,l=search_key(node,mi) try: if l.kvList[i]==mi: result.append(l.kvList[i]) return result else: return result except IndexError: return result else: i1,l1=search_key(node,mi) i2,l2=search_key(node,ma) if l1 is l2: if i1==i2: return result else: result.extend(l.kvList[i1:i2]) return result else: result.extend(l1.kvList[i1:]) l=l1 while True: if l.bro==l2: result.extend(l2.kvList[:i2+1]) return result else: result.extend(l.bro.kvList) l=l.bro #遍历B+树的所有叶子节点 def traversal(self): result=[] l=self.__leaf while True: result.extend(l.kvList) if l.bro==None: return result else: l=l.bro #显示B+树 def show(self): def dotShow(tree): q=deque() h=0 q.append([self.__root,h]) #生成childList对应的dot格式的文本串 def childListDotStr(n): dotStr ='{' if n.childList==[]: return '{}' else: for i,k in enumerate(n.indexList): dotStr +='#%s|'%(n.childList[i].id,n.childList[i].id) #childList比indexList多一个,要处理一下最右孩子指针 dotStr +='#%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.indexList): dotStr +='node%s:f%s:s--node%s:e:n;\n'% (n.id,n.childList[i].id,n.childList[i].id) #childList比indexList多一个,要处理一下最右孩子指针 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: if not node.isleaf(): #内部节点 #print node.indexList,'the height is',height nodeText = str([k for k in node.indexList]) tree.dotStr += 'node%s [label = "{ #%s|%s| %s}" ];\n'% (node.id,node.id,nodeText,childListDotStr(node)) tree.dotStr += childListEdgeStr(node) if height==h: h+=1 q.extend([[n,h] for n in node.childList]) else: #叶节点 #print [v.key for v in node.kvList],'the leaf is,',height nodeText = str([i.key if i.vLen <=1 else '%s^%s'%(i.key,i.vLen) for i in node.kvList]) tree.dotStr += 'node%s [label = "{ #%s|%s}" ];\n'% (node.id,node.id,nodeText) self.dotStr='' dotShow(self) print(self.svgStr()) # 生成svg图形对应的文本串 def svgStr(self): dotHeader =''' graph G { rankdir = TB; node [shape=record]; ''' dotStr = dotHeader + self.dotStr +'}' dotFile =open('BplusTree.dot','w') dotFile.write(dotStr) dotFile.close() #调用dot命令生成svg文件 os.system('dot -Tsvg BplusTree.dot -o BplusTree.html') #取出svg图形文件的内容 svgFile =open('BplusTree.html','r') svgStr = svgFile.read() svgFile.close() return svgStr #插入操作 def insert(self,key_value): #内部节点分裂 def split_node(n1): mid=self.M/2 #分裂点为度数的中点 newnode=Bptree_InterNode(self.M) #新节点的数据是原节点的后半部分 newnode.indexList=n1.indexList[mid:] newnode.childList=ChildList(n1.childList[mid:]) newnode.par=n1.par for c in newnode.childList: c.par=newnode if n1.par is None: #如果当前节点是根节点,则创建一个新的根节点 newroot=Bptree_InterNode(self.M) tipInfo(' #%s 号内部节点分裂,键 %s 将复制(上升)到新的根节点 #%s 中'%(n1.id,n1.indexList[mid-1],newroot.id)) newroot.indexList=[n1.indexList[mid-1]] newroot.childList=ChildList([n1,newnode]) n1.par=newnode.par=newroot self.__root=newroot else: #如果当前节点不是根节点 tipInfo(' #%s 号内部节点分裂,键 %s 将复制(上升)到父节点 #%s 中'%(n1.id,n1.indexList[mid-1],n1.par.id)) i=n1.par.childList.index(n1) n1.par.indexList.insert(i,n1.indexList[mid-1]) n1.par.childList.insert(i+1,newnode) n1.indexList=n1.indexList[:mid-1] n1.childList=ChildList(n1.childList[:mid]) return n1.par #叶子节点分裂 def split_leaf(n2): mid=(self.L+1)/2 #分裂点为叶子节点度数+1的中点 newleaf=Bptree_Leaf(self.L) newleaf.kvList=n2.kvList[mid:] if n2.par==None: #如果当前节点是既是叶子节点又是根节点,则创建一个新的内部节点 newroot=Bptree_InterNode(self.M) tipInfo(' #%s 号叶子节点分裂,键 %s 将复制(上升)到新的根节点 #%s 中'%(n2.id,n2.kvList[mid].key,newroot.id)) newroot.indexList=[n2.kvList[mid].key] newroot.childList=ChildList([n2,newleaf]) n2.par=newroot newleaf.par=newroot self.__root=newroot else: tipInfo(' #%s 号叶子节点分裂,键 %s 将复制(上升)到父节点 #%s 中'%(n2.id,n2.kvList[mid].key,n2.par.id)) i=n2.par.childList.index(n2) n2.par.indexList.insert(i,n2.kvList[mid].key) n2.par.childList.insert(i+1,newleaf) newleaf.par=n2.par n2.kvList=n2.kvList[:mid] n2.bro=newleaf #插入节点 def insert_node(n): tipInfo('对 #%s 号节点进行检查 '%(n.id)) if not n.isleaf(): tipInfo(' #%s 号节点是内部节点 '%(n.id)) if n.isfull(): tipInfo(' #%s 号节点已满,分裂后再做插入操作 '%(n.id)) insert_node(split_node(n)) else: p=bisect_right(n.indexList,key_value) #tipInfo(' 插入位置:%s '%p) pp = 0 if p == 0 else p - 1 if p > 0: tipInfo(' #%s 号节点未满,找到稍小于 %s 的键值 %s ,在 %s 的右孩子 #%s 号节点上执行插入操作'%(n.id,key_value.key,n.indexList[pp],n.indexList[pp],n.childList[p].id)) else: tipInfo(' #%s 号节点未满,只能找到比 %s 稍大的键值 %s ,在 %s 的左孩子 #%s 号节点上执行插入操作'%(n.id,key_value.key,n.indexList[pp],n.indexList[pp],n.childList[p].id)) insert_node(n.childList[p]) else: tipInfo(' #%s 号节点是叶子节点, 实际插入键值与卫星数据 '%(n.id)) #p=bisect_right(n.kvList,key_value) p=bisect_left(n.kvList,key_value) if len(n.kvList) > p and n.kvList[p]==key_value: #发现了重复键值 n.kvList[p].appendValue(key_value.value) else: n.kvList.insert(p,key_value) if n.isfull(): tipInfo(' #%s 号叶子节点已满, 分裂该节点 '%(n.id)) split_leaf(n) else: return insert_node(self.__root) #------------------ B+ 树 定义结束 ---------------------------------------- def tipInfo(str): global infoLevel if infoLevel and infoLevel < 2: println(str) def debug(value,tip=''): global infoLevel if infoLevel and infoLevel < 1: print('\n
****** debug ****** %s *******'
%tip) print(value) print('\n
'
) def println(str): print('\n
'
) print(str) print('\n
'
) #固定序列 def fixTestList(): testlist=[] keyList =[10, 17, 9, 33, 33, 50, 36, 41, 31, 30, 13, 6, 37, 45, 20, 4, 35, 11, 2, 40] #keyList =[2, 2, 2, 2, 2, 2] keyList =[10, 17, 9, 33, 33, 50] for key in keyList: value=choice('abcdefghijklmn') testlist.append(KeyValue(key,value)) tipInfo(str([k.key for k in testlist])) return testlist; #随机序列,用50个数的随机序列生成B+树时,观察生成的图形,大部分时候树高都是3, #但偶尔出现树高为4,是因为B+树在面对特定数据时树高会高一些吗? def randTestList(): testlist=[] for i in range(1,100): key=randint(1,100) value=choice('abcdefghijklmn') testlist.append(KeyValue(key,value)) tipInfo(str([k.key for k in testlist])) return testlist; #测试插入操作 def testInsert(): M=4 L=4 #构造一个空的B+树 mybptree=Bptree(M,L) println('B+树的插入过程, 内部%s阶,叶子%s阶 '%(M,L)) tipInfo('插入序列') testlist = fixTestList() #testlist = randTestList() for kv in testlist: tipInfo('
------------ 准备插入 %s : -------------------------------'
%kv.key) mybptree.insert(kv) tipInfo('插入 %s 后的B+树'%kv.key) mybptree.show() if __name__=='__main__': testInsert()

你可能感兴趣的:(python,编程语言,图形菜单,B+树,重复键值,python,graphviz,svg)