王清欢Randy 王清欢Randy
首页
  • 编程语言

    • C/C++ 学习笔记
    • Golang 学习笔记
  • 算法分析

    • LeetCode 刷题笔记
  • 操作系统

    • Linux 基础
    • Vim 实用技巧
    • Shell 脚本编程
    • GDB 学习笔记
  • 开发工具

    • Git 学习笔记
  • 分布式理论

    • 共识算法
    • 分布式事务
  • 数据库内核

    • PostgreSQL
    • Postgres-XL
  • hidb
  • pgproxy
  • 实用技巧
  • 学习方法
  • 资源分享
GitHub (opens new window)
首页
  • 编程语言

    • C/C++ 学习笔记
    • Golang 学习笔记
  • 算法分析

    • LeetCode 刷题笔记
  • 操作系统

    • Linux 基础
    • Vim 实用技巧
    • Shell 脚本编程
    • GDB 学习笔记
  • 开发工具

    • Git 学习笔记
  • 分布式理论

    • 共识算法
    • 分布式事务
  • 数据库内核

    • PostgreSQL
    • Postgres-XL
  • hidb
  • pgproxy
  • 实用技巧
  • 学习方法
  • 资源分享
GitHub (opens new window)
  • 基础算法

    • 双指针

      • 双指针基础
      • 碰撞指针
      • 快慢指针
      • 滑动窗口
    • 二分查找

      • 基础应用
      • 边界收缩
      • 局部有序
    • 排序算法

      • 01八大排序
      • 02快速排序
      • 03归并排序
      • 04桶排序
      • 05堆排序
    • 优先搜索

      • 01递归
      • 02网格结构深度优先搜索
      • 03树结构深度优先搜索
      • 04图结构深度优先搜索
      • 05网格结构广度优先搜索
      • 06树结构广度优先搜索
      • 07图结构广度优先搜索
    • 回溯算法

      • 01递归
      • 02 子集问题
      • 03 全排列问题
      • 04 组合问题
      • 05 回溯搜索问题
  • 基础数据结构

    • 线性表与哈希表

      • 01数组
      • 02栈和队列
      • 03单调栈
      • 04优先队列
      • 05双端队列
      • 06哈希表
      • 07多重集合
      • 08前缀和
      • 09数据结构设计
    • 字符串

      • 01字符串比较
      • 02回文字符串
      • 03字符串匹配
      • 04字符串算术表达式
    • 单链表

      • 01链表基础操作
      • 02链表遍历
    • 二叉树

      • 01二叉树的属性
      • 02二叉树的操作
      • 03层次遍历
      • 04前中后序遍历
      • 05二叉搜索树的属性
        • 05 二叉搜索树的属性
          • 538 把二叉搜索树转换为累加树
          • 235 二叉搜索树的最近公共祖先
          • 236 二叉树的最近公共祖先
          • 530 二叉搜索树的最小绝对差
          • 897 递增顺序搜索树
          • 653. 两数之和 IV - 输入 BST
      • 06二叉搜索树的操作
      • 07字典树
      • 08二叉搜索树BST
    • 图

      • 01二分图
      • 02拓扑排序
      • 03并查集
      • 04最小生成树
      • 05最短路径
  • 进阶算法

    • 贪心算法

      • 01跳跃游戏
      • 02分配问题
      • 03区间问题
    • 分治策略

    • 动态规划

      • 01一维动态规划
      • 02二维动态规划
      • 03分割型动态规划
      • 04子序列问题
      • 05背包问题
      • 06字符串编辑问题
      • 07股票交易问题
  • 其他内容

    • 数学问题

      • 01公倍数与公因数
      • 02质数问题
      • 03进制转换问题
      • 04数字字符串求和问题
      • 05众数问题
      • 06中位数问题
      • 07数字处理问题
      • 08随机数问题
    • 位运算

      • 01位运算基础
      • 02妙用异或运算
      • 03二进制特性
  • LeetCode刷题笔记
  • 基础数据结构
  • 二叉树
王清欢
2023-03-24
目录

05二叉搜索树的属性

# 05 二叉搜索树的属性

