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

C++-二叉搜索树

一、二叉树搜索树

1.1 二叉搜索树的概念

二叉搜索树(BST、Binary Search Tree)又称二叉排序树或二叉查找树,可以是一棵空树,也可以是具有以下性质的特殊二叉树:

1.如果左子树不为空,那么左子树上所有的节点的值(key)都小于根节点的值(key)。 2.如果右子树不为空,那么右子树上所有的节点的值(key)都大于根节点的值(key)。 3.左右子树也是一棵搜索二叉树。 4.搜索二叉树不允许有重复的值(key)。

默认的二叉搜索树走中序遍历结果是升序。

1.2 二叉搜索树的操作

对于下面这棵二叉搜索树:

int a[] = { 8,3,1,10,6,4,7,14,13 };

1.2.1二叉搜索树的查找

1.如果要查找的值(key)比根节点的值(key)要大,那么往右子树继续查找,如果比根节点的值(key) 小,那么往左子树查找。 2.最多查找高度次,如果走到空还没有找到,说明这个值不在该二叉搜索树里。

1.2.2二叉搜索树的插入

两种情况 1.树为空,则直接新增一个节点,并将该节点赋值给根节点。 2.树不为空,先查找插入位置,然后插入新节点。

依次插入16、0。插入成功返回true,插入失败返回false。

1.2.3二叉搜索树的删除

首先查找要删除的值是否在树中,如果不在,则返回false。如果在树中,又可能分为以下四种情况: 1.被删除的节点没有左右孩子。 1.被删除的节点只有左孩子。 2.被删除的节点只有右孩子。 3.被删除的节点既有左孩子,又有右孩子。

情况1可以和情况2或3合并。合并以后的新情况:

1.被删除的节点只有左孩子,左孩子可能为空树。 2.被删除的节点只有右孩子。 3.被删除的节点既有左孩子,又有右孩子。
情况1.被删除的节点只有左孩子,左孩子可能为空树。

情况1的删除方法

情况1存在的特殊情况以及处理方法:根节点只有左子树

情况2.被删除的节点只有右孩子。

情况2的处理方式以及情况2的特殊情况的处理方式

情况3:被删除的节点既有左孩子又有有孩子。

情况3的处理方法

替换法: 1.找到左子树的最大节点(最右节点),或右子树的最小节点(最左节点)。 2.左子树的最右节点必定是没有右子树的,右子树的最左节点必定是没有左子树的。 3.将被删除的节点替换最右节点或最左节点。 4.删除替换后的最右节点或最左节点。

以节点8和找右子树的最左节点为例

二、二叉搜索树的实现

2.1 二叉搜索树的框架结构

1.二叉树的每个节点的结构

包含:模板参数K、一个显示构造函数、节点的值_key、指向左孩子的节点指针_left、指向右孩子的 节点指针_right。
//1.二叉树的每个节点的结构 template<class K> struct BSTreeNode { //构造函数 BSTreeNode(const K& key=K()) :_key(key) ,_left(nullptr) ,_right(nullptr) {} //成员变量 K _key; //key值 BSTreeNode<K>* _left; //指向左孩子 BSTreeNode<K>* _right; //指向右孩子 };

2.二叉树的结构

包含:模板参数K、对节点名称的重命名,根节点_root,默认成员函数、功能成员函数
//模板参数K template<class K> class BSTree { //对节点重命名 typedef BSTreeNode<K> Node; public: //成员函数 //1.默认成员函数 //构造函数 BSTree(); //拷贝构造函数 BSTree(const BSTree<K>& tree); //赋值重载函数 BSTree<K>& operator=(BSTree<K> tree); //析构函数 ~BSTree(); //... //2.功能成员函数 //查找 bool Find(const K& key); //插入 bool Insert(const K& key); //删除 bool Erase(const K& key); //中序遍历 void InOrder(); //... private: //成员变量 Node* root = nullptr; };

其中功能成员函数又有递归版本和非递归版本。

2.2 实现

2.2.1 Find查找

非递归版本

//查找 bool Find(const K& key) { Node* cur = _root; while (cur != nullptr) { if (key > cur->_key) { //继续去右子树查找 cur = cur->_right; } else if (key < cur->_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 true; } else if (root->_key < key) { return _FindR(root->_right, key); } else { return _FindR(root->_left, key); } }

2.2.2 插入Insert

非递归版本

