二叉搜索树

一、BST 基本概念

二叉搜索树(Binary Search Tree,BST)是一种有序二叉树,核心性质:

  1. 若左子树不为空,左子树所有节点值 小于 根节点值

  2. 若右子树不为空,右子树所有节点值 大于 根节点值

  3. 左右子树也都是二叉搜索树

  4. 无重复节点值

中序遍历 BST 会得到升序序列,这是 BST 最核心的特性。

二、BST 节点结构

复制代码
template<class K>
struct BSTreeNode {
    BSTreeNode<K>* _left;   // 左孩子指针
    BSTreeNode<K>* _right;  // 右孩子指针
    K _key;                 // 节点值
​
    // 构造函数:初始化节点,左右孩子默认为空
    BSTreeNode(const K& key)
        :_left(nullptr)
        ,_right(nullptr)
        ,_key(key)
    {}
};
  • 节点包含三个成员:左指针、右指针、节点值

  • 构造函数用于创建新节点,自动初始化指针为空,避免野指针

三、BST 类核心:四大默认成员函数

包含默认构造、拷贝构造、赋值重载、析构函数,统一管理树的创建、拷贝、销毁。

复制代码
template<class K>
class BSTree {
    typedef BSTreeNode<K> Node;
public:
    // 1. 默认构造函数:初始化空树
    BSTree()
        :_root(nullptr)
    {}
​
    // 2. 拷贝构造函数:深拷贝整棵树
    BSTree(const BSTree<K>& t) {
        _root = Copy(t._root);
    }
​
    // 3. 析构函数:释放所有节点内存
    ~BSTree() {
        Destroy(_root);
    }
​
    // 4. 赋值运算符重载:现代写法(交换指针,简洁安全)
    BSTree<K>& operator=(BSTree<K> t) {
        swap(_root, t._root);
        return *this;
    }
​
private:
    // 递归拷贝树(深拷贝)
    Node* Copy(Node* root) {
        if (root == nullptr) return nullptr;
        Node* copyroot = new Node(root->_key);        // 拷贝当前节点
        copyroot->_left = Copy(root->_left);         // 递归拷贝左子树
        copyroot->_right = Copy(root->_right);       // 递归拷贝右子树
        return copyroot;
    }
​
    // 递归销毁树(释放内存+置空指针)
    void Destroy(Node*& root) {
        if (root == nullptr) return;
        Destroy(root->_left);    // 销毁左子树
        Destroy(root->_right);   // 销毁右子树
        delete root;             // 释放当前节点
        root = nullptr;          // 指针置空,防止野指针
    }
​
    Node* _root;  // 树根节点
};

核心细节解析

  1. 深拷贝:拷贝构造不能直接拷贝指针,必须递归创建新节点,避免多个对象共用同一块内存

  2. 赋值重载现代写法:传值参数会自动调用拷贝构造生成临时对象,交换指针后,临时对象销毁自动释放原内存,简洁且异常安全

  3. Destroy 中的引用 Node*& root

    • 作用:不仅能释放节点内存,还能修改实参指针本身,将指针置空

    • 意义:防止函数结束后,原指针变成野指针,这是内存管理的关键细节

四、BST 核心功能(递归 + 非递归 对照)

1. 查找功能

作用:判断树中是否存在目标值,利用 BST 有序性,每次比较排除一半子树。

复制代码
// 非递归查找
bool Find(const K& key) {
    Node* cur = _root;
    while (cur) {
        if (cur->_key < key)    // 目标值更大,去右子树
            cur = cur->_right;
        else if (cur->_key > key)  // 目标值更小,去左子树
            cur = cur->_left;
        else                    // 找到目标
            return true;
    }
    return false;               // 遍历到空,未找到
}
​
// 递归查找
bool FindR(const K& key) {
    return _FindR(_root, key);
}
​
// 递归辅助函数
bool _FindR(Node* root, const K& key) {
    if (root == nullptr) return false;  // 递归终止:空树
    if (root->_key < key)
        return _FindR(root->_right, key);
    else if (root->_key > key)
        return _FindR(root->_left, key);
    else
        return true;  // 找到目标
}

2. 插入功能

作用:向树中添加新节点,保证 BST 性质,不允许重复值。

复制代码
// 非递归插入
bool Insert(const K& key) {
    if (_root == nullptr) {  // 空树,直接创建根节点
        _root = new Node(key);
        return true;
    }
​
    Node* parent = nullptr;
    Node* cur = _root;
​
    // 找到插入位置的父节点
    while (cur) {
        if (cur->_key < key) {
            parent = cur;
            cur = cur->_right;
        } else if (cur->_key > key) {
            parent = cur;
            cur = cur->_left;
        } else {  // 存在重复值,插入失败
            return false;
        }
    }
​
    // 创建新节点,链接到父节点
    cur = new Node(key);
    if (parent->_key < key)
        parent->_right = cur;
    else
        parent->_left = cur;
    return true;
}
​
// 递归插入
bool InsertR(const K& key) {
    return _InsertR(_root, key);
}
​
// 递归辅助函数(核心:Node*& root)
bool _InsertR(Node*& root, const K& key) {
    if (root == nullptr) {
        root = new Node(key);  // 空位置直接创建节点
        return true;
    }
​
    if (root->_key < key)
        return _InsertR(root->_right, key);
    else if (root->_key > key)
        return _InsertR(root->_left, key);
    else
        return false;  // 重复值
}
关键语法:_InsertRNode*& root 的妙用
  • 递归函数参数是指针的引用,直接操作上层节点的指针(根节点/左/右孩子指针)

  • 无需单独记录父节点,找到空位置时,直接修改原指针指向新节点

  • 代码极简,逻辑清晰,完美适配递归场景,是递归实现 BST 的点睛之笔

