数据结构与算法Python版 二叉查找树

文章目录


一、二叉查找树

二叉查找树Binary Search Tree

  • 性质 :比父节点小的key都出现在左子树 ,比父节点大的key都出现在右子树
  • 应用:用二叉查找树保存key-value,实现key的快速搜索。把它作为抽象数据类型-映射 Map的实现方案之一。

二叉查找树的实现

  • TreeNode类:定义二叉查找树中使用的节点,包括key和val等属性
  • BinarySearchTree类:实例化TreeNode类节点对象,构建二叉查找树。并利用二叉查找树特性,实现抽象数据类型-映射。
    • _put辅助方法:如果key比currentNode小,那么_put到左子树;但如果没有左子树,那么key就成为左子节点。反之,_put到右子树或成为右子节点(当没有右子树)。
    • _get辅助方法:如果currentNode为空,查找结束;如果查找的key等于currentNode的key,返回结果;如果查找的key小于currentNode的key,那么到左子树中查找;反之,到右子树中查找。
    • _delete辅助方法:从BST中删除一个节点,还要求仍然保持BST的性质,分以下情形:
      • 这个节点没有子节点:直接删除
      • 这个节点只有左子节点:将这个子节点上移,替换被删节点。然后考虑被删节点本身是其父节点的左?还是右子节点?被删节点本身就是根节点?
      • 这个节点只有右子节点:将这个子节点上移,替换被删节点。然后考虑被删节点本身是其父节点的左?还是右子节点?被删节点本身就是根节点?
      • 这个节点有2个子节点:在被删节点的右子树中,找最小的那个节点,来替换被删节点,这个节点称为"后继"。然后调整后继节点的位置。
python 复制代码
class TreeNode:
    def __init__(self, key, val, l_child=None, r_child=None, parent=None):
        self.key = key
        self.val = val
        self.l_child: TreeNode = l_child
        self.r_child: TreeNode = r_child
        self.parent: TreeNode = parent

    def __iter__(self):
        """实现for迭代:中序遍历的迭代"""
        if self:
            if self.has_l_child():
                for elem in self.l_child:
                    yield elem
            yield self.key
            if self.has_r_child():
                for elem in self.r_child:
                    yield elem

    def has_l_child(self):
        # return True if self.l_child else False
        return False if self.l_child == None else True

    def has_r_child(self):
        return True if self.r_child else False

    def is_l_child(self):
        return self.parent and self.parent.l_child == self

    def is_r_child(self):
        return self.parent and self.parent.r_child == self

    def is_root(self):
        return not self.parent

    def is_leaf(self):
        return not (self.l_child or self.r_child)

    def has_any_children(self):
        return self.l_child or self.r_child

    def has_both_children(self):
        return self.l_child and self.r_child

    def replace_node_data(self, key, val, lc, rc):
        self.key = key
        self.val = val
        self.l_child = lc
        self.r_child = rc

    def find_successor(self):
        """寻找后继节点"""
        succ = None
        if self.has_r_child():
            succ = self.r_child.find_min()
        return succ

    def find_min(self):
        """寻找当前节点下最小节点:一直向左子节点寻找"""
        current = self
        while current.has_l_child():
            current = current.l_child
        return current

    def splice_out(self):
        """将后继节点移出"""
        if self.is_leaf():  # 如果当前是叶子节点
            if self.is_l_child():
                self.parent.l_child = None
            else:
                self.parent.r_child = None
        elif self.has_r_child:  # 如果当前节点有右子节点
            if self.is_l_child():
                self.r_child.parent = self.parent
                self.parent.l_child = self.r_child
            else:
                self.r_child.parent = self.parent
                self.parent.r_child = self.r_child

        # 后继节点不会有左子节点。因为后继节点,是被删节点的右子树中最小的。


