当前位置: 首页 > news >正文

深入理解二叉搜索树:从原理到实现

目录

一、 二叉搜索树的概念与特性

二、 二叉搜索树的性能分析

三、二叉搜索树的核心操作

3.1 插入操作

3.2 查找操作

3.3 删除操作

四、二叉搜索树的完整实现

4.1 基础版本(Key模型)

4.2 扩展版本(Key-Value模型)

五、二叉搜索树的应用场景

5.1 key搜索场景

5.2 Key-Value模型应用场景

六、总结


本文将详细解析二叉搜索树的核心概念、操作实现及应用场景,帮助读者全面掌握这一重要的数据结构。

一、 二叉搜索树的概念与特性

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

  • 若左子树非空,则左子树上所有节点的值都小于等于根节点的值

  • 若右子树非空,则右子树上所有节点的值都大于等于根节点的值

  • 左右子树也分别为二叉搜索树

重要说明:关于相等值的处理,二叉搜索树可以设计为支持或不支持重复值,这取决于具体应用场景。在C++ STL中:

  • map/set:不支持重复键值

  • multimap/multiset:支持重复键值

二、 二叉搜索树的性能分析

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其高度为:log₂N

最差情况下,二叉搜索树退化为单支树(或者类似单支树),其高度为:N

所以综合而言,二叉搜索树增删查改时间复杂度为:O(N)

那么这样的效率显然是无法满足我们需求的,我们后续课程需要继续讲解二叉搜索树的变形,平衡二叉树。二叉搜索树、AVL树和红黑树,才能适用于我们在内存中存储和搜索数据。

另外需要说明的是,二分查找也可以实现O(log₂N)级别的查找效率,但是二分查找有两大缺陷

  1. 需要存储在支持下标随机访问的结构中,并且有序。

  2. 插入和删除数据效率很低,因为存储在支持下标随机访问的结构中,插入和删除数据一般需要挪动数据。

情况树形态树高度操作时间复杂度
最优完全二叉树O(log₂N)O(log₂N)
最差单支树O(N)O(N)

三、二叉搜索树的核心操作

3.1 插入操作

如下:

1. 树为空,创建新节点作为根节点

2. 树非空,从根节点开始比较:

  • 插入值 > 当前节点值:向右子树移动

  • 插入值 < 当前节点值:向左子树移动

  • 插入值 = 当前节点值:根据设计决定(支持重复值则继续移动,否则返回失败)

3. 找到空位置后插入新节点

代码实现:

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;
}

3.2 查找操作

步骤:

  1. 从根节点开始比较

  2. 查找值 > 当前节点值:向右子树查找

  3. 查找值 < 当前节点值:向左子树查找

  4. 查找值 = 当前节点值:找到目标

  5. 遇到空节点:查找失败

代码实现

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;
}

3.3 删除操作

首先查找元素是否在二叉搜索树中,如果不存在,则返回false。

如果查找元素存在则分以下四种情况分别处理:(假设要删除的结点为N)

情况1:删除叶子节点

直接删除,父节点对应指针置空

情况2:删除只有右子树的节点

用右子树替代被删节点

情况3:删除只有左子树的节点

用左子树替代被删节点

情况4:删除有两个子树的节点(最复杂)

