数据结构与算法python—12.二叉搜索树及python实现与leetcode总结

文章目录

    • 一、什么是二叉搜索树?
    • 二、二叉搜索树的实现
      • 1.二叉搜索树的创建
      • 2.二叉搜索树添加元素
      • 3.二叉搜索树查找元素
      • 4.二叉搜索树的遍历
      • 5.二叉搜索树删除元素
        • 5.1要删除的节点没有孩子
        • 5.2 要删除的节点只有一个孩子
        • 5.3 要删除的节点有两个孩子
    • 三、二叉搜索树leetcode题目总结
      • 1.基本操作类题目
      • 2.基于中序遍历类题目

一、什么是二叉搜索树?

  二叉查找树(Binary Search Tree),(又:二叉搜索树,二叉排序树)它或者是一棵空树,或者是具有下列性质的二叉树:

  1. 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  2. 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  3. 它的左、右子树也分别为二叉排序树。

二、二叉搜索树的实现

1.二叉搜索树的创建

节点创建:

class TreeNode:
    def __init__(self, key, value, left=None, right=None, parent=None):
        # 初始化key,value,左孩子、右孩子与父亲
        self.key = key
        self.value = value
        self.left_child = left
        self.right_child = right
        self.parent = parent
    # 判断是否有左孩子
    def has_left_child(self):
        return self.left_child
    # 判断是否有右孩子
    def has_right_child(self):
        return self.right_child
    # 判断是否是左孩子
    def is_left_child(self):
        return self.parent and self.left_child.parent == self
    # 判断是否是右孩子
    def is_right_child(self):
        return self.parent and self.right_child.parent == self
    # 判断是否是叶子节点
    def is_leaf(self):
        return not (self.left_child or self.right_child)
    # 判断是否是根节点
    def is_root(self):
        return not self.parent
    # 判断是否有其他子孩子
    def has_any_child(self):
        return self.right_child or self.right_child
    # 判断是否左孩子右孩子都存在
    def has_both_child(self):
        return self.left_child and self.right_child
    # 将初始化的值重新赋值
    def replace_data(self, key, value, lc, rc):
        self.key = key
        self.value = value
        self.left_child = lc
        self.right_child = rc

        if self.has_left_child():
            self.left_child.parent = self
            
        if self.has_right_child():
            self.right_child.parent = self

二叉搜索树创建:

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

    def length(self):
        return self.size

    def __len__(self):
        return self.length()

    def __iter__(self):
        return self.root.__iter__()

2.二叉搜索树添加元素

思路框架:

从树的根节点开始,通过搜索二叉树来比较新的键值和当前节点的键值,

  • 如果新的键值小于当前节点,则搜索左子树。
  • 如果新的关键大于当前节点,则搜索右子树。

当搜索不到左(或右)子树,我们在树中所处的位置就是设置新节点的位置。向树中添加一个节点,创建一个新的TreeNode对象并在这个点的上一个节点中插入这个对象。

    # 二叉搜索树添加新元素
    def put(self, key, value):
        # 判断根节点是否为空
        # 如果不为空,则递归添加新元素
        if self.root:
            self._put(key, value, self.root)
        # 如果为空,则创建树节点,并设置该树节点为根节点
        else:
            self.root = TreeNode(key, value)
        self.size = self.size + 1

    def _put(self, key, value, current_node):
        # 如果新的键值小于当前节点,则搜索左子树
        if key < current_node.key:
            # 如果有左子树,则递归创建
            if current_node.has_left_child():
                self._put(key, value, current_node.left_child)
            # 如果没有左子树,我们在树中所处的位置就是设置新节点的位置
            else:
                current_node.left_child = TreeNode(key, value, parent=current_node)
        # 如果新的关键大于当前节点,则搜索右子树
        elif key > current_node.key:
            # 如果有右子树,则递归创建
            if current_node.has_right_child():
                self._put(key, value, current_node.right_child)
            # 如果没有右子树,我们在树中所处的位置就是设置新节点的位置
            else:
                current_node.right_child = TreeNode(key, value, parent=current_node)
    # 实现了通过字典查询的功能
    def __setitem__(self, key, value):
        self.put(key, value)

3.二叉搜索树查找元素

思维框架:

