• 二叉树合集(五):二叉搜索树(图片详解,含基本操作)


    合集地址

    二叉树合集(一):二叉树基础(含四种遍历,图文详解)
    二叉树合集(二):霍夫曼树(图文详解)
    二叉树合集(三):线索二叉树(图文详解)
    二叉树合集(四):对称二叉树(递归和迭代实现)
    二叉树合集(五):二叉搜索树(图片详解,含基本操作)
    二叉树合集(六):高度平衡二叉树

    定义


    二叉搜索树(BST)是二叉树的一种特殊表示形式,它满足如下特性:

    1. 每个节点中的值必须大于(或等于)存储在其左侧子树中的任何值。
    2. 每个节点中的值必须小于(或等于)存储在其右子树中的任何值。

    下面是一个二叉搜索树的例子:

    img

    像普通的二叉树一样,我们可以按照前序、中序和后序来遍历一个二叉搜索树。 但是值得注意的是,对于二叉搜索树,我们可以通过中序遍历得到一个递增的有序序列。因此,中序遍历是二叉搜索树中最常用的遍历方法。

    二叉搜索树代码实现

    1、二叉搜索树的结点

    ​ 每个二叉搜索树的结点,包含关键字key、左孩子指针lchild、右孩子指针rchild以及父结点指针parent。在C++实现中,我们定义一个结点类BSTNode来表示一个结点,并初始化结点的关键字等于0,左右孩子指针和父结点指针为NIL。

    /* 二叉搜索树节点 */
    class BSTNode
    {
    private:
    	double key;					// 关键字
    	BSTNode *lchild;			// 左孩子
    	BSTNode *rchild;			// 右孩子
    	BSTNode *parent;			// 父节点
    	friend class BSTree;
    public:
    	BSTNode(double k = 0.0, BSTNode *l = NULL, BSTNode *r = NULL, BSTNode *p = NULL) :key(k), lchild(l), rchild(r), parent(p){}
    };
    

    二叉搜索树的基本操作

    二叉搜索树主要支持三个操作:搜索、插入和删除。

    在二叉搜索树中实现搜索操作


    根据BST的特性,对于每个节点:

    1. 如果目标值等于节点的值,则返回节点;
    2. 如果目标值小于节点的值,则继续在左子树中搜索;
    3. 如果目标值大于节点的值,则继续在右子树中搜索。

    我们一起来看一个例子:我们在上面的二叉搜索树中搜索目标值为 4 的节点。

    img

    class Solution {
        public TreeNode searchBST(TreeNode root, int val) {
          if (root == null || val == root.val) return root;
        return val < root.val ? searchBST(root.left, val) : searchBST(root.right, val);
    }
    }
    

    在二叉搜索树中实现插入操作

    二叉搜索树中的另一个常见操作是插入一个新节点。有许多不同的方法去插入新节点,我们只讨论一种使整体操作变化最小的经典方法。 它的主要思想是为目标节点找出合适的叶节点位置,然后将该节点作为叶节点插入。 因此,搜索将成为插入的起始。

    与搜索操作类似,对于每个节点,我们将:

    1. 根据节点值与目标节点值的关系,搜索左子树或右子树;
    2. 重复步骤 1 直到到达外部节点;
    3. 根据节点的值与目标节点的值的关系,将新节点添加为其左侧或右侧的子节点。

    这样,我们就可以添加一个新的节点并依旧维持二叉搜索树的性质。

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode() {}
     *     TreeNode(int val) { this.val = val; }
     *     TreeNode(int val, TreeNode left, TreeNode right) {
     *         this.val = val;
     *         this.left = left;
     *         this.right = right;
     *     }
     * }
     */
    class Solution {
        public TreeNode insertIntoBST(TreeNode root, int val) {
           TreeNode node= root;
            while(node!=null){
                if(val>node.val){
                    if(node.right==null){
                         node.right = new TreeNode(val);
                        return root;
                    }
                    else  node = node.right;
                    
                }
                if(val<node.val){
                    if(node.left==null){
                         node.left = new TreeNode(val);
                        return root;
                    }
                    else  node = node.left;     
                }
            }
            return new TreeNode(val);
        }
    }
    

    在二叉搜索树中实现删除操作


    删除要比我们前面提到过的两种操作复杂许多。有许多不同的删除节点的方法,这篇文章中,我们只讨论一种使整体操作变化最小的方法。我们的方案是用一个合适的子节点来替换要删除的目标节点。根据其子节点的个数,我们需考虑以下三种情况:

    1. 如果目标节点***没有子节点***,我们可以直接移除该目标节点。
      2. 如果目标节***只有一个子节点***,我们可以用其子节点作为替换。
      3. 如果目标节点***有两个子节点***,我们需要用其中序后继节点或者前驱节点来替换,再删除该目标节点。

    我们来看下面这几个例子,以帮助你理解删除操作的中心思想:

    例 1:目标节点没有子节点

    img

    例 2:目标节只有一个子节点

    img

    例 3:目标节点有两个子节点
    image-20200624233130700

    通过理解以上的示例,你应该可以独立实现删除操作了。

      public int successor(TreeNode root) {
        root = root.right;
        while (root.left != null) root = root.left;
        return root.val;
      }
    
      /*
      One step left and then always right
      */
      public int predecessor(TreeNode root) {
        root = root.left;
        while (root.right != null) root = root.right;
        return root.val;
      }
    
      public TreeNode deleteNode(TreeNode root, int key) {
        if (root == null) return null;
    
        // delete from the right subtree
        if (key > root.val) root.right = deleteNode(root.right, key);
        // delete from the left subtree
        else if (key < root.val) root.left = deleteNode(root.left, key);
        // delete the current node
        else {
          // the node is a leaf
          if (root.left == null && root.right == null) root = null;
          // the node is not a leaf and has a right child
          else if (root.right != null) {
            root.val = successor(root);
            root.right = deleteNode(root.right, root.val);
          }
          // the node is not a leaf, has no right child, and has a left child    
          else {
            root.val = predecessor(root);
            root.left = deleteNode(root.left, root.val);
          }
        }
        return root;
      }
    }
        
    

    小结

    我们已经介绍了二叉搜索树的相关特性,以及如何在二叉搜索树中实现一些基本操作,比如搜索、插入和删除。熟悉了这些基本概念之后,相信你已经能够成功运用它们来解决二叉搜索树问题。

    二叉搜索树的有优点是,即便在最坏的情况下,也允许你在O(h)的时间复杂度内执行所有的搜索、插入、删除操作。

    通常来说,如果你想有序地存储数据或者需要同时执行搜索、插入、删除等多步操作,二叉搜索树这个数据结构是一个很好的选择。

    一个例子


    问题描述:设计一个类,求一个数据流中第k大的数。

    一个很显而易见的解法是,先将数组降序排列好,然后返回数组中第k个数。

    但这个解法的缺点在于,为了在O(1)时间内执行搜索操作,每次插入一个新值都需要重新排列元素的位置。从而使得插入操作的解法平均时间复杂度变为O(N)。因此,算法总时间复杂度会变为O(N^2)

    鉴于我们同时需要插入和搜索操作,为什么不考虑使用一个二叉搜索树结构存储数据呢?

    我们知道,对于二叉搜索树的每个节点来说,它的左子树上所有结点的值均小于它的根结点的值,右子树上所有结点的值均大于它的根结点的值。

    换言之,对于二叉搜索树的每个节点来说,若其左子树共有m个节点,那么该节点是组成二叉搜索树的有序数组中第m + 1个值。

    操作的解法平均时间复杂度变为O(N)。因此,算法总时间复杂度会变为O(N^2)

    鉴于我们同时需要插入和搜索操作,为什么不考虑使用一个二叉搜索树结构存储数据呢?

    我们知道,对于二叉搜索树的每个节点来说,它的左子树上所有结点的值均小于它的根结点的值,右子树上所有结点的值均大于它的根结点的值。

    换言之,对于二叉搜索树的每个节点来说,若其左子树共有m个节点,那么该节点是组成二叉搜索树的有序数组中第m + 1个值。

    你可以先独立思考这个问题。请先尝试把多个节点存储到树中。你可能还需要在每个节点中放置一个计数器,以计算以此节点为根的子树中有多少个节点。

  • 相关阅读:
    分布式一致性模型
    ubuntu18.04 基于Hadoop3.1.2集群的Hbase2.0.6集群搭建
    ubuntu18.04 flink-1.9.0 Standalone集群搭建
    Idea 打印GC
    goroutine简介
    MESI缓存一致性协议
    Spark RDD 算子总结
    SparkStreaming 笔记
    Spark 内存管理
    Highcharts(数据分析图)
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13308036.html
Copyright © 2020-2023  润新知