//插入 bool Insert(const K& key) { //如果根为空,直接插入新节点 if (_root == nullptr) { _root = new Node(key); return true; } Node* cur = _root; //记录cur的父节点 Node* prev = _root; while (cur != nullptr) { prev = cur; if (key > cur->_key) { //继续去右子树 cur = cur->_right; } else if (key < cur->_key) { //继续去左子树 cur = cur->_left; } else { //搜索二叉树不允许有重复值 return false; } } //插入 if (key > prev->_key) { prev->_right = new Node(key); } else { prev->_left = new Node(key); } return true; }

递归版本

递归版本的插入使用了引用传参,可以在找到插入位置时修改root可以直接插入
//递归版本 bool InsertR(const K& key) { return _InsertR(_root, key); } //引用传参,方便插入 bool _InsertR(Node*& root, const K& key) { if (root == nullptr) { //已经找到插入位置,直接插入 root = new Node(key); return true; } if (root->_key == key) { //搜索二叉树不允许有重复值 return false; } else if (root->_key > key) { _InsertR(root->_left, key); } else { _InsertR(root->_right, key); } return true; }

2.2.3 删除

非递归版本

//删除 bool Erase(const K& key) { Node* cur = _root; //指向cur的父节点 Node* prev = _root; while (cur&&cur->_key!=key) { prev = cur; if (cur->_key > key) { //去左子树 cur = cur->_left; } else if (cur->_key < key) { //去右子树 cur = cur->_right; } } if (cur == nullptr) { //没找到要删除的节点 return false; } //找到了要删除的节点 //开始删除 if (cur->_left && cur->_right) { //左右孩子均存在 //1.找右子树的最左节点 Node* tmp = cur->_right; Node* parent = cur; while (tmp->_left != nullptr) { parent = tmp; tmp = tmp->_left; } //交换最左节点和cur节点的值_key swap(cur->_key, tmp->_key); //如果parent没有动,说明cur的第一个右孩子就是最左节点 if (parent == cur) { cur->_right = tmp->_right; } else { //如果parent动了,那么tmp就必定是parent的左孩子 parent->_left = tmp->_right; } delete tmp; } else if (cur->_right) { //只有右孩子 //1.特殊情况:如果cur等于_root if (cur == _root) { _root = cur->_right; } else { //判断cur是父节点的左孩子还是右孩子 if (prev->_key > cur->_key) { //是左孩子 prev->_left = cur->_right; } else { //是右孩子 prev->_right = cur->_right; } } delete cur; } else { //只有左孩子或没有孩子 if (cur == _root) { _root = cur->_left; } else { //判断cur是父节点的左孩子还是右孩子 if (prev->_key > cur->_key) { //是左孩子 prev->_left = cur->_left; } else { //是右孩子 prev->_right = cur->_left; } } delete cur; } return true; }

递归版本

递归版本的删除也使用的引用传参,可以更方便直接删除节点。
//删除的递归版本 bool EraseR(const K& key) { return _EraseR(_root, key); } bool _EraseR(Node*& root, const K& key) { if (root == nullptr) { //没找到要删除的节点 return false; } if (root->_key > key) { return _EraseR(root->_left, key); } else if (root->_key < key) { return _EraseR(root->_right, key); } else { Node* cur = root; //找到了要删除的节点 if (root->_left && root->_right) { //有左右孩子 //1.找右子树的最左节点 Node* tmp = root->_right; while (tmp->_left != nullptr) { tmp = tmp->_left; } //交换最左节点和cur节点的值_key swap(root->_key, tmp->_key); //cur的右子树仍然能确保是一棵二叉搜索树,在cur的右子树里递归删除key _EraseR(root->_right, key); } else if (root->_right) { //只有右孩子 root = root->_right; delete cur; } else { //只有左孩子或没有孩子 root = root->_left; delete cur; } return true; } }

2.2.4 中序遍历

递归版本

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

2.2.5 构造函数

//构造函数 BSTree() :_root(nullptr) {}

2.2.6 拷贝构造函数

二叉搜索树的拷贝构造需要深拷贝。

//拷贝构造函数 BSTree(const BSTree<K>& tree) { //需要进行深拷贝 copy(_root, tree._root); } //root2拷贝至root1 void copy(Node*& root1, Node* root2) { if (root2 == nullptr) { root1 = nullptr; return; } root1 = new Node(root2->_key); copy(root1->_left, root2->_left); copy(root1->_right, root2->_right); }

2.2.7 赋值重载函数