使用替换法删除

  • 找到左子树的最大节点或右子树的最小节点

  • 用找到的节点值替换待删除节点的值

  • 删除被替换的节点(该节点必定是情况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 {// 找到要删除的节点if (cur->_left == nullptr) {// 情况1、2:无左子树if (parent == nullptr) {_root = cur->_right;} else {if (parent->_left == cur)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;} else if (cur->_right == nullptr) {// 情况3:无右子树if (parent == nullptr) {_root = cur->_left;} else {if (parent->_left == cur)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;} else {// 情况4:有两个子树// 找右子树的最小节点Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left) {rightMinParent = rightMin;rightMin = rightMin->_left;}// 替换值cur->_key = rightMin->_key;// 删除右子树的最小节点if (rightMinParent->_left == rightMin)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;delete rightMin;}return true;}}return false;
}

四、二叉搜索树的完整实现

4.1 基础版本(Key模型)

Key模型是最基础的版本控制方式,主要用于管理简单的键值对数据。它的核心特点包括:

    1. 数据结构

  • 采用最简单的键值对存储方式

  • 每个Key对应一个Value

  • Value可以是字符串、数字等基本数据类型

     2. 版本控制机制

  • 每次修改都会生成新的版本记录

  • 通过时间戳或版本号标识不同版本

  • 支持基本的版本回退功能

     3. 典型应用场景

  • 配置管理:如保存应用的参数配置

  • 用户偏好设置:存储用户的自定义设置

  • 简单的元数据管理:如产品属性、标签等

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>
class BSTree {typedef BSTNode<K> Node;
private:Node* _root = nullptr;// 递归中序遍历void _InOrder(Node* root) {if (root == nullptr) return;_InOrder(root->_left);std::cout << root->_key << " ";_InOrder(root->_right);}public:// 插入、查找、删除等方法如前文所示void InOrder() {_InOrder(_root);std::cout << std::endl;}
};

4.2 扩展版本(Key-Value模型)

在标准版本的基础上,我们引入了Key-Value存储模型来增强数据结构的灵活性和查询效率。这个扩展版本特别适合需要快速查找和动态更新的应用场景。

主要特性:

  1. 灵活的键值对存储:每个数据项都由唯一的键(Key)和对应的值(Value)组成

    • 键通常采用字符串或数字类型

    • 值可以是任意数据结构(字符串、数字、数组、对象等)

    • 示例:{"user_id": 123, "name": "张三", "orders": [1001,1002]}

  2. 高效查询机制

    • 支持精确键查询(O(1)时间复杂度)

    • 可选的二级索引支持范围查询

template<class K, class V>
struct BSTNode {K _key;V _value;BSTNode<K, V>* _left;BSTNode<K, V>* _right;BSTNode(const K& key, const V& value): _key(key), _value(value), _left(nullptr), _right(nullptr) {}
};template<class K, class V>
class BSTree {typedef BSTNode<K, V> Node;
private:Node* _root = nullptr;public:// 插入方法bool Insert(const K& key, const V& value) {if (_root == nullptr) {_root = new Node(key, value);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, value);if (parent->_key < key) {parent->_right = cur;} else {parent->_left = cur;}return true;}// 查找方法,返回节点指针以便修改valueNode* 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 cur;}}return nullptr;}
};

五、二叉搜索树的应用场景

5.1 key搜索场景

场景特点:只需要判断关键字是否存在,不关心关联值。

只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在。key的搜索场景实现的二叉树搜索树支持增删查,但是不支持修改,修改key破坏搜索树结构了。

场景1:小区无人值守车库,小区车库买了车位的业主车才能进小区,那么物业会把买了车位的业主的车牌号录入后台系统,车辆进入时扫描车牌在不在系统中,在则抬杆,不在则提示非本小区车辆,无法进入。

代码实现

// 车牌检查系统
BSTree<std::string> carSystem;
carSystem.Insert("京A12345");
carSystem.Insert("沪B67890");// 车辆入场检查
std::string licensePlate = "京A12345";
if (carSystem.Find(licensePlate)) {std::cout << "允许进入" << std::endl;
} else {std::cout << "未授权车辆" << std::endl;
}

5.2 Key-Value模型应用场景

场景特点:需要通过关键字查找对应的关联值。

每一个关键码key,都有与之对应的值valuevalue可以是任意类型对象。树的结构中(结点)除了需要存储key,还要存储对应的value,增/删/查还是以key为关键字走二叉搜索树的规则进行比较,可以快速查找到key对应的value。key/value的搜索场景实现的二叉搜索树支持修改,但是不支持修改key,修改key会破坏搜索树性质,可以修改value

场景1商场无人值守车库,入口进场时扫描车牌,记录车牌和入场时间,出口离场时,扫描车牌,查找入场时间,用当前时间-入场时间计算出停车时长,计算出停车费用,缴费后抬杆,车辆离场。

代码示例

// 单词统计系统
BSTree<std::string, int> wordCount;
std::vector<std::string> words = {"apple", "banana", "apple", "orange", "banana", "apple"};for (const auto& word : words) {auto node = wordCount.Find(word);if (node == nullptr) {wordCount.Insert(word, 1);} else {node->_value++;}
}// 输出统计结果
// 中序遍历会按字典序输出

六、总结

二叉搜索树作为一种基础而重要的数据结构,具有以下特点:

优点

  • 动态性能好,支持高效插入删除

  • 中序遍历可得有序序列

  • 实现相对简单

缺点

  • 普通BST可能退化为链表

  • 需要平衡机制保证性能

适用场景

  • 需要动态维护有序数据集

  • 查找、插入、删除操作都较频繁

  • 数据量不是特别大,或可以使用平衡BST

掌握二叉搜索树不仅是学习更高级数据结构的基础,也是理解许多系统底层实现的关键。在实际应用中,我们通常使用其平衡变种(如红黑树)来保证稳定性能。

http://www.dtcms.com/a/548746.html

相关文章:

  • Rust 泛型参数的实践与思考
  • AppML 案例:Employees 应用解析
  • 【Qt开发】布局管理器(一)-> QVBoxLayout垂直布局
  • CF练习记录~
  • 自动化测试 | 认识接口自动化封装中的YAML用例
  • dedecms做门户网站石家庄网站建站
  • windows系统下docker desktop创建容器指定ip
  • 微网站建设费用预算旅游网站开发的需求
  • Ionic + Angular 跨端实战:用 Capacitor 实现相机拍照功能并适配移动端
  • Python 爬虫:从基础到实战的完整指南
  • Angular【http服务端交互】
  • Angular【核心特性】
  • 做seo前景怎么样郑州企业网站优化多少钱
  • 华为 USG 防火墙 NAT 配置
  • uni-app App更新升级前端实现
  • 数据通信领域的专业认证——华为数通认证
  • JavaSE基础——第十二章 集合
  • iis发布网站页面出问题网上服务平台社保
  • 基于C语言上,面向对象语言:C++基础(学完C语言后再看)
  • windows npm打包无问题,但linux npm打包后部分样式缺失
  • npm install命令介绍
  • 人机交互与网页开发
  • p2p理财网站建设新浪云怎么做自己的网站
  • 手机分销网站wordpress视频上传不
  • 健身俱乐部|基于Java+Vue的健身俱乐部管理系统(源码+数据库+文档)
  • linux服务器升级显卡驱动(笔记)
  • 一个DevExpress的Docx文件处理的Bug的解决
  • Ubuntu(④Mysql)
  • Docker 拉取配置教程:解决镜像拉取连接超时问题
  • 开始改变第六天 MySQL(1)