​ 二叉查找树 / 二叉搜索树(Binary Search Tree, BST)是一种特殊的二叉树:对于每个父节点,其左子节点的值小于等于父结点的值,其右子节点的值大于等于父结点的值。因此对于一个二叉查找树,我们可以在 O(nlogn) 的时间内查找一个值是否存在:从根节点开始,若当前节点的值大于查找值则向左下走,若当前节点的值小于查找值则向右下走。同时因为二叉查找树是有序的,对其中序遍历的结果即为排好序的数组。 ​ 一个二叉查找树的实现如下:

BST
template <class T>
class BST {
    struct Node {
        T data;
        Node* left;
        Node* right;
    };
    
    Node* root;
    
    Node* makeEmpty(Node* t) {
        if (t == NULL) return NULL;
        makeEmpty(t->left);
        makeEmpty(t->right);
        delete t;
        return NULL;
    }
    
    Node* insert(Node* t, T x) {
        if (t == NULL) {
            t = new Node;
            t->data = x;
            t->left = t->right = NULL;
        } else if (x < t->data) {
            t->left = insert(t->left, x);
        } else if (x > t->data) {
            t->right = insert(t->right, x);
        }
        return t;
    }
    
    Node* find(Node* t, T x) {
        if (t == NULL) return NULL;
        if (x < t->data) return find(t->left, x);
        if (x > t->data) return find(t->right, x);
        return t;
    }
    
    Node* findMin(Node* t) {
        if (t == NULL || t->left == NULL) return t;
        return findMin(t->left);
    }
    
    Node* findMax(Node* t) {
        if (t == NULL || t->right == NULL) return t;
        return findMax(t->right);
	}
    
    Node* remove(Node* t, T x) {
        ode* temp;
        if (t == NULL) return NULL;
        else if (x < t->data) t->left = remove(t->left, x);
        else if (x > t->data) t->right = remove(t->right, x);
        else if (t->left && t->right) {
            temp = findMin(t->right);
            t->data = temp->data;
            t->right = remove(t->right, t->data);
        } else {
            temp = t;
            if (t->left == NULL) t = t->right;
            else if (t->right == NULL) t = t->left;
            delete temp;
        }
        return t;
    }

public:
    BST(): root(NULL) {}
    ~BST() {
        root = makeEmpty(root);
    }
    void insert(T x) {
    	insert(root, x);
    }
    void remove(T x) {
    	remove(root, x);
    }
};  

​ 二叉搜索树的特点是节点左子树都小于节点值,节点右子树都大于节点值。所以,二叉搜索树的中序遍历序列就是一个递增序列,这一属性被广泛使用。

# 538 把二叉搜索树转换为累加树 (opens new window)

给出一个二叉搜索树,该树的节点值各不相同,请将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

输入一个二叉搜索树,输出一个累加树

输入:root = [3,2,4,1]
输出:[7,9,4,10]

解析:

​ 本题可以采用返序的中序遍历解决,中序遍历先遍历左节点、再遍历根节点、最后遍历右节点。

​ 而二叉搜索树的特点是节点左子树都小于节点值,节点右子树都大于节点值;累加树又是累加的是大于等于当前节点值的节点;所以只要从二叉搜索树最底层,最右侧开始遍历,就可以自下而上累加节点值。

​ 即当前节点值为其右子数最大累和加上自身节点值,而最大累和在右子树存在左子树的情况下出现在该左子数最底层最左侧节点累加值。

​ 根据累加树的节点值累加顺序:先右节点累和,然后根节点基于右节点值进行累和,最后左节点根据根节点值进行累和。所以本题采用返序中序遍历,先遍历右节点、再遍历根节点、最后遍历左节点;同时需要一个累和全局变量存储遍历当前节点时已经被遍历节点值之和。

​ 简而言之,按从大到小的顺序遍历二叉搜索树,每遍历一个节点,加上自身节点值来更新累和值,并将累和值作为其新值。

class Solution {
    // 存储遍历当前节点时已经被遍历节点值之和
    int sum = 0;
public:
    TreeNode* convertBST(TreeNode* root) {
        if(!root){
            return root;
        }
        // 先遍历右节点
        convertBST(root->right);
        // 在遍历根节点
        sum+=root->val;
        root->val = sum;
        // 最后遍历左节点
        convertBST(root->left);
        return root;
    }
};