一旦树被构造,接下来的任务就是为一个给定的键值实现检索。
get方法比 put方法更容易因为它只需递归搜索树,直到发现不匹配的叶节点或找到一个匹配的键值。
当找到一个匹配的键值后,就会返回节点中的值。

    def get(self, key):
        # 判断根节点是否为空
        if self.root:
            # 如果根节点不为空,则递归的调用内置的_get方法
            res = self._get(key, self.root)
            # 如果key存在,则返回节点值
            if res:
                return res.value
            # 否则,返回None
            else:
                return None
        # 如果根节点为空,则返回None
        else:
            return None

    def _get(self, key, current_node):
        # 如果当前节点不存在,则返回None
        if not current_node:
            return None
        # 如果当前节点key == key,则返回当前节点
        elif current_node.key == key:
            return current_node
        # 如果key < 当前节点的key,则搜索当前节点的左子树
        elif key < current_node.key:
            return self._get(key, current_node.left_child)
        # 如果key > 当前节点的key,则搜索当前节点的右子树
        else:
            return self._get(key, current_node.right_child)

    # 实现了通过字典查询的功能
    def __getitem__(self, key):
        return self.get(key)
    # 实现了判断键是否存在于字典中,如果键在字典dict里返回true,否则返回false
    def __contains__(self, key):
        return True if self.get(key) else False

4.二叉搜索树的遍历

   # 前序遍历
    def preorder(self,root):
        # 边界
        if root is None:
            return []
        result = [root.value]
        # 递归调用
        left_item = self.preorder(root.left_child)
        right_item = self.preorder(root.right_child)
        return result + left_item + right_item

    # 中序遍历
    def inorder(self, root):
        if root is None:
            return []
        result = [root.value]
        # 递归调用
        left_item = self.inorder(root.left_child)
        right_item = self.inorder(root.right_child)
        return left_item + result + right_item

    # 后序遍历
    def postorder(self, root):
        if root is None:
            return []
        result = [root.value]
        # 递归调用
        left_item = self.postorder(root.left_child)
        right_item = self.postorder(root.right_child)
        return left_item + right_item + result

    # 广度优先遍历/层次遍历
    def traverse(self):
        if self.root is None:
            return None
        # 定义一个队列来存放二叉树
        q = [self.root]
        res = [self.root.value]
        while q != []:
            pop_node = q.pop(0)
            if pop_node.left_child != None:
                q.append(pop_node.left_child)
                res.append(pop_node.left_child.value)

            if pop_node.right_child != None:
                q.append(pop_node.right_child)
                res.append(pop_node.right_child.value)
        return res

    # 简单遍历
    # 使用迭代器,每次调用迭代器时,一个迭代器只返回一个节点
    # python提供了一个创建迭代器非常强大的方法—yield,它的功能是创建一个可迭代的对象,因此,也叫作生成器。
    # yield跟return(返回值给调用者)类似,只不过需要额外的步骤暂停函数的执行,以便下次调用函数时,为继续执行做准备
    # 重写了__iter__魔法方法,借助for _ in 实现了递归
    def _iter__(self):
        if self:
            if self.has_left_child():
                for elem in self.left_child:
                    yield elem

            yield self.key

            if self.has_right_child():
                for elem in self.right_child:
                    yield elem

5.二叉搜索树删除元素

思维框架:

删除一个键值。首要任务是要找到搜索树中要删除的节点。

  • 如果树有一个以上的节点,我们使用_get方法找到需要删除的节点。
  • 如果树只有一个节点,这意味着我们要删除树的根,但是我们仍然要检查根的键值是否与要删除的键值匹配。

在以上两种情况下,如果没有找到该键,del操作就会报错。

    def delete(self, key):
        # 如果树有一个以上的节点,我们使用_get方法找到需要删除的节点
        if self.size > 1:
            node_to_remove = self.get(key)
            # 如果找到了,则删除
            if node_to_remove:
                self._remove(node_to_remove)
                self.size = self.size - 1
            # 如果没有找到,则直接弹出错误!
            else:
                raise KeyError(' Error! The key isnot on the tree.')
        # 如果树只有一个节点,并且根的键值与要删除的键值匹配,则直接删除根节点
        if self.size == 1 and self.root.key == key:
            self.root = None
            self.size = self.size - 1
        # size = 0,直接弹出错误!
        else:
            raise KeyError(' Error! The key is not on the tree.')

remove操作我们要考虑三种情况:

  1. 要删除的节点没有孩子.
  2. 要删除的节点只有一个孩子.
  3. 要删除的节点有两个孩子.

5.1要删除的节点没有孩子

if current_node.is_leaf():
    if current_node == current_node.parent.left_child:
    	current_node.parent.left_child = None
else:
    current_node.parent.right_child = None

5.2 要删除的节点只有一个孩子

  1. 如果当前节点是左子树,那我们只需要更新左子树的引用指向当前节点的父节点,然后更新父节点的左子树引用指向当前节点的左子树。
  2. 如果当前节点是右子树,那我们只需要更新右子树的引用指向当前节点的父节点,然后更新父节点的右子树引用指向当前节点的左子树。
  3. 如果当前节点没有父节点,它一定是根。这种情况下,我们只需把键替换为左子树和右子树里的数据。
