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

C++ 二叉搜索树的模拟实现(key结构的非递归和key_value结构的非递归的实现)

目录

二叉搜索树的概念

key结构非递归的模拟实现

节点和类模板的实现

节点插入函数

递归版本:

中序遍历函数

节点查找函数

节点删除函数

删除场景1(左子树为空 ,右子树可能空或非空)

删除场景2(右子树为空 ,左子树可能空或非空)

删除场景3(左右子树都非空)

代码测试:

​编辑

key_value结构非递归的模拟实现

节点和类模板的实现

节点插入函数

中序遍历函数

节点查找函数

节点删除函数

代码测试:

key模型和key_value模型的二叉搜索树的应用

完整代码:

key结构非递归的模拟实现

key_value结构非递归的模拟实现

总结:


二叉搜索树的概念

二叉搜索树(Binary Search Tree)又称二叉排序树,它可以是一颗空树,也可以是一颗具有以下性质的二叉树

  1. 若它的左子树不为空,那么它左子树上所有节点上的值均小于根节点的值
  2. 若它的右子树不为空,那么它右子树上所有节点上的值均大于根节点的值
  3. 它的左右子树也分别为二叉搜索树

下图就为一个二叉搜索树,观察其节点的值,均符合二叉搜索树的性质

二叉搜索树的模拟实现一共有三种结构

  1. key结构的非递归
  2. key_value结构的递归

这三种结构的实现是以模板的形式实现,因为模板声明和定义不分离,所以声明和定义放入同一个.h头文件中实现

key结构非递归的模拟实现

key结构具体是什么结构?怎么理解?

  • key结构里的 key 可以理解为用于确定节点在树中位置的“关键值”。二叉搜索树的核心规则是:左子树中所有节点的 key 都小于根节点的 key,右子树中所有节点的 key 都大于根节点的 key。所以,“key结构”指的是二叉搜索树的节点仅以 单个关键值(key) 为核心来组织,通过这个 key 来维护树的有序性,实现对数据的插入、查找、删除等操作。

节点和类模板的实现

  1. 首先我们需要一个struct结构体作为二叉搜索树中的节点BSTreeNode,由于这个节点中的成员变量要被经常访问,那么我们将其定义为strcut,那么这个节点中存储左右节点的指针即_left和_right,同时还存储有一个模板K类型的变量_key
  2. 在节点中还应该有一个构造函数---用于初始化节点,那么接收K类型的数据key,由于这个K的类型可能是自定义类型,传参消耗大,所以我们采用引用传参可以减小消耗,同时由于我们不对这个数据key进行修改,那么我们采用const进行修饰这个数据key,我们在构造函数的初始化列表中对左右节点指针初始化为空,并且让接收的数据赋值给节点中存储数据的变量_key
  3. 除此之外我们还应该定义一个二叉搜索树的类模板BSTree,由于需要对成员进行封装所以采用class进行定义这个二叉搜索树,那么初始只需要一个私有成员变量即根节点_root,为了便于定义和使用节点我们使用typedef将节点类型BSTreeNode<K>重命名为Node便于使用
  4. 最后这个节点类型Node声明出一个根节点的指针_root,在初始的时候,我们在二叉搜索树中的构造函数的初始化列表中对这个根节点的指针置为空即可,也可以直接给缺省值nullptr

节点插入函数

插入的具体过程如下:

  1. 树为空,则直接新增结点,赋值给root指针
  2. 树不空,按二叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点。

  • const K& key :用 const +引用避免拷贝,传入要插入的键值(比如int、string等可比较类型)。
  • 返回值 bool :二叉搜索树要求键值唯一(不能存重复key),所以返回 true =插入成功(key是新的), false =插入失败(key已在树中)。

1. 处理“空树”场景(最特殊的初始情况)

if (_root == nullptr)
{_root = new Node(key);  // 空树时,新节点直接作为根节点return true;            // 插入成功,返回true
}
  • _root 是树的根节点指针,初始为 nullptr (树为空)。
  • 空树插入时,无需找位置,直接new一个节点给 _root ,插入成功返回 true 。

2. 非空树:找插入位置(核心遍历逻辑)

