学习笔记-用二叉排序树实现字典类(Python实现)

1、二叉排序树,又称二叉查找树,或二叉搜索树。

二叉排序树或者为空,或者具有以下性质:

  • 如果其左子树不空,那么其左子树上所有结点的值均小于其根结点的值。
  • 如果其右子树不空,那么其右子树上所有结点的值均大于其根结点的值。
  • 非空的左子树或右子树也是二叉排序树。

如果对二叉排序树做中序遍历,将得到一个递增序列。利用二叉排序树可以提高查找、插入和删除元素的速度。

下图是数据集[36,65,18,7,60,89,43,57,96,52,74]所生成的两棵二叉排序树。可见,同一个数据集对应的二叉排序树不唯一。


以下利用二叉排序树来实现一个字典类:

class Assoc: #定义一个关联类
    def __init__(self, key, value):
        self.key = key #键(关键码)
        self.value = value #值
    
    def __lt__(self, other):#Python解释器中遇到比较运算符<,会去找类里定义的__lt__方法(less than)
        return self.key < other.key
    
    def __le__(self, other): #(less than or equal to)
        return self.key < other.key or self.key == other.key
    
    def __str__(self):
        return 'Assoc({0},{1})'.format(self.key, self.value) #key和value分别替换前面{0},{1}的位置。

class BinTNode:  
    def __init__(self, dat, left = None, right = None):  
        self.data = dat  
        self.left = left  
        self.right = right 
    
class DictBinTNode:  
    def __init__(self, root = None):  
        self.root = root
    
    def is_empty(self):
        return self.root is None
    
    def search(self, key):#检索是否存在关键码key
        bt = self.root
        while bt is not None:
            entry = bt.data
            if key < entry.key: 
                bt = bt.left
            elif key > entry.key:
                bt = bt.right
            else:
                return entry.value
        return None
        
if __name__=="__main__":
#    t = BinTNode(5, BinTNode(3,BinTNode(2)), BinTNode(8)) 
    t = BinTNode(Assoc(5,'a'), BinTNode(Assoc(3,'b'),BinTNode(Assoc(2,'d'))), BinTNode(Assoc(8,'c')))
    dic = DictBinTNode(t)
    print(dic.search(3))

2、向二叉排序树字典中插入数据的基本算法:

  • 如果二叉树为空,直接将数据插在根结点上
  • 如果小于根结点的值,则转向左子树,若左子树为空,则将数据插在这里。
  • 如果大于根结点的值,则转向右子树,若右子树为空,则将数据插在这里。
  • 如果等于结点的值,则将结点的关联值直接替换掉。
def insert(self, key, value):
        bt = self.root
        if bt is None:
            self.root = BinTNode(Assoc(key, value))
            return
        while True:
            entry = bt.data
            if key < entry.key: #如果小于当前关键码,转向左子树
                if bt.left is None: #如果左子树为空,就直接将数据插在这里
                    bt.left = BinTNode(Assoc(key,value))
                    return
                bt = bt.left
            elif key > entry.key:
                if bt.right is None:
                    bt.right = BinTNode(Assoc(key,value))
                    return
                bt = bt.right
            else:
                bt.data.value = value
                return
if __name__=="__main__":
    t = BinTNode(Assoc(5,'a'), BinTNode(Assoc(3,'b'),BinTNode(Assoc(2,'d')), BinTNode(Assoc(4,'e')) ), BinTNode(Assoc(8,'c')))
    dic = DictBinTree(t)
    dic.insert(7,'e')
3、下面定义一个可以将字典中所有数据值都打印出来的迭代器方法,以便于可以利用for循环查看里面所有的数据。(其实就是中序 遍历)。   SStack类是基于之前文章中的定义的栈类: 点击打开链接
def print_all_values(self):
        bt, s = self.root, SStack()
        while bt is not None or not s.is_empty(): #最开始时栈为空,但bt不为空;bt = bt.right可能为空,栈不为空;当两者都为空时,说明已经全部遍历完成了
            while bt is not None:
                s.push(bt)
                bt = bt.left
            bt = s.pop() #将栈顶元素弹出
            yield bt.data.value
            bt = bt.right #将当前结点的右子结点赋给bt,让其在while中继续压入栈内
if __name__=="__main__":
    t = BinTNode(Assoc(5,'a'), BinTNode(Assoc(3,'b'),BinTNode(Assoc(2,'d')), BinTNode(Assoc(4,'e')) ), BinTNode(Assoc(8,'c')))
    dic = DictBinTree(t)
    for i in dic.print_all_values():
        print(i)

4、有时希望将字典中的键值对取出,只需修改上面函数中的一个语句即可。将yield bt.data.value换为yield bt.data.key, bt.data.value。

def entries(self):
        bt, s = self.root, SStack()
        while bt is not None or not s.is_empty():
            while bt is not None:
                s.push(bt)
                bt = bt.left
            bt = s.pop() 
            yield bt.data.key, bt.data.value
            bt = bt.right
            
