zoukankan      html  css  js  c++  java
  • 二叉树合集(五):二叉搜索树(图片详解,含基本操作)

    合集地址

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

    定义


    二叉搜索树(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个值。

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

  • 相关阅读:
    Benelux Algorithm Programming Contest 2016 Preliminary K. Translators’ Dinner(思路)
    Benelux Algorithm Programming Contest 2016 Preliminary Target Practice
    Benelux Algorithm Programming Contest 2016 Preliminary I. Rock Band
    Benelux Algorithm Programming Contest 2016 Preliminary A. Block Game
    ICPC Northeastern European Regional Contest 2019 Apprentice Learning Trajectory
    ICPC Northeastern European Regional Contest 2019 Key Storage
    2018 ACM ICPC Asia Regional
    2018 ACM ICPC Asia Regional
    Mybatis入库出现异常后,如何捕捉异常
    优雅停止 SpringBoot 服务,拒绝 kill -9 暴力停止
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13308036.html
Copyright © 2011-2022 走看看