Node* parent = nullptr;  // 记录cur的父节点(后续要挂新节点)
Node* cur = _root;       // 从根节点开始遍历,找插入位置
while (cur)  // 循环直到cur为空(找到空位置,就是新节点要插的地方)
{if (cur->_key < key)  // 1. 当前节点的key < 要插入的key → 往右边找{parent = cur;     // 先更新父节点为当前curcur = cur->_right;// cur移到右孩子,继续找}else if (cur->_key > key)  // 2. 当前节点的key > 要插入的key → 往左边找{parent = cur;cur = cur->_left; // cur移到左孩子,继续找}else  // 3. cur->_key == key(key已存在){return false;     // 插入失败,直接返回false(不允许重复)}
}
  • 核心逻辑:利用二叉搜索树的左小右大规则遍历,直到 cur 为空(此时 parent 就是新节点的父节点)。
  • 中途如果遇到 cur->_key == key ,说明key已存在,直接返回 false (插入失败)。

3. 插入新节点(挂到父节点的左/右)

cur = new Node(key);  // 创建新节点(此时cur指向新节点)
// 判断新节点该挂在parent的左还是右
if (parent->_key < key)  // parent的key < 新key → 新节点挂右边
{parent->_right = cur;
}
else  // parent的key > 新key → 新节点挂左边
{parent->_left = cur;
}
return true;  // 插入成功,返回true
  • 此时 cur 是空(遍历结束的位置),所以用 new Node(key) 创建新节点,再通过 parent 挂到树中。
  • 挂的位置依然遵循“左小右大”:父节点key小 → 新节点挂右;父节点key大 → 新节点挂左。
  • 挂完后返回 true ,表示插入成功。

while循环结束时,cur是nullptr,它只负责找到“该插入位置的父节点parent”,但完全没记录“这个nullptr是parent的左孩子还是右孩子”,所以必须再比较一次parent和key的大小,才能确定新节点挂在parent的左边还是右边。
简单说:while循环找“爹(parent)”,if语句定“爹的左/右胳膊(挂左边还是右边)”。

整体流程:

  • 空树 → 新节点当根,返回 true ;
  • 非空树 → 按“左小右大”遍历,找插入位置;
  • 遍历中遇到重复key → 返回 false ;
  • 找到空位置 → 新节点挂到父节点左/右,返回 true 。

递归版本:

InsertR 是公有的插入函数,直接调用私有递归函数  _Insert ,并把根节点  _root  传进去(因为后续要修改根节点,所以传引用)。

递归核心  _Insert :函数参数是「当前节点的引用」和「要插入的键值  x 」,逻辑分3种情况:
- 情况1:当前节点为空( root == nullptr )
  说明找到了插入位置,直接创建新节点,挂到当前位置,返回  true  表示插入成功。
- 情况2:当前节点的键 < 插入键( root->_key < x )
  按照二叉搜索树规则,需要往右子树递归插入,继续调用  _Insert(root->right, x) 。
- 情况3:当前节点的键 > 插入键( root->_key > x )
  同理,往左子树递归插入,调用  _Insert(root->left, x) 。
- 情况4:键相等( root->_key == x )
  二叉搜索树不允许重复键,直接返回  false  表示插入失败。

注意点:

  1. 因为  _Insert  的参数是  Node*& root (节点指针的引用)所以递归中修改子节点(比如给  nullptr  赋值新节点)会直接影响原树的结构。
  2. 递归的终止条件是“找到空位置插入”或“遇到重复键”。

中序遍历函数

搜索二叉树和中序遍历的关系?