def print_key_value(self):
    for k, v in self.entries():
        print(k, v)

5、二叉排序树的删除有三种情况

  • 如果待删除结点是叶结点:则将其直接删除即可。
  • 如果待删除结点仅有左子树或者右子树:则用左子树或者右子树直接上移,取代删除结点的位置即可。
  • 如果待删除结点既有左子树又有右子树:则用待删除结点的直接前驱后者直接后继来取代待删除结点,然后调整前驱或后继结点处的树的位置。

以下是一个例子:

(1)如果删除的是以下带有颜色的叶子结点的位置,直接将其删除即可。


(2)如果删除的结点只有左子树或者右子树,则将左子树或者右子树直接上移


(3)如果删除的结点105既只有左子树又有右子树,此处选择用其前驱结点104来取代删除结点,然后将103结点上移到104结点的位置。这里注意104结点不可能再有右子树,因为104结点已经是105结点的直接前驱,如果104结点存在右子树,则105的直接前驱将是其他的值。


def delete(self, key):
        #以下这一段用于找到待删除结点及其父结点的位置。
        del_position_father, del_position = None, self.root #del_position_father是待删除结点del_position的父结点
        while del_position is not None and del_position.data.key != key: #通过不断的比较,找到待删除结点的位置
            del_position_father = del_position
            if key < del_position.data.key:
                del_position = del_position.left
            else:
                del_position = del_position.right
            if del_position is None:
                print('There is no key')
                return
            
        if del_position.left is None: #如果待删除结点只有右子树
            if del_position_father is None: #如果待删除结点的父结点是空,则说明待删除结点是根结点
                self.root = del_position.right #则直接将根结点置空
            elif del_position is del_position_father.left: #如果待删除结点是其父结点的左结点
                del_position_father.left = del_position.right #***改变待删除结点父结点的左子树的指向
            else:
                del_position_father.right = del_position.right
            return
        
        #如果既有左子树又有右子树,或者仅有左子树时,都可以用直接前驱替换的删除结点的方式,只不过得到的二叉树与原理中说明的不一样,但是都满足要求。
        pre_node_father, pre_node = del_position, del_position.left
        while pre_node.right is not None: #找到待删除结点的左子树的最右结点,即为待删除结点的直接前驱
            pre_node_father = pre_node
            pre_node = pre_node.right
        del_position.data = pre_node.data #将前驱结点的data赋给删除结点即可,不需要改变其原来的连接方式

        if pre_node_father.left is pre_node:
            pre_node_father.left = pre_node.left
        if pre_node_father.right is pre_node:
            pre_node_father.right = pre_node.left

if __name__=="__main__":
    t = BinTNode(Assoc(5,'a'), BinTNode(Assoc(3,'b'),BinTNode(Assoc(2,'d')), BinTNode(Assoc(4,'e'),BinTNode(Assoc(3.5,'f')) ) ) )
    dic = DictBinTree(t)
    dic.delete(3)
    for i in dic.print_all_values():
        print(i)

6、最后单独定义一个可以生成一棵二叉排序树。它不是二叉排序树类的一部分

def build_dictBinTree(entries):
    dic = DictBinTree()
    for k,v in entries:
        dic.insert(k,v)
    return dic
                    
if __name__=="__main__":
    entries = [(5,'a'),(3,'b'),(2,'d'),(4,'e'),(3.5,'f')]
    dic = build_dictBinTree(entries)
    dic.print_key_value()

总结:

以上的查找,插入,删除等操作的效率,都与二叉树的高度有密切的关系。

如果一棵二叉树的结构良好,其高度与书中结点个数成对数关系O(log n)。

如果一棵二叉树的结构畸形,则查找的时间复杂度可能达到O(n)。这是最坏的情况。

如果build_dictBinTree(entries)中的entries序列按照递增或者递减顺序排列,则得到的就会是一棵高度等于结点个数的二叉排序树。


最后附上全部代码,省的一点点复制了。

class StackUnderflow(ValueError):   
    pass  
  
class SStack():  
    def __init__(self):  
        self.elems = []  
          
    def is_empty(self):  
        return self.elems == []  
      
    def top(self): #取得栈里最后压入的元素,但不删除  
        if self.elems == []:  
            raise StackUnderflow('in SStack.top()')  
        return self.elems[-1]  
      
    def push(self, elem):  
        self.elems.append(elem)  
          
    def pop(self):  
        if self.elems == []:  
            raise StackUnderflow('in SStack.pop()')  
        return self.elems.pop()

class Assoc: #定义一个关联类
    def __init__(self, key, value):
        self.key = key #键(关键码)
        self.value = value #值
    
    def __lt__(self, other):#Python解释器中遇到比较运算符<,会去找类里定义的__lt__方法(less than)
        return self.key < other.key
    
    def __le__(self, other): #(less than or equal to)
        return self.key < other.key or self.key == other.key
    
    def __str__(self):
        return 'Assoc({0},{1})'.format(self.key, self.value) #key和value分别替换前面{0},{1}的位置。

