搜索二叉树

1. 二叉搜索树的基本定义

二叉搜索树(Binary Search Tree, BST)又称二叉排序树,是一种特殊的二叉树结构,满足以下性质:

  • 若左子树不为空,则左子树上所有节点的值均 小于等于 根节点的值。
  • 若右子树不为空,则右子树上所有节点的值均 大于等于 根节点的值。
  • 左右子树本身也必须是二叉搜索树。

✅ 关键点:

  • 支持重复值插入,但需保持逻辑一致性(如统一向右或向左插入相等元素)。
  • 不支持修改键值(key),否则会破坏搜索树结构。
cpp 复制代码
template <typename T>
struct TreeNode {
    T val;
    TreeNode* left;
    TreeNode* right;

    TreeNode(T x) : val(x), left(nullptr), right(nullptr) {}
};

2. 二叉搜索树的性能分析

情况 高度 时间复杂度
最优情况(完全/近似完全二叉树) log⁡2N\log_2 Nlog2N O(log⁡N)O(\log N)O(logN)
最差情况(退化为链表) NNN O(N)O(N)O(N)

⚠️ 综合来看,增删查改操作的时间复杂度为:O(N)O(N)O(N)

这在极端情况下效率极低,无法满足高性能需求。
🔍 解决方案 :引入平衡机制,形成 AVL树 和 红黑树 ,可保证高度始终为 O(log⁡N)O(\log N)O(logN),从而将操作时间稳定在对数级别。
📌 补充说明:

虽然二分查找也能达到 O(log⁡N)O(\log N)O(logN) 效率,但它依赖于数组等支持随机访问的数据结构。其主要缺陷在于:

  1. 插入/删除需移动大量元素,效率低;
  2. 不适合动态数据集合。

3. 二叉搜索树的插入操作

插入过程遵循如下规则:

  1. 若树为空,创建新节点作为根节点。
  2. 否则从根开始比较:
    • 插入值 < 当前节点值 → 往左子树走;
    • 插入值 > 当前节点值 → 往右子树走;
    • 插入值 == 当前节点值 → 可选择左或右(必须保持一致)。
  3. 找到空位置后,插入新节点。
cpp 复制代码
template <typename T>
bool insert(TreeNode<T>*& root, const T& val) {
    if (!root) {
        root = new TreeNode<T>(val);
        return true;
    }

    if (val <= root->val) {
        return insert(root->left, val); // 允许相等值插入左子树
    } else {
        return insert(root->right, val); // 向右插入
    }
}

💡 提示:若不允许重复值,可在 val == root->val 时直接返回失败。


4. 二叉搜索树的查找操作

查找从根节点出发,根据大小关系决定路径:

  • 目标值 < 当前节点值 → 向左查找;
  • 目标值 > 当前节点值 → 向右查找;
  • 目标值 == 当前节点值 → 找到目标;
  • 到达空节点仍未找到 → 值不存在。

✅ 重要细节 :

若允许重复值存在,通常要求返回中序遍历中的第一个匹配节点(即最左侧的相同值)。

cpp 复制代码
template <typename T>
TreeNode<T>* find(TreeNode<T>* root, const T& val) {
    while (root) {
        if (val < root->val) {
            root = root->left;
        } else if (val > root->val) {
            root = root->right;
        } else {
            return root; // 找到相等值
        }
    }
    return nullptr; // 未找到
}

🔄 递归版本也可实现:

cpp 复制代码
template <typename T>
TreeNode<T>* find_recursive(TreeNode<T>* root, const T& val) {
    if (!root || root->val == val) return root;
    return val < root->val ? find_recursive(root->left, val)
                           : find_recursive(root->right, val);
}

5. 二叉搜索树的删除操作

删除操作需先确认节点存在,再按四种情况进行处理:

情况分类及处理方式:
情况 描述 处理方法
① 叶子节点(左右子树为空) 删除该节点即可 直接释放内存
② 只有右子树 用右子树替代当前节点 父亲指针指向右孩子
③ 只有左子树 用左子树替代当前节点 父亲指针指向左孩子
④ 两个子树都存在 无法直接删除,使用"替换法" 替换为左子树最大值或右子树最小值

✅ 推荐策略:

使用 右子树的最小值节点(最左节点) 替代被删节点,更利于维护平衡。

cpp 复制代码
template <typename T>
bool remove(TreeNode<T>*& root, const T& val) {
    if (!root) return false;

    if (val < root->val) {
        return remove(root->left, val);
    } else if (val > root->val) {
        return remove(root->right, val);
    } else {
        // 找到要删除的节点
        if (!root->left && !root->right) {
            delete root;
            root = nullptr;
        } else if (!root->left) {
            TreeNode<T>* temp = root;
            root = root->right;
            delete temp;
        } else if (!root->right) {
            TreeNode<T>* temp = root;
            root = root->left;
            delete temp;
        } else {
            // 有两个子节点:找右子树中最左边的节点(最小值)
            TreeNode<T>* successor = root->right;
            while (successor->left) {
                successor = successor->left;
            }

            // 交换值
            std::swap(root->val, successor->val);

            // 删除原位置的节点(此时 successor 是叶子或只有一个子树)
            remove(root->right, val);
        }
        return true;
    }
}