搜索二叉树和中序遍历有着紧密的关系,中序遍历是搜索二叉树的一个重要特性,搜索二叉树通过中序遍历可以得到有序序列。具体如下:

  1. 中序遍历结果有序:搜索二叉树的定义为对于任意节点,其左子树中所有节点的值均小于该节点的值,而右子树中所有节点的值均大于该节点的值。中序遍历的顺序是“左根右”,这就使得搜索二叉树在中序遍历时,会先访问左子树上的节点,然后是根节点,最后是右子树上的节点,从而确保节点值按照从小到大的顺序输出。
  2. 用于验证搜索二叉树:可以通过中序遍历的方式来验证一棵二叉树是否为搜索二叉树。在中序遍历过程中,记录前一个访问的节点值,若当前节点值不大于前一个节点值,则说明该树不是搜索二叉树。
  3. 在搜索二叉树中查找第k小元素:在搜索二叉树中,可以通过中序遍历的方式逐步访问节点,因为中序遍历是按从小到大的顺序访问节点,所以直到访问到第k个节点,即可找到第k小的元素。
  4. 将搜索二叉树转换为有序双向链表:在某些场景下,可以将搜索二叉树转换为一个有序的双向链表。这一过程通常通过中序遍历实现,每次访问节点时,将当前节点与前一个节点建立双向链接,从而实现转换。

这里的中序遍历函数我们依旧采用函数递归的方式进行:

公有函数 InOrder()

void InOrder()
{_InOrder(_root);cout << endl;
}
  • 这是对外暴露的接口函数,用户只需调用  InOrder()  就 能触发中序遍历。
  • 它的核心操作是调用私有函数  _InOrder(_root) (其中 _root 是二叉树的根节点指针 , 是私有成员变量)。

私有递归函数  _InOrder(Node* root) 

void _InOrder(Node* root)
{if (root == nullptr)    //递归终止条件return;_InOrder(root->left);    // 先遍历左子树cout << root->key << " "; // 再访问当前节点的键值_InOrder(root->right);   // 最后遍历右子树
}

为什么要将 _InOrder 封装在 InOrder 内部(即私有函数被公有函数调用)?

  •  _InOrder  是实现中序遍历的“底层逻辑”,包含递归细节和对根节点的直接操作。将其设为私有函数,可以避免用户直接调用时因参数错误(比如误传非根节点)导致逻辑混乱,从而隐藏复杂的实现细节,只暴露简洁的  InOrder()  接口。
  • 中序遍历必须从根节点开始才能遍历整棵树, InOrder  函数通过固定传入  _root (类的根节点成员),确保了遍历的起点正确。用户无需关心根节点的存在,只需调用  InOrder()  即可,降低了使用门槛。
  • 面向对象设计强调“高内聚、低耦合”,将实现细节( _InOrder )封装在内部,对外提供统一、简洁的接口( InOrder ),让类的使用更安全、更易维护。

节点查找函数

这里的查找是指判断key值是否在这个二叉搜索树中,所以其对应的函数返回值是bool值

1. 初始化定义一个指针 cur,从二叉搜索树的根节点( _root )开始查找。
2. 循环查找:

  • 若当前节点值  cur->_key  小于目标  key :根据二叉搜索树“右子树值更大”的特性,目标  key  若存在,一定在右子树中,因此  cur  指向右子节点( cur = cur->_right )。
  • 若当前节点值  cur->_key  大于目标  key :根据“左子树值更小”的特性,目标  key  若存在,一定在左子树中,因此  cur  指向左子节点( cur = cur->_left )。
  • 若当前节点值  cur->_key  等于目标  key :说明找到目标值,直接返回  true 。

3. 如果循环结束( cur  变为  nullptr ,即遍历到空节点),说明整棵树中没有目标  key ,返回  false 。

补充:

二叉搜索树的核心特性是左子树值 < 根节点值 < 右子树值,因此查找时无需遍历整棵树,而是通过“比大小决定走左/右子树”,将时间复杂度优化到  O(h) ( h  为树的高度,理想平衡时  h = log₂n , n  为节点数),比普通二叉树的  O(n)  查找更高效。


节点删除函数

首先查找元素是否在二叉搜索树中,如果不存在,则返回false。
如果查找元素存在则分以下四种情况分别处理:(假设要删除的结点为N)

  1. 要删除结点N左右孩子均为空
  2. 要删除的结点N左孩子为空,右孩子结点不为空
  3. 要删除的结点N右孩子为空,左孩子结点不为空
  4. 要删除的结点N左右孩子结点均不为空