# 235 二叉搜索树的最近公共祖先 (opens new window)

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先

输入一个二叉搜索树,输出一个节点表示两个指定节点的最近公共祖先

输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 0, q = 3 输出: 2 解释: 节点 0 和节点 3 的最近公共祖先是 2。

解析:

​ 二叉搜索树的特点是节点左子树都小于节点值,节点右子树都大于节点值。

​ 利用此特性可以仅使用一遍遍历就可以找出最近公共祖先:

​ 从根节点开始遍历,如果 p 和 q 的值均小于当前节点的值,说明 p 和 q 应该在当前节点的左子树,将当前节点移动到它的左子节点。

​ 如果 p 和 q 的值均大于当前节点的值,说明 p 和 q 应该在当前节点的右子树,将当前节点移动到它的右子节点。

​ 如果当前节点的值不满足上述两条要求,即 p 和 q 分别位于当前节点的左右子树,那么说明当前节点就是 p 和 q 最近公共祖先。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(!root){
            return root;
        }
        while(true){
            if(root->val < p->val && root->val < q->val){
                root = root->right;
            }else if(root->val > p->val && root->val > q->val){
                root = root->left;
            }else{
                break;
            }
        }
        return root;
    }
};

# 236 二叉树的最近公共祖先 (opens new window)

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先

输入一个二叉树,输出一个节点表示两个指定节点的最近公共祖先

输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 6, q = 7 输出:5 解释:节点 6 和节点 7 的最近公共祖先是节点 5

解析:

​ 二叉树不具备二叉搜索树的特点,所以要递归遍历查找。但是思想是一样的,判断是 p 和 q 是否分别位于当前节点的左右子树。

​ 采用深度优先搜索的遍历方法,递归遍历当前节点的左右子树:

​ 递归终止条件:当前节点为叶子节点或者刚好是 p 或者 q 节点。

​ 采用自上而下的递归遍历左右子树,返会结果时自下而上返回保证了返回的公共祖先是最近的。

​ 如果左右子树递归遍历返回结果都为真,那么说明 p 和 q 位于当前节点的左右子树,直接返回当前节点。如果左子树递归遍历结果为假,则说明 p 和 q 位于当前节点的右子树,返回右节点;如果右子树递归遍历结果为假,则说明 p 和 q 位于当前节点的左子树,返回左节点;如果左右子树递归遍历返回结果都为假,那么说明 p 和 q 不在以当前节点为根节点的子树中。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(!root || root == p || root == q){
            return root;
        }
        auto left = lowestCommonAncestor(root->left,p,q);
        auto right = lowestCommonAncestor(root->right,p,q);
        if(left && right){
            return root;
        }else if(!left){
            return right;
        }else if(!right){
            return left;
        }else{
            return nullptr;
        }
    }
};

# 530 二叉搜索树的最小绝对差 (opens new window)

给定一棵所有节点为非负值的二叉搜索树,请计算树中任意两节点的差的绝对值的最小值。

输入一个二叉搜索树,输出一个整数表示树中任意两节点的差的绝对值的最小值

输入:[1,null,3,null,null,2]

输出:1

解释:最小绝对差为 1,其中 2 和 1 的差的绝对值为 1(或者 2 和 3)。

解析:

​ 二叉搜索树的特点是节点左子树都小于节点值,节点右子树都大于节点值。那么使用二叉搜索树的中序遍历序列就是一个递增序列,利用此特点可以快速解决本题。

​ 计算任意两节点的差的绝对值的最小值,有两种思路,一种就是将二叉搜索树的中序遍历序列存储到一个数组中,然后遍历该数组计算最小差。另一种思路就是在中序遍历过程中记录前一个节点值,在遍历过程中计算两节点的差的绝对值的最小值。