class BinTNode:  
    def __init__(self, dat, left = None, right = None):  
        self.data = dat  
        self.left = left  
        self.right = right 
    
class DictBinTree:  
    def __init__(self, root = None):  
        self.root = root
    
    def is_empty(self):
        return self.root is None
    
    def search(self, key):#检索是否存在关键码key
        bt = self.root
        while bt is not None:
            entry = bt.data
            if key < entry.key: 
                bt = bt.left
            elif key > entry.key:
                bt = bt.right
            else:
                return entry.value
        return None
        
    def insert(self, key, value):
        bt = self.root
        if bt is None:
            self.root = BinTNode(Assoc(key, value))
            return
        while True:
            entry = bt.data
            if key < entry.key: #如果小于当前关键码,转向左子树
                if bt.left is None: #如果左子树为空,就直接将数据插在这里
                    bt.left = BinTNode(Assoc(key,value))
                    return
                bt = bt.left
            elif key > entry.key:
                if bt.right is None:
                    bt.right = BinTNode(Assoc(key,value))
                    return
                bt = bt.right
            else:
                bt.data.value = value
                return
    def print_all_values(self):
        bt, s = self.root, SStack()
        while bt is not None or not s.is_empty(): #最开始时栈为空,但bt不为空;bt = bt.right可能为空,栈不为空;当两者都为空时,说明已经全部遍历完成了
            while bt is not None:
                s.push(bt)
                bt = bt.left
            bt = s.pop() #将栈顶元素弹出
            yield bt.data.key, bt.data.value
            bt = bt.right #将当前结点的右子结点赋给bt,让其在while中继续压入栈内
            
    def entries(self):
        bt, s = self.root, SStack()
        while bt is not None or not s.is_empty():
            while bt is not None:
                s.push(bt)
                bt = bt.left
            bt = s.pop() 
            yield bt.data.key, bt.data.value
            bt = bt.right
            
    def print_key_value(self):
        for k, v in self.entries():
            print(k, v)
            
    def delete(self, key):
        #以下这一段用于找到待删除结点及其父结点的位置。
        del_position_father, del_position = None, self.root #del_position_father是待删除结点del_position的父结点
        while del_position is not None and del_position.data.key != key: #通过不断的比较,找到待删除结点的位置
            del_position_father = del_position
            if key < del_position.data.key:
                del_position = del_position.left
            else:
                del_position = del_position.right
            if del_position is None:
                print('There is no key')
                return
            
        if del_position.left is None: #如果待删除结点只有右子树
            if del_position_father is None: #如果待删除结点的父结点是空,则说明待删除结点是根结点
                self.root = del_position.right #则直接将根结点置空
            elif del_position is del_position_father.left: #如果待删除结点是其父结点的左结点
                del_position_father.left = del_position.right #***改变待删除结点父结点的左子树的指向
            else:
                del_position_father.right = del_position.right
            return
        
        #如果既有左子树又有右子树,或者仅有左子树时,都可以用直接前驱替换的删除结点的方式,只不过得到的二叉树与原理中说明的不一样,但是都满足要求。
        pre_node_father, pre_node = del_position, del_position.left
        while pre_node.right is not None: #找到待删除结点的左子树的最右结点,即为待删除结点的直接前驱
            pre_node_father = pre_node
            pre_node = pre_node.right
        del_position.data = pre_node.data #将前驱结点的data赋给删除结点即可,不需要改变其原来的连接方式

        if pre_node_father.left is pre_node:
            pre_node_father.left = pre_node.left
        if pre_node_father.right is pre_node:
            pre_node_father.right = pre_node.left
        
            
def build_dictBinTree(entries):
    dic = DictBinTree()
    for k,v in entries:
        dic.insert(k,v)
    return dic
                    
if __name__=="__main__":
#    t = BinTNode(5, BinTNode(3,BinTNode(2)), BinTNode(8)) 
#    t = BinTNode(Assoc(5,'a'), BinTNode(Assoc(3,'b'),BinTNode(Assoc(2,'d')), BinTNode(Assoc(4,'e')) ), BinTNode(Assoc(8,'c')))
#    t = BinTNode(Assoc(5,'a'))
#    t = BinTNode(Assoc(5,'a'), BinTNode(Assoc(3,'b'),BinTNode(Assoc(2,'d')), BinTNode(Assoc(4,'e'),BinTNode(Assoc(3.5,'f')) ) ) )
#    dic = DictBinTree(t)
#    print(dic.search(3))
#    dic.insert(7,'e')      
#    dic.delete(5)
#    for i in dic.print_all_values():
#        print(i)
    entries = [(5,'a'),(3,'b'),(2,'d'),(4,'e'),(3.5,'f')]
    dic = build_dictBinTree(entries)
    dic.print_key_value()


猜你喜欢

转载自blog.csdn.net/qq_34840129/article/details/80721230