对应以上四种情况的解决方案:

  1. 把N结点的父亲对应孩子指针指向空,直接删除N结点(情况1可以当成2或者3处理,效果是一样的)
  2. 把N结点的父亲对应孩子指针指向N的右孩子,直接删除N结点
  3. 把N结点的父亲对应孩子指针指向N的左孩子,直接删除N结点
  4. 无法直接删除N结点,因为N的两个孩子无处安放,只能用替换法删除找N左子树的值最大结点R(最右结点)或者N右子树的值最小结点R(最左结点)替代N,因为这两个结点中任意一个,放到N的位置,都满足二叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转而变成删除R结点,R结点符合情况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){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;elseminRightParent->_right = minRight->_right;delete minRight;}return true;}}return false;
}

代码的总体流程分为 “查找节点→分场景删除→保持BSTree性质” 这三个步骤:

1. 查找目标节点(遍历逻辑)

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;
  1. 从根节点 _root 出发,根据二叉搜索树的“左小右大”性质遍历,找到 key 对应的节点 cur ,同时用 parent 记录 cur 的父节点。
  2. 若 cur->_key < key :目标在右子树, parent 更新为当前 cur , cur 移动到右孩子;
  3. 若 cur->_key > key :目标在左子树, parent 更新为当前 cur , cur 移动到左孩子;
  4. 若 cur->_key == key :找到目标,进入删除逻辑的三个子场景;
  5. 若循环结束后 cur 为 nullptr :说明树中无该 key ,返回 false 表示删除失败。

删除场景1(左子树为空 ,右子树可能空或非空)

场景 : 当要删除的节点 cur 左子树为空时,属于该场景。此时 cur 的右子树可能为空(节点是叶子),也可能非空(有右子树分支)。
 
本质 : 因为左子树为空,只需让 cur 的父节点 parent (或根节点 _root,如果 cur 是根)直接指向 cur 的右子树,再释放 cur 的内存即可。

if (cur->_left == nullptr)  // 判定左子树为空,进入场景1
{if (cur != _root)  // 情况1:cur不是根节点{if (cur == parent->_left)  // cur是父节点的左孩子{parent->_left = cur->_right;  // 父节点左指针指向cur的右子树}else  // cur是父节点的右孩子{parent->_right = cur->_right;  // 父节点右指针指向cur的右子树}}else  // 情况2:cur是根节点{_root = cur->_right;  // 根节点直接指向cur的右子树}delete cur;  // 释放cur的内存,完成删除
}

左子树为空 ,右子树可能空或非空:

示例:

  1. 删除节点1 : 节点1的左子树为空,父节点是3,且是3的左孩子。执行  parent->_left = cur->_right (即3的左指针指向1的右子树,此处1的右子树为空,所以3的左指针变为 nullptr ),再 delete 节点1,完成删除。
  2. 删除节点10 : 节点10的左子树为空,父节点是8,且是8的右孩子。执行  parent->_right = cur->_right (即8的右指针指向10的右子树14),再 delete 节点10,完成删除。

删除场景2(右子树为空 ,左子树可能空或非空)

场景 : 当要删除的节点  cur  右子树为空时,属于该场景。此时  cur  的左子树可能为空(节点是叶子),也可能非空(有左子树分支)。

本质 : 因为右子树为空,只需让  cur  的父节点  parent (或根节点  _root ,如果  cur  是根)直接指向  cur  的左子树,再释放  cur  的内存即可。这是场景一的对称逻辑(场景一处理“左空”,场景二处理“右空”)。

else if (cur->_right == nullptr)  // 判定右子树为空,进入场景2
{if (cur != _root)  // 情况1:cur不是根节点{if (cur == parent->_left)  // cur是父节点的左孩子{parent->_left = cur->_left;  // 父节点左指针指向cur的左子树}else  // cur是父节点的右孩子{parent->_right = cur->_left;  // 父节点右指针指向cur的左子树}}else  // 情况2:cur是根节点{_root = cur->_left;  // 根节点直接指向cur的左子树}delete cur;  // 释放cur的内存,完成删除
}

删除场景3(左右子树都非空)

场景 : 当要删除的节点  cur  左、右子树都不为空时,属于该场景。此时不能直接让父节点接管某一子树(会破坏“左小右大”性质),必须找一个“替代节点”。
 