//赋值重载函数 BSTree<K>& operator=(BSTree<K> tree) { swap(_root, tree._root); return *this; }

2.2.8 析构函数

//析构函数 ~BSTree() { destroy(_root); } void destroy(Node*& root) { if (root == nullptr) { return; } destroy(root->_left); destroy(root->_right); delete root; root = nullptr; }

2.2.9 整体代码

//1.二叉树的每个节点的结构 template<class K> struct BSTreeNode { //构造函数 BSTreeNode(const K& key=K()) :_key(key) ,_left(nullptr) ,_right(nullptr) {} //成员变量 K _key; //key值 BSTreeNode<K>* _left; //指向左孩子 BSTreeNode<K>* _right; //指向右孩子 }; //模板参数K template<class K> class BSTree { //对节点重命名 typedef BSTreeNode<K> Node; public: //构造函数 BSTree() :_root(nullptr) {} //拷贝构造函数 BSTree(const BSTree<K>& tree) { //需要进行深拷贝 copy(_root, tree._root); } //root2拷贝至root1 void copy(Node*& root1, Node* root2) { if (root2 == nullptr) { root1 = nullptr; return; } root1 = new Node(root2->_key); copy(root1->_left, root2->_left); copy(root1->_right, root2->_right); } //赋值重载函数 BSTree<K>& operator=(BSTree<K> tree) { swap(_root, tree._root); return *this; } //析构函数 ~BSTree() { destroy(_root); } void destroy(Node*& root) { if (root == nullptr) { return; } destroy(root->_left); destroy(root->_right); delete root; root = nullptr; } //2.功能成员函数 //查找 bool Find(const K& key) { Node* cur = _root; while (cur != nullptr) { if (key > cur->_key) { //继续去右子树查找 cur = cur->_right; } else if (key < cur->_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 true; } else if (root->_key < key) { return _FindR(root->_right, key); } else { return _FindR(root->_left, key); } } //插入 bool Insert(const K& key) { //如果根为空,直接插入新节点 if (_root == nullptr) { _root = new Node(key); return true; } Node* cur = _root; //记录cur的父节点 Node* prev = _root; while (cur != nullptr) { prev = cur; if (key > cur->_key) { //继续去右子树 cur = cur->_right; } else if (key < cur->_key) { //继续去左子树 cur = cur->_left; } else { //搜索二叉树不允许有重复值 return false; } } //插入 if (key > prev->_key) { prev->_right = new Node(key); } else { prev->_left = new Node(key); } return true; } //递归版本的插入使用了引用传参,可以在找到插入位置时修改root可以直接插入 //递归版本 bool InsertR(const K& key) { return _InsertR(_root, key); } //引用传参,方便插入 bool _InsertR(Node*& root, const K& key) { if (root == nullptr) { //已经找到插入位置,直接插入 root = new Node(key); return true; } if (root->_key == key) { //搜索二叉树不允许有重复值 return false; } else if (root->_key > key) { _InsertR(root->_left, key); } else { _InsertR(root->_right, key); } return true; } //删除 bool Erase(const K& key) { Node* cur = _root; //指向cur的父节点 Node* prev = _root; while (cur&&cur->_key!=key) { prev = cur; if (cur->_key > key) { //去左子树 cur = cur->_left; } else if (cur->_key < key) { //去右子树 cur = cur->_right; } } if (cur == nullptr) { //没找到要删除的节点 return false; } //找到了要删除的节点 //开始删除 if (cur->_left && cur->_right) { //左右孩子均存在 //1.找右子树的最左节点 Node* tmp = cur->_right; Node* parent = cur; while (tmp->_left != nullptr) { parent = tmp; tmp = tmp->_left; } //交换最左节点和cur节点的值_key swap(cur->_key, tmp->_key); //如果parent没有动,说明cur的第一个右孩子就是最左节点 if (parent == cur) { cur->_right = tmp->_right; } else { //如果parent动了,那么tmp就必定是parent的左孩子 parent->_left = tmp->_right; } delete tmp; } else if (cur->_right) { //只有右孩子 //1.特殊情况:如果cur等于_root if (cur == _root) { _root = cur->_right; } else { //判断cur是父节点的左孩子还是右孩子 if (prev->_key > cur->_key) { //是左孩子 prev->_left = cur->_right; } else { //是右孩子 prev->_right = cur->_right; } } delete cur; } else { //只有左孩子或没有孩子 if (cur == _root) { _root = cur->_left; } else { //判断cur是父节点的左孩子还是右孩子 if (prev->_key > cur->_key) { //是左孩子 prev->_left = cur->_left; } else { //是右孩子 prev->_right = cur->_left; } } delete cur; } return true; } //删除的递归版本 bool EraseR(const K& key) { return _EraseR(_root, key); } bool _EraseR(Node*& root, const K& key) { if (root == nullptr) { //没找到要删除的节点 return false; } if (root->_key > key) { return _EraseR(root->_left, key); } else if (root->_key < key) { return _EraseR(root->_right, key); } else { Node* cur = root; //找到了要删除的节点 if (root->_left && root->_right) { //有左右孩子 //1.找右子树的最左节点 Node* tmp = root->_right; while (tmp->_left != nullptr) { tmp = tmp->_left; } //交换最左节点和cur节点的值_key swap(root->_key, tmp->_key); //cur的右子树仍然能确保是一棵二叉搜索树,在cur的右子树里递归删除key _EraseR(root->_right, key); } else if (root->_right) { //只有右孩子 root = root->_right; delete cur; } else { //只有左孩子或没有孩子 root = root->_left; delete cur; } return true; } } //中序遍历 void InOrder() { _InOrderR(_root); } void _InOrderR(Node* root) { if (root == nullptr) { return; } _InOrderR(root->_left); cout << root->_key << " "; _InOrderR(root->_right); } //... private: //成员变量 Node* _root = nullptr; };

