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

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

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

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

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

下图是数据集[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)  # keyvalue分别替换前面{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))

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

  • 如果二叉树为空,直接将数据插在根结点上
  • 如果小于根结点的值,则转向左子树,若左子树为空,则将数据插在这里。
  • 如果大于根结点的值,则转向右子树,若右子树为空,则将数据插在这里。
  • 如果等于结点的值,则将结点的关联值直接替换掉。

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')

三、下面定义一个可以将字典中所有数据值都打印出来的迭代器方法,以便于可以利用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)

四、有时希望将字典中的键值对取出,只需修改上面函数中的一个语句即可。将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)

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

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

以下是一个例子:

(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)

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

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)  # keyvalue分别替换前面{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()


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值