✅ 优点:通过值交换,将复杂删除转化为简单情况。


6. 二叉搜索树的完整代码实现(基于 C++)
cpp 复制代码
//二叉搜索树

#include <iostream>

using namespace std;

template <class K>
struct BSTNode
{
	K _key;
	BSTNode<K>* _left;
	BSTNode<K>* _right;

	BSTNode(const K& key)
		:_key(key)
		, _left(nullptr)
		, _right(nullptr)
	{}

};

template<class K>
struct BSTree
{
	typedef BSTNode<K> Node;
public:

	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;

	}

	//中序
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool Find(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
			{
				return true;
			}
		}
		return false;
	}

	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
			{
				// 准备删除
				if (cur->_left == nullptr)
				{
					if (cur != _root)
					{
						// 左为空,父亲指向我的右
						if (cur == parent->_left)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
					}
					else
					{
						_root = cur->_right;
					}

					delete cur;
				}
				else if (cur->_right == nullptr)
				{
					if (cur != _root)
					{
						// 右为空,父亲指向我的左
						if (cur == parent->_left)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
					else
					{
						_root = cur->_left;
					}

					delete cur;
				}
				else
				{
					// 左右都不为空,找子树中适合的节点替代我
					Node* minRightParent = cur;
					Node* minRight = cur->_right;
					while (minRight->_left)
					{
						minRightParent = minRight;
						minRight = minRight->_left;
					}

					swap(cur->_key, minRight->_key);

					if (minRight == minRightParent->_left)
						minRightParent->_left = minRight->_right;
					else
						minRightParent->_right = minRight->_right;

					delete minRight;
				}

				return true;
			}
		}

		return false;
	}

private:

	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}

private:
	Node* _root = nullptr;
};

✅ 使用示例:

cpp 复制代码
int main() {
    BinarySearchTree<int> bst;

    int arr[] = {50, 30, 70, 20, 40, 60, 80, 30}; // 包含重复值

    for (int x : arr) {
        bst.insert(x);
    }

    std::cout << "中序遍历结果:";
    bst.inorderTraversal(); // 输出: 20 30 30 40 50 60 70 80

    auto node = bst.find(40);
    if (node) {
        std::cout << "找到值 40" << std::endl;
    }

    bst.remove(30); // 仅删除一个 30(中序第一个)
    std::cout << "删除 30 后中序遍历:";
    bst.inorderTraversal();

    return 0;
}

7. 二叉搜索树的应用场景详解
7.1 仅 Key 搜索场景
  • 用途:判断某个键是否存在。
  • 结构特点 :每个节点只存储 key。
  • 典型应用 :
    • 唯一性校验(如用户ID去重)
    • 集合类容器(如 std::set)
  • 限制 :
    • 不支持修改键;
    • 不能存储附加信息。
cpp 复制代码
// 简化版:仅存储 key
struct SetNode {
    int key;
    SetNode* left, *right;
    SetNode(int k) : key(k), left(nullptr), right(nullptr) {}
};

7.2 Key/Value 映射场景
  • 用途:以 key 查找对应的 value。
  • 结构特点 :每个节点包含 key 与 value。
  • 典型应用 :
    • 缓存系统(LRU Cache)
    • 数据库索引
    • 字典、映射表(如 std::map)
  • 优势 :
    • 快速查找、插入、删除;
    • 支持修改 value;
    • 保持有序性(中序遍历即有序)。
cpp 复制代码
template <typename K, typename V>
struct KVNode {
    K key;
    V value;
    KVNode* left;
    KVNode* right;

    KVNode(K k, V v) : key(k), value(v), left(nullptr), right(nullptr) {}
};

✅ 代码实现要点:

  • 所有操作仍以 key 为比较依据;
  • 返回的是 KVNode*,可通过 ->value 获取对应值;
  • 修改 value 安全,修改 key 会破坏树结构,应避免。

7.3 Key/Value 二叉搜索树完整实现(模板版)
cpp 复制代码
template <typename K, typename V>
class MapBST {
private:
    struct Node {
        K key;
        V value;
        Node* left;
        Node* right;

        Node(K k, V v) : key(k), value(v), left(nullptr), right(nullptr) {}
    };

    Node* root;

    void destroy(Node* node) {
        if (!node) return;
        destroy(node->left);
        destroy(node->right);
        delete node;
    }

public:
    MapBST() : root(nullptr) {}