核心就是“找替代节点维持树结构”:
采用“右子树找最小值节点” 的方案(也可左子树找最大值,此处选前者):
1. 在  cur  的右子树中,找到最靠左的节点  minRight (该节点是右子树最小值,满足“比  cur  左子树大、比  cur  右子树其他节点小”,是最佳替代者);
2. 将  minRight  的值与  cur  的值交换(相当于用  minRight  间接“替代”了  cur  的位置);
3. 删除  minRight  节点( minRight  因是右子树最左节点,其左子树必为空,可按场景一逻辑处理)。

else  // 判定左右子树都非空,进入场景3
{// 1. 找到cur右子树的最小值节点minRight及其父节点minRightParentNode* minRightParent = cur;  // 初始化minRight的父节点为curNode* minRight = cur->_right;  // 初始化minRight为cur的右孩子while (minRight->_left)  // 循环找最左节点(左子树为空时停止){minRightParent = minRight;  // 父节点随子节点移动minRight = minRight->_left;  // 子节点向左移动}// 2. 交换cur和minRight的值:用minRight的值“覆盖”cur的位置swap(cur->_key, minRight->_key);// 3. 删除minRight(其左子树为空,按场景一逻辑处理)if (minRight == minRightParent->_left)  // minRight是父节点的左孩子{minRightParent->_left = minRight->_right;  // 父节点左指针指向minRight的右子树}else  // minRight是父节点的右孩子(仅当cur右子树无左分支时触发){minRightParent->_right = minRight->_right;  // 父节点右指针指向minRight的右子树}delete minRight;  // 释放minRight的内存
}
return true;  // 删除成功

场景三的核心是“值交换 + 简化删除”:

  • 不直接删除有两个子树的复杂节点  cur ,而是找一个“子树结构简单(左空)”的  minRight  节点,通过交换值让  minRight  承担“被删除”的角色,最终将场景三转化为场景一处理,既维持了二叉搜索树性质,又降低了操作复杂度。

代码测试:

key_value结构非递归的模拟实现

key_value 又是什么结构?

key-value(键值对) 是一种“通过键(key)快速查找值(value)”的数据结构,核心是“键唯一对应值”,像字典一样:用“单词(key)”查“释义(value)”。

核心特点:

  • 键(key):唯一、可比较(比如二叉搜索树需要key能比大小,哈希表需要key能哈希)。
  • 值(value):可以是任意数据类型(数字、字符串、对象等),通过key快速获取。

简单说:键值对就是“用唯一标识(key)绑定数据(value)”的存储方式

key_value结构非递归的模拟实现和key结构非递归的模拟实现高度相似,只不过key_value结构的二叉搜索树中存储是两个值,一个是_key,一个是_value。

节点和类模板的实现

那么由于结构中的节点存储的数据多了一个,那么模板参数列表中就需要多一个模板参数V用来表示value的类型,那么就需要改变二叉搜索树的节点中的成员变量,多添加一个V类型的_value,同时二叉搜索树的节点的类型就变为了BSTreeNode<K, V>,那么对应节点的指针类型就需要进行改变,同时在构造函数也需要增加一个函数参数value用于在初始化列表中对_value进行初始化

这是二叉搜索树的“基本单元”:

  • 每个节点同时保存 key (用于维护二叉搜索树的“左小右大”规则)和 value (实际要存储的数据);
  • 插入/查找时,通过 key 的大小比较,找到对应的节点,再操作其 value 。

同时在二叉搜索树的类模板的模板参数列表中也需要增加一个模板参数V,同时二叉搜索树的类型也改变了,变成了BSTree<K,V>,同时先前在二叉搜索树中还有对节点类型的typedef,那么对应也应该进行修改节点类型为BSTreeNode<K,V>

节点插入函数

  1. 空树直接插根:如果树是空的(根节点为空),直接把新键值对节点作为根。
  2. 遍历找插入位:从根开始,用 cur 遍历树(小往左、大往右),同时用 parent 记录 cur 的父节点;遇到重复键则插入失败。
  3. 挂到父节点下:遍历到 cur 为空时,创建新节点,根据父节点与新键的大小,把新节点挂到父节点的左/右孩子上。