class Solution {
public:
    int getMinimumDifference(TreeNode* root) {
        if(!root){
            return 0;
        }
        vector<int> inorder;
        stack<TreeNode*> s;
        TreeNode* cur = root;
        while(cur || !s.empty()){
            if(cur){
                s.push(cur);
                cur = cur->left;
            }else{
                cur = s.top();
                s.pop();
                inorder.push_back(cur->val);
                cur = cur->right;
            }
        }
        int ans = INT_MAX;
        for(int i=1;i<inorder.size();++i){
            ans = min(ans,inorder[i]-inorder[i-1]);
        }
        return ans;
    }
    
    // 一遍遍历方式:使用pre记录前一个节点值
    int getMinimumDifference(TreeNode* root) {
        if(!root){
            return 0;
        }
        int pre = -1, ans = INT_MAX;
        stack<TreeNode*> s;
        TreeNode* cur = root;
        while(cur || !s.empty()){
            if(cur){
                s.push(cur);
                cur = cur->left;
            }else{
                cur = s.top();
                s.pop();
                if(pre==-1){
                    pre = cur->val;
                }else{
                    ans = min(ans,cur->val-pre);
                    pre = cur->val;
                }
                cur = cur->right;
            }
        }
        return ans;
    }
};

# 897 递增顺序搜索树 (opens new window)

给定一棵二叉搜索树,请你 按中序遍历 将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。

输入一个二叉搜索树,输出一个只有右节点的递增顺序搜索树

输入:root = [5,3,6,2,4,null,8,1,null,null,null,7,9] 输出:[1,null,2,null,3,null,4,null,5,null,6,null,7,null,8,null,9]

解析:

​ 本题和530 二叉搜索树的最小绝对差 (opens new window)一样也可以从二叉搜索树的中序遍历是递增序列的思路解题。先设置一个空头节点,然后使用尾插法将遍历的每一个节点插入到上一节点的右节点。在中序遍历过程中将当前节点插入到上一节点的右节点,直到遍历完成。

​ 需要特别注意的是在插入节点的过程中要将当前节点的左节点置为空,因为二叉搜索树中左节点都小于当前节点,所以他们已经被插入到结果树中。如果不将左节点置为空,可能导致出现环路。

class Solution {
public:
    TreeNode* increasingBST(TreeNode* root) {
        if(!root){
            return root;
        }
        // 创建空头节点
        TreeNode* ans = new TreeNode();
        TreeNode* tail = ans;
        stack<TreeNode*> s;
        TreeNode* cur = root;
        while(cur || !s.empty()){
            if(cur){
                s.push(cur);
                cur = cur->left;
            }else{
                cur = s.top();
                s.pop();
                // 将当前节点作为前一节点的右节点
                tail->right = cur;
                tail = cur;
                // 将当前节点左节点置为空,避免环路
                cur->left = nullptr;
                cur = cur->right;
            }
        }
        return ans->right;
    }
};

# 653. 两数之和 IV - 输入 BST (opens new window)

给定一个二叉搜索树和一个目标结果 k,如果 BST 中存在两个元素且它们的和等于给定的目标结果,则返回 true。

输入一个二叉搜索树和目标值 k,输出一个布尔值表示是否存在两个元素之和为目标值k

输入: root = [5,3,6,2,4,null,7], k = 9
输出: true

解析:

​ 看到两数之和很容易想到使用哈希表,那么一种最为简单的思路就是遍历二叉搜索树并将所有元素存入哈希表中。然后,遍历哈希表寻找两数之和。

class Solution {
public:
    bool findTarget(TreeNode* root, int k) {
        unordered_set<int> hash;
        stack<TreeNode*> s;
        TreeNode* cur = root;
        while(cur || !s.empty()){
            if(cur){
                s.push(cur);
                cur = cur->left;
            }else{
                cur = s.top();
                s.pop();
                hash.insert(cur->val);
                cur = cur->right;
            }
        }
        
        for(const auto elem: hash){
            if(hash.find(k-elem) != hash.end() && k-elem != elem){
                return true;
            }
        }
        return false;
    }
};
上次更新: 2023/11/19, 12:55:48
04前中后序遍历
06二叉搜索树的操作

← 04前中后序遍历 06二叉搜索树的操作→

Theme by Vdoing | Copyright © 2023-2024 Wang Qinghuan | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式