if current_node.has_left_child():
    if current_node.is_left_child():
        current_node.left_child.parent = current_node.parent
        current_node.parent.left_child = current_node.left_child
    elif current_node.is_right_child():
        current_node.left_child.parent = current_node.parent
        current_node.parent.right_child = current_node.left_child
    else:
        current_node.replace_data(current_node.key, current_node.value, current_node.left_child,
                                  current_node.right_child)
else:
    if current_node.is_left_child():
        current_node.left_child.parent = current_node.parent
        current_node.parent.left_child = current_node.right_child
    elif current_node.is_right_child():
        current_node.right_child.parent = current_node.parent
        current_node.parent.right_child = current_node.right_child
        current_node.replace_data(current_node.key, current_node.valuecurrent_node.left_child, current_node.right_child)

5.3 要删除的节点有两个孩子

  我们需要寻找一个节点,用来替换这个将要删除的节点,我们需要的这个节点能够保持现有二叉搜索树的左、右子树的关系。这个节点在树中具有第二大的键值。我们称这个节点为后继节点。

	elif current_node. has_both_child():
	    # 找到后序节点
	    succ = current_node.find_successor()
	    # 删除后继节点
	    succ.splice_out()
	    current_node.key = succ.key
	    current_node.value = succ.value

当寻找后继节点时需要考虑三种情况:

  1. 如果节点有右子节点,那么后继节点是右子树中最小的关键节点。
  2. 如果节点没有右子节点,是其父节点的左子树,那么父节点是后继节点。
  3. 如果节点是其父节点的右子节点,而本身无右子节点,那么这个节点的后继节点是其父节点的后继节点,但不包括这个节点。
# 定义找到后继节点的函数
        def find_successor(self):
            succ = None
            if self.has_right_child():
                succ = self.find_min()
            else:
                if self.parent:
                    if self.is_left_child():
                        succ = self.parent
                    else:
                        self.right_child = None
                        succ = self.parent.find_successor()
                        self.right_child = self
            return succ
		# 定义寻找子树中最小节点的函数
        def find_min(self):
            current = self
            while self.has_left_child():
                current = self.left_childreturn
            return current

        # 定义删除后继节点的函数
        def splice_out(self):
            if self.is_leaf():
                if self.is_left_child():
                    self.parent.left_child = None
                else:
                    self.parent.right_child = None
            elif self.has_any_child():
                if self.has_left_child():
                    if self.is_left_child():
                        self.parent.left_child = self.left_child
                    else:
                        self.parent.right_child = self.left_child
                        self.left_child.parent = self.parent
                else:
                    if self.is_right_child():
                        self.parent.left_child = self.right_child
                    else:
                        self.parent.right_child = self.right_child
                    self.right_child.parent = self.parent
        def _remove(self, current_node):
            if current_node.is_leaf():
                if current_node == current_node.parent.left_child:
                    current_node.parent.left_child = None
            else:
                current_node.parent.right_child = None

            if current_node.has_left_child():
                if current_node.is_left_child():
                    current_node.left_child.parent = current_node.parent
                    current_node.parent.left_child = current_node.left_child
                elif current_node.is_right_child():
                    current_node.left_child.parent = current_node.parent
                    current_node.parent.right_child = current_node.left_child
                else:
                    current_node.replace_data(current_node.key, current_node.value, current_node.left_child,
                                              current_node.right_child)
            else:
                if current_node.is_left_child():
                    current_node.left_child.parent = current_node.parent
                    current_node.parent.left_child = current_node.right_child
                elif current_node.is_right_child():
                    current_node.right_child.parent = current_node.parent
                    current_node.parent.right_child = current_node.right_child
                    current_node.replace_data(current_node.key, current_node.valuecurrent_node.left_child,
                                              current_node.right_child)
                elif current_node.has_both_child():
                    # 找到后序节点
                    succ = current_node.find_successor()
                    # 删除后继节点
                    succ.splice_out()
                    current_node.key = succ.key
                    current_node.value = succ.value

如果对您有帮助,麻烦点赞关注,这真的对我很重要!!!如果需要互关,请评论或者私信!
在这里插入图片描述


三、二叉搜索树leetcode题目总结

1.基本操作类题目

235. 二叉搜索树的最近公共祖先
450. 删除二叉搜索树中的节点
669. 修剪二叉搜索树
700. 二叉搜索树中的搜索
701. 二叉搜索树中的插入操作

2.基于中序遍历类题目

98. 验证二叉搜索树
99. 恢复二叉搜索树
230. 二叉搜索树中第K小的元素
501. 二叉搜索树中的众数


如果对您有帮助,麻烦点赞关注,这真的对我很重要!!!如果需要互关,请评论或者私信!
在这里插入图片描述


你可能感兴趣的:(#,数据结构与算法,数据结构与算法,python,二叉搜索树)