核心是用循环替代递归,通过 parent 记录父节点,保证新节点能正确挂载到树中,同时遵循二叉搜索树“左小右大、无重复键”的规则。

中序遍历函数

中序遍历(递归版本),核心是利用“左→根→右”的遍历顺序,而二叉搜索树的中序遍历结果恰好是升序排列的键值对。

节点查找函数

  1. 从根节点 _root 开始,用 cur 遍历树;
  2. 对比 cur 的键和目标 key :小则往右走、大则往左走;
  3. 找到则返回对应节点指针,遍历到空( cur=nullptr )则返回 nullptr (没找到)。

它的效率很高(时间复杂度是树的高度 O(h) ,平衡树中是 O(logn) ),是二叉搜索树“快速查找”特性的体现。

节点删除函数

删除函数的核心是分场景处理待删除节点的子树情况:

  1. 左子树为空:将待删除节点的右子树挂到父节点对应位置;
  2. 右子树为空:将待删除节点的左子树挂到父节点对应位置;
  3. 左右子树都不为空:找到待删除节点右子树的最小节点(最左节点),用其键(值)替代待删除节点的键(值),再删除该最小节点(该节点的子树最多只有右子树,符合场景1/2的处理逻辑)。

该实现保证了删除操作后,二叉搜索树的结构仍合法,同时避免了树的断裂。

key-value结构的二叉搜索树删除逻辑,和仅存key的二叉搜索树删除逻辑是完全一致的——核心差异只是“节点里多存了value,但删除的判断、子树的调整规则不依赖value”。因为二叉搜索树的删除规则,只和key的大小关系(左小右大)以及节点的子树结构(左空/右空/左右都非空)有关,和节点是否存储value无关:

  • 无论是仅存key,还是key+value,删除时都是先通过key找到待删除节点;
  • 后续分“左空、右空、左右非空”的场景处理子树,逻辑完全相同;
  • 唯一的小差异:如果是key-value结构,在“左右非空”场景下,除了交换key,还需要同步交换value(你提供的代码里只交换了key,实际需要补充 swap(cur->_value, minRight->_value) )。


代码测试:

测试一,查找key对应的value

  1. 我们使用如下代码进行测试,输入中文可以得到对应的英文
  2. 退出循环使用ctrl+z,空格

测试二,统计次数

  1. 使用如下代码进行测试,统计名字出现了几次

使用 BSTree<string, int> (键是字符串,值是出现次数),利用二叉搜索树的快速查找/插入特性,高效统计每个字符串的出现次数。
遍历字符串数组 arr ,对每个字符串 str 做以下处理:

  1. 调用 Find(str) 查找当前字符串对应的节点:
  2. 如果返回 nullptr (该字符串未出现过):调用 Insert(str, 1) ,插入新节点,初始次数设为1;
  3. 如果返回非空节点(该字符串已出现过):直接修改节点的 value ( pNode->_value++ ),将出现次数加1。

调用 InOrder() (二叉搜索树的中序遍历),由于二叉搜索树的特性,最终会按字符串的字典序输出每个字符串及其对应的出现次数(如调试窗口中“苹果:9、西瓜:2、香蕉:2”)。


key模型和key_value模型的二叉搜索树的应用

key模型的二叉搜索树的应用

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

  • 应用场景是快速判断在不在的场景,例如判断书写英语单词是否写错,即将所有英文词库中的英语单词放到二叉搜索树中,英语单词是字符串形式,字符串可以比较大小,将这个书写的英语单词作为key,那么就可以使用二叉搜索树进行检索这个书写的英语单词是否在二叉搜索树中,如果在那么说明这个英语单词拼写正确,如果不在那么说明这个英语单词拼写错误

key_value模型的二叉搜索树的应用

key_value模型(KV模型):每一个key关键码都有对应的值value,即通过一个值搜索去找另一值的形式

  • 应用场景是:中英词典,检票系统等
  • 例如将英语单词和中文存放到二叉搜索树中,那么就是以英语单词作为关键码key去对应的二叉搜索树中查找对应的value中文
  • 统计人名出现的次数,例如遍历人名数组在二叉搜索树中作为关键码key进行比对,如果没有出现,那么在二叉搜索树中插入关键码key:人名,对应的值value:1,表示这个此时出现了一次,如果出现了,那么就对对应的人名对应的value加一,表示这个人名又出现了一次,这样遍历完一次人名数组中,二叉搜索树中就存储人名对应出现的次数,那么此时就可以查找人名出现的次数了