三、二叉搜索树的应用

3.1 K模型

K模型:K模型即只有key作为关键码,结构中只要存储key即可,关键码即为需要搜到的值

比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:

1.以词库里所有单词作为key,构建一棵搜索二叉树。 2.在二叉树里检索该单词word是否存在,存在则拼写正确,如果不存在,则是该单词拼写错误。

3.2 K-V模型

KV模型:每一个关键码key,都有一个与之对应的value,即<key,value>的键值对

比如英汉词典中的英文和汉语就是对应关系,通过英文可以快速找到对应的中文,<word,chinese>。

再比如统计单词,单词和单词出现的次数也是一对key-value模型,<word,count>。

3.3 改造K模型为K-V模型

删除不需要改变,每个节点的结构需要改变,插入也只需要新增一个参数value和new节点时的参 数,find返回值改为找到的节点的地址,遍历只需要多打印一个value。
//1.K-V模型的二叉树的每个节点的结构 template<class K,class V> struct BSTreeNode { //构造函数 BSTreeNode(const K& key = K(),const V& value=V()) :_key(key) ,_value(value) , _left(nullptr) , _right(nullptr) {} //成员变量 K _key; //key值 V _value; BSTreeNode<K,V>* _left; //指向左孩子 BSTreeNode<K,V>* _right; //指向右孩子 }; //模板参数K,V template<class K,class V> class BSTree { //对节点重命名 typedef BSTreeNode<K,V> Node; public: //查找 Node* Find(const K& key) { Node* cur = _root; while (cur != nullptr) { if (key > cur->_key) { //继续去右子树查找 cur = cur->_right; } else if (key < cur->_key) { //继续去左子树查找 cur = cur->_left; } else { //找到了,返回当前节点 return cur; } } //没找到 return nullptr; } //插入 bool Insert(const K& key,const V& value) { //如果根为空,直接插入新节点 if (_root == nullptr) { _root = new Node(key,value); return true; } Node* cur = _root; //记录cur的父节点 Node* prev = _root; while (cur != nullptr) { prev = cur; if (key > cur->_key) { //继续去右子树 cur = cur->_right; } else if (key < cur->_key) { //继续去左子树 cur = cur->_left; } else { //搜索二叉树不允许有重复值 return false; } } //插入 if (key > prev->_key) { prev->_right = new Node(key, value); } else { prev->_left = new Node(key, value); } return true; } //删除 bool Erase(const K& key) { Node* cur = _root; //指向cur的父节点 Node* prev = _root; while (cur && cur->_key != key) { prev = cur; if (cur->_key > key) { //去左子树 cur = cur->_left; } else if (cur->_key < key) { //去右子树 cur = cur->_right; } } if (cur == nullptr) { //没找到要删除的节点 return false; } //找到了要删除的节点 //开始删除 if (cur->_left && cur->_right) { //左右孩子均存在 //1.找右子树的最左节点 Node* tmp = cur->_right; Node* parent = cur; while (tmp->_left != nullptr) { parent = tmp; tmp = tmp->_left; } //交换最左节点和cur节点的值_key swap(cur->_key, tmp->_key); //如果parent没有动,说明cur的第一个右孩子就是最左节点 if (parent == cur) { cur->_right = tmp->_right; } else { //如果parent动了,那么tmp就必定是parent的左孩子 parent->_left = tmp->_right; } delete tmp; } else if (cur->_right) { //只有右孩子 //1.特殊情况:如果cur等于_root if (cur == _root) { _root = cur->_right; } else { //判断cur是父节点的左孩子还是右孩子 if (prev->_key > cur->_key) { //是左孩子 prev->_left = cur->_right; } else { //是右孩子 prev->_right = cur->_right; } } delete cur; } else { //只有左孩子或没有孩子 if (cur == _root) { _root = cur->_left; } else { //判断cur是父节点的左孩子还是右孩子 if (prev->_key > cur->_key) { //是左孩子 prev->_left = cur->_left; } else { //是右孩子 prev->_right = cur->_left; } } delete cur; } return true; } //中序遍历 void InOrder() { _InOrderR(_root); } void _InOrderR(Node* root) { if (root == nullptr) { return; } _InOrderR(root->_left); cout << root->_key << root->_value << " "; _InOrderR(root->_right); } //... private: //成员变量 Node* _root = nullptr; };

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