    ~MapBST() {
        destroy(root);
    }

    void insert(K key, V value) {
        insertHelper(root, key, value);
    }

    bool get(K key, V& out_value) const {
        Node* node = find(root, key);
        if (node) {
            out_value = node->value;
            return true;
        }
        return false;
    }

    void update(K key, V new_value) {
        Node* node = find(root, key);
        if (node) {
            node->value = new_value;
        }
    }

    bool remove(K key) {
        return removeHelper(root, key);
    }

    void inorder() const {
        inorderHelper(root);
        std::cout << std::endl;
    }

private:
    void insertHelper(Node*& node, K key, V value) {
        if (!node) {
            node = new Node(key, value);
            return;
        }
        if (key < node->key) {
            insertHelper(node->left, key, value);
        } else {
            insertHelper(node->right, key, value);
        }
    }

    Node* find(Node* node, K key) const {
        while (node) {
            if (key == node->key) return node;
            if (key < node->key) node = node->left;
            else node = node->right;
        }
        return nullptr;
    }

    bool removeHelper(Node*& node, K key) {
        if (!node) return false;

        if (key < node->key) {
            return removeHelper(node->left, key);
        } else if (key > node->key) {
            return removeHelper(node->right, key);
        } else {
            if (!node->left && !node->right) {
                delete node;
                node = nullptr;
            } else if (!node->left) {
                Node* temp = node;
                node = node->right;
                delete temp;
            } else if (!node->right) {
                Node* temp = node;
                node = node->left;
                delete temp;
            } else {
                Node* succ = node->right;
                while (succ->left) succ = succ->left;
                std::swap(node->key, succ->key);
                std::swap(node->value, succ->value);
                removeHelper(node->right, key);
            }
            return true;
        }
    }

    void inorderHelper(Node* node) const {
        if (!node) return;
        inorderHelper(node->left);
        std::cout << "{" << node->key << ": " << node->value << "} ";
        inorderHelper(node->right);
    }
};

✅ 示例使用:

cpp 复制代码
int main() {
    MapBST<std::string, int> phoneBook;

    phoneBook.insert("Alice", 123456);
    phoneBook.insert("Bob", 789012);
    phoneBook.insert("Charlie", 345678);

    int num;
    if (phoneBook.get("Alice", num)) {
        std::cout << "Alice's number: " << num << std::endl;
    }

    phoneBook.update("Bob", 999999);
    std::cout << "更新后:";
    phoneBook.inorder(); // {Alice: 123456} {Bob: 999999} {Charlie: 345678}

    return 0;
}

总结:核心要点提炼

特性 说明
✅ 结构特性 左 ≤ 根 ≤ 右,递归定义
✅ 插入 从根出发,按大小方向走,直到空位插入
✅ 查找 逐层比较,最多走高度次
✅ 删除 四种情况,双子树用"替换法"
✅ 时间复杂度 最好 O(log⁡N)O(\log N)O(logN),最坏 O(N)O(N)O(N)
✅ 应用场景 仅 key → 集合;key/value → 映射表
✅ 限制 不支持修改 key,否则破坏结构
✅ 升级方案 用 AVL / 红黑树解决不平衡问题

🚀 最终建议 :

在实际工程中,不要直接使用原始二叉搜索树 。

应优先选用标准库中的 std::set(仅 key)、std::map(key/value),它们底层正是基于 红黑树 实现,具备良好的性能和稳定性。

相关推荐
Purple Coder1 小时前
M-T文章
1024程序员节
szxinmai主板定制专家1 小时前
ARM 还是 x86?CODESYS 数控机床控制器选型、FPGA 取舍与实时性真相
arm开发·人工智能·fpga开发·zynq·1024程序员节·codesys
冰暮流星1 小时前
mysql之索引设计的原则
数据库·mysql·1024程序员节
qq_429432301 小时前
叫班系统对接 IP-PBX/SIP 与排班系统:接口方式与部署选型要点
网络·网络协议·tcp/ip·信息与通信·ip·1024程序员节·voip通信协议
ljt27249606611 小时前
Vue笔记(十六)--自定义Hook
前端·1024程序员节
杭州领祺科技1 小时前
057 虚拟电厂聚合商工程链:准入 × 调度对接 × 结算口径 × 27 号令 4 区
储能·1024程序员节·虚拟电厂·电力·算电协同·电力现货
大陈子编程2 小时前
动态规划(C++):从入门到实战的完整指南
1024程序员节
Godspeed Zhao2 小时前
具身智能系统概论——边缘计算和云端协调
人工智能·边缘计算·1024程序员节
JavaPub-rodert2 小时前
不用 React、Vue,也不用后端:王仕宇是怎么把 JavaPub Tools 做成 Web + Electron 双端产品的
1024程序员节