完整代码:

key结构非递归的模拟实现

namespace key
{template<class K>struct BSTNode{K _key;BSTNode<K>* _left;BSTNode<K>* _right;BSTNode(const K& key):_key(key), _left(nullptr), _right(nullptr){}};// keytemplate<class K>class 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;}bool InsertR(const K& key){return _Insert(_root, key);}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 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;elseminRightParent->_right = minRight->_right;delete minRight;}return true;}}return false;}void InOrder(){_InOrder(_root);cout << endl;}private:bool _Insert(Node*& root, const K& x){if (root == nullptr){root = new Node(x);return true;}if (root->_key < x)return _Insert(root->_right, x);else if (root->_key > x)return _Insert(root->_left, x);elsereturn false;}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}private:Node* _root = nullptr;};
}

key_value结构非递归的模拟实现

namespace key_value
{template<class K, class V>struct BSTNode{K _key;V _value;// pair<K, V> _kv;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;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;}Node* 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;}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;elseminRightParent->_right = minRight->_right;delete minRight;}return true;}}return false;}void InOrder(){_InOrder(_root);cout << endl;}private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}private:Node* _root = nullptr;};}

总结:

二叉搜索树(BST)是一种具有特定排序性质的二叉树结构,左子树节点值均小于根节点,右子树节点值均大于根节点。本文详细介绍了二叉搜索树的两种实现方式:key模型(单纯存储键值)和key_value模型(存储键值对)。重点讲解了节点的插入、查找、删除等核心操作的非递归实现方法,包括处理删除时的三种不同情况(左右子树为空或非空)。文章还探讨了二叉搜索树与中序遍历的关系,并通过具体应用场景(如词典查找、词频统计)展示了其实用价值,最后给出了完整的C++实现代码。

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

相关文章:

  • dw制作简单网站如何推广新品
  • SUSE Linux Enterprise Server 15 SP4安装步骤
  • 红帽企业 Linux 9 启动过程详解:从按下电源到登录提示符
  • 合肥建设厅网站建设一个一般网站需要多少钱
  • 麻省理工学院未来研发更高温超导体打开了新路径
  • Android studio修改app 桌面logo和名称
  • 【MCU控制 初级手札】2.1 电学基础知识 【电学基础】
  • C#1113变量类型
  • RabbitMq消费消息遇到的坑
  • SAP FICO应付账款账龄分析表
  • Pinia Store 生命周期与状态持久性详解
  • 大数据时代时序数据库选型指南:为何Apache IoTDB是最优解
  • 做网站的一个专题在线上传图片生成链接
  • 图论专题(三):“可达性”的探索——DFS/BFS 勇闯「钥匙和房间」
  • 图论专题(一):Hello, Graph! 掌握“建图”与“遍历”的灵魂
  • 做彩票网站能挣到钱吗中国最好的购物平台
  • 南京做网站群的公司岳西县住房和城乡建设局网站
  • 前端高频面试题之Vue(高级篇)
  • 【附源码】告别静态密码!openHiTLS 开源一次性密码协议(HOTP/TOTP),推动动态认证普及
  • UniApp 小程序中使用地图组件
  • 25华北理工大学考情数据分析
  • Unity Shader Graph 3D 实例 - 基础的模型贴图渲染
  • 17.TCP编程
  • Java高级特性:单元测试、反射、注解、动态代理
  • python机器学习工程化demo(包含训练模型,预测数据,模型列表,模型详情,删除模型)支持线性回归、逻辑回归、决策树、SVC、随机森林等模型
  • 逻辑回归在个性化推荐中的原理与应用
  • 织梦网站后台怎么登陆郑州知名做网站公司有哪些
  • 免费做网站的软件跨境电商自建站平台
  • 本机oracle连接延时41970 毫秒
  • 不到一块钱的带USB 2.4G收发 SOC芯片,集成2.4G射频 32位MCU