插入和删除都需要先进行查找,因此查找效率变成了代表二叉搜索树的各个操作的性能。

二叉搜索树查找的时间复杂度:O(N)

1.当二叉搜索树接近一棵完全二叉树或满二叉树时,查找效率为O(log(N))。 2.当二叉搜索树是以有序的顺序插入时,查找效率为O(N)。

在最坏情况下,二叉搜索树退化为了一只单支树,搜索性能就失去了,时间复杂度是按照最坏的情况进行计算的,因此二叉搜索树的时间复杂度为O(N)。

http://www.jsqmd.com/news/899134/

相关文章:

  • 导师严选!2026年不容错过的专业降AIGC工具 - 降AI小能手
  • 【Radan 2026.1 正式发布】更智能、更高效,钣金加工再升级!
  • 从“能用”到“好用”:基于ShardingSphere 5.1.2实现自定义分库分表策略(附完整代码)
  • ubuntu22.04在vscode使用codex
  • 为Claude Code配置稳定可靠的Taotoken后端接入点
  • 厂房工程采暖选GZ4钢制四柱暖气片靠谱吗?
  • 《AI智能体时代,大学生如何提升竞争力?》
  • ElementPlus 多个并列 Table 独立全选/取消全选 (适配嵌套表格业务)
  • dashscope-openai 20260527
  • 威海多特瑞:9 年深耕流量仪表,以隔离涡街技术打破国外垄断 - 资讯纵览
  • 【爬虫随笔】WX小程序强制开启F12开发者工具
  • UE5官方文档(第一人称射击游戏教程)解读 第十章
  • 无人机辅助近场RIS物理层密钥生成:MRF方案与AI协调实践
  • 构建本地AI语音助手:从Whisper、LLM到技能执行的完整实践
  • 为AI智能体构建防篡改审计日志:基于数字签名的责任追溯方案
  • 【第一次用办公小浣熊做航运比价,我彻底被惊艳到了】
  • 极化码List-Fast-SSC解码器专用排序架构:从算法特性到硬件优化
  • 收藏!零基础小白也能进阶大模型Agent开发的超全实战指南
  • RAG:检索增强生成
  • 数据库-索引
  • 用Unity和C#手把手教你实现一个简单的社会力模型(Social Force Model)模拟器
  • 智能化的固定资产管理软件公司选型参考与选择逻辑 - 资讯快报
  • 《2026 年 5 月中国居住地新政研究报告》
  • 2026年罗茨风机深度选型:如何为你的工业场景匹配最佳方案? - 资讯纵览
  • ESXI 内网环境离线安装群晖NAS
  • 2026年电线电缆品牌推荐:珠江电缆优势深度解析与联系指南! - 资讯快报
  • FPGA实时癫痫检测:时间序列分割与异常检测的硬件实现
  • 【力扣100题】64.岛屿数量
  • API聚合平台从比价到选型:2026年AI大模型API中转站选购核心逻辑与实战评估
  • StreamFX终极指南:5个核心功能让你的直播画面瞬间升级