3. 删除功能

删除分三种情况:

  1. 待删除节点左子树为空:父节点直接指向其右孩子

  2. 待删除节点右子树为空:父节点直接指向其左孩子

  3. 待删除节点左右子树都不为空:替换法(用左子树最大值/右子树最小值替换当前节点,再删除替换节点)

复制代码
// 非递归删除
bool Erase(const K& key) {
    Node* parent = nullptr;
    Node* cur = _root;
​
    // 找到待删除节点
    while (cur) {
        if (cur->_key < key) {
            parent = cur;
            cur = cur->_right;
        } else if (cur->_key > key) {
            parent = cur;
            cur = cur->_left;
        } else {  // 找到目标节点
            // 情况1:左子树为空
            if (cur->_left == nullptr) {
                if (cur == _root)
                    _root = cur->_right;
                else
                    parent->_right == cur ? parent->_right = cur->_right : parent->_left = cur->_right;
                delete cur;
            }
            // 情况2:右子树为空
            else if (cur->_right == nullptr) {
                if (cur == _root)
                    _root = cur->_left;
                else
                    parent->_right == cur ? parent->_right = cur->_left : parent->_left = cur->_left;
                delete cur;
            }
            // 情况3:左右子树都不为空(替换法:找右子树最小值)
            else {
                Node* minParent = cur;
                Node* minRight = cur->_right;
                // 找到右子树最左节点(最小值)
                while (minRight->_left) {
                    minParent = minRight;
                    minRight = minRight->_left;
                }
                // 替换值
                cur->_key = minRight->_key;
                // 删除替换节点
                minParent->_left == minRight ? minParent->_left = minRight->_right : minParent->_right = minRight->_right;
                delete minRight;
            }
            return true;
        }
    }
    return false;  // 未找到目标值
}
​
// 递归删除
bool EraseR(const K& key) {
    return _EraseR(_root, key);
}
​
// 递归辅助函数(核心:Node*& root)
bool _EraseR(Node*& root, const K& key) {
    if (root == nullptr) return false;
​
    // 查找目标节点
    if (root->_key < key)
        return _EraseR(root->_right, key);
    else if (root->_key > key)
        return _EraseR(root->_left, key);
    // 找到待删除节点
    else {
        Node* del = root;
        // 情况1+2:左空/右空,直接修改指针指向孩子
        if (root->_left == nullptr)
            root = root->_right;
        else if (root->_right == nullptr)
            root = root->_left;
        // 情况3:左右都不为空,替换法
        else {
            // 找左子树最大值(最右节点)
            Node* leftMax = root->_left;
            while (leftMax->_right)
                leftMax = leftMax->_right;
            // 交换值
            swap(root->_key, leftMax->_key);
            // 递归删除替换后的节点
            return _EraseR(root->_left, root->_key);
        }
        delete del;
        return true;
    }
}
递归删除 Node*& root 核心作用
  1. 直接修改上层节点的指针,无需记录父节点,简化逻辑

  2. 根节点删除、普通节点删除逻辑统一,无需单独判断根节点

  3. 替换法递归删除时,自动处理指针链接,代码极度简洁

五、中序遍历

验证 BST 有序性,递归实现:

复制代码
void InOrder() {
    _InOrder(_root);
    cout << endl;
}
​
void _InOrder(Node* root) {
    if (root == NULL) return;
    _InOrder(root->_left);         // 左
    cout << root->_key << " ";     // 根
    _InOrder(root->_right);        // 右
}

六、关键知识点总结

  1. 指针引用 Node*& 的核心价值

    • 递归插入/删除/销毁中使用,直接修改上层指针本身

    • 省略父节点记录,简化代码逻辑

    • 确保指针能被置空,避免野指针

  2. 递归 vs 非递归

    • 非递归:效率高,无栈溢出风险,逻辑繁琐(需记录父节点)

    • 递归:代码简洁、可读性高,依赖 Node*& 实现指针修改,深度过大可能栈溢出

  3. BST 核心操作时间复杂度

    • 最优(平衡树):O(\\log N)

    • 最坏(退化为链表):O(N)

  4. 删除核心

    • 左右子树都不为空时,必须用替换法,保证 BST 性质不变
相关推荐
云泽8082 小时前
笔试算法 - 双指针篇(一):移动零、复写零、快乐数与盛水容器
c++·算法
小堃学编程2 小时前
【项目实战】基于protobuf的发布订阅式消息队列(4)—— 服务端
c语言·c++·vscode·消息队列·gtest·protobuf·muduo
小白学大数据3 小时前
解决 Python 爬虫被限制:延迟抓取指令深度解析
开发语言·c++·爬虫·python
会编程的土豆3 小时前
【复习】二分查找
数据结构·c++·算法
yuanpan3 小时前
Python 调用 DLL 动态库入门:Windows 下调用 C++ 与 C# 动态库完整示例
c++·windows·python
handler014 小时前
Linux: 基本指令知识点(3)
linux·服务器·c语言·开发语言·c++·笔记
wuminyu4 小时前
专家视角看Java线程生命周期与上下文切换的本质
java·linux·c语言·jvm·c++
云深麋鹿4 小时前
C++ | 容器list
开发语言·c++·容器·list
handler015 小时前
Linux 基本指令知识点(1)
linux·c++·笔记