class BinarySearchTree:
    def __init__(self):
        self.root: TreeNode = None
        self.size = 0

    def __len__(self):
        """支持调用len()获得数据项的数量"""
        return self.size

    def __iter__(self):
        """实现for迭代"""
        return self.root.__iter__()

    def __setitem__(self, key, val):
        """支持[]形式赋值"""
        self.put(key, val)

    def __getitem__(self, key):
        """支持[]索引获取值"""
        return self.get(key)

    def __contains__(self, key):
        """支持归属判断运算符in"""
        return True if self._get(key, self.root) else False

    def __delitem__(self, key):
        """实现del语法删除"""
        return self.delete(key)

    def put(self, key, val):
        """插入key val,构造BST"""
        # 如果树不为空时,调用_put()插入到BST;否则成为BST的根
        if self.root:
            self._put(key, val, self.root)
        else:
            self.root = TreeNode(key, val)

        self.size += 1

    def _put(self, key, val, cur_node: TreeNode):
        # 如果key比currentNode小,那么_put到左子树;否则,_put到左子树。
        # print(f"-----{cur_node.key,cur_node.val}--------")
        if key < cur_node.key:
            if cur_node.has_l_child():
                self._put(key, val, cur_node.l_child)
            else:
                cur_node.l_child = TreeNode(key, val, parent=cur_node)
        else:
            if cur_node.has_r_child():
                self._put(key, val, cur_node.r_child)
            else:
                cur_node.r_child = TreeNode(key, val, parent=cur_node)

    def get(self, key):
        if self.root:
            res_node = self._get(key, self.root)
            return res_node.val if res_node else None
        else:
            return None

    def _get(self, key, cur_node: TreeNode) -> TreeNode:
        if cur_node == None:
            return
        # 如果查找的key等于当前节点的key,返回结果;
        # 如果查找的key小于当前节点的key,在左孩子节点查找;否则,在右孩子查找
        if key == cur_node.key:
            return cur_node
        elif key < cur_node.key:
            return self._get(key, cur_node.l_child)
        else:
            return self._get(key, cur_node.r_child)

    def delete(self, key):
        if self.root == None:
            return False
        if self.size == 1:
            if self.root.key == key:
                self.root = None
                self.size = 0
                return True
            else:
                return False

        # 找节点,如果找不到返回False,否则删除节点
        res_node = self._get(key, self.root)
        if res_node == None:
            return False
        else:
            self._delete(res_node)
            self.size -= 1

    def _delete(self, cur_node: TreeNode):
        # 如果当前节点是叶子节点
        if cur_node.is_leaf():
            if cur_node.is_l_child():
                cur_node.parent.l_child = None
            else:
                cur_node.parent.r_child = None
            return

        # 如果当前节点只有左子节点
        if cur_node.has_l_child() and not cur_node.has_r_child():
            if cur_node.is_l_child():  # 如果当前节点是父节点的左节点
                cur_node.l_child.parent = cur_node.parent
                cur_node.parent.l_child = cur_node.l_child
            elif cur_node.is_r_child():  # 如果当前节点是父节点的右节点
                cur_node.l_child.parent = cur_node.parent
                cur_node.parent.r_child = cur_node.l_child
            else:  # 如果当前节点是根节点
                cur_node.l_child.parent = None
                self.root = cur_node.l_child
            return

        # 如果当前节点只有右子节点
        if cur_node.has_r_child() and not cur_node.has_l_child():
            if cur_node.is_l_child():
                cur_node.r_child.parent = cur_node.parent
                cur_node.parent.l_child = cur_node.r_child
            elif cur_node.is_r_child():
                cur_node.r_child.parent = cur_node.parent
                cur_node.parent.r_child = cur_node.r_child
            else:
                cur_node.r_child.parent = None
                self.root = cur_node.r_child
            return

        # 如果当前节点有左右两个子节点
        if cur_node.has_both_children():
            succ = cur_node.find_successor()
            succ.splice_out()
            cur_node.key = succ.key
            cur_node.val = succ.val

二、二叉查找树测试

  • 依次将下面6个节点插入到二叉查找树,查看二叉查当前数据项数量
  • 在二叉查找树上查找key
  • 在二叉查找树上删除key,再进行查找
python 复制代码
bst = BinarySearchTree()
bst[54] = "cat"
bst[26] = "dog"
bst[93] = "lion"
bst[17] = "tiger"
bst[77] = "bird"
bst[31] = "cow"
print(len(bst))
print(bst[20])
print(bst[26])
del bst[26]
print(bst[26])
print(bst[93])
print(len(bst))

### 输出结果
6
None
dog 
None
lion
5

三、二叉查找树算法分析

  • 查找性能决定因素在于二叉搜索树的高度(最大层次),
  • 如果key的列表是随机分布的话,那么大于和小于根节点key的键值大致相等。此时,n个节点的树高度是log2n,这样的树称为平衡树
  • 受key列表分布和插入顺序影响,put方法最优性能为O(log2n),最差性能为O(n)

您正在阅读的是《数据结构与算法Python版》专栏!关注不迷路~

相关推荐
这儿有一堆花38 分钟前
比特币:固若金汤的数字堡垒与它的四道防线
算法·区块链·哈希算法
客卿12343 分钟前
力扣100-移动0
算法·leetcode·职场和发展
多吃蔬菜!!!4 小时前
排序算法C语言实现
数据结构
零叹4 小时前
篇章六 数据结构——链表(二)
数据结构·链表·linkedlist
CM莫问4 小时前
<论文>(微软)WINA:用于加速大语言模型推理的权重感知神经元激活
人工智能·算法·语言模型·自然语言处理·大模型·推理加速
程序员的世界你不懂5 小时前
Appium+python自动化(八)- 认识Appium- 下章
python·appium·自动化
恸流失5 小时前
DJango项目
后端·python·django
计信金边罗6 小时前
是否存在路径(FIFOBB算法)
算法·蓝桥杯·图论
MZWeiei6 小时前
KMP 算法中 next 数组的构建函数 get_next
算法·kmp
Julyyyyyyyyyyy6 小时前
【软件测试】web自动化:Pycharm+Selenium+Firefox(一)
python·selenium·pycharm·自动化