C++中二叉搜索树的模拟实现(二叉搜索树是map,set的底层原理)

搜索二叉树

定义

搜索二叉树:左子树小于根,右子树大于根.搜索二叉树的中序序列是升序的.所以对于二叉树而言,它的左子树和右子数都是二叉搜索树

下图就是二叉搜索树
在这里插入图片描述

二叉搜索树的性质:

  • 二叉搜索树的中序遍历出的数据是有序的,并且二叉树搜索树在查找某个数的时候,一般情况下的时间复杂度是O(log2(N))级别的.
  • 二叉搜索树中是没有值相同的节点的,否则无法构成二叉搜索树.

节点的定义

二叉树和别的树的区别就是各个节点的排列有了区别,节点中存储的内容还是不会变的,仍然是左右指针,和一个值.

template<class K>
struct BinarySearchTreeNode
{typedef BinarySearchTreeNode<K> Node;Node* _left;Node* _right;K _key;BinarySearchTreeNode(const K& key): _left(nullptr), _right(nullptr), _key(key){}
};

节点的构造方法

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

二叉搜索树的创建

二叉搜索树的创建首先只需要一个根节点即可,后续插入节点或者删除节点时,保持住连接关系就好.

template<class T>
class BinarySearchTree
{typedef BinarySearchTreeNode<K> Node;private:Node* _root = nullptr;public:// 各种函数
};

注意:后边的这些方法都是写在类的public中的.

向树中插入节点

例如:插入节点A的时候,要判断A中的key值和树中根节点开始,依次比较key值,我们定义一个cur指针,用于为新来的节点找到合适的插入位置,假如A节点的key值<cur节点的key值,那么就cur就向左树开始遍历.假如A的key值和cur的key值相同,直接返回.假如A的key值大于cur的key值,cur就向右数遍历.最终cur的位置就是能插入数据的地方,但是cur的值最后是为空的,那么我们如何将cur处的值替换为这个A节点呢?换句话说,如何让cur的父节点指向这个A节点呢?答案是:我们在cur向下一个节点行进之前,先保存当前节点的指针,也就是保存好cur的父节点的值.

但是A节点最终是链接在父节点的左边还是在父节点的右边呢??这个只能通过保存的父节点中保存的值进行判断.若A节点的值小于父节点,那么就链接在父节点的左边,否则链接在父节点的右边.
在这里插入图片描述

代码实现:

bool insert(const K& key)
{// 插入节点之前,检查是不是空树if(_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = _root;while(cur){if(key<cur->_key){parent = cur;cur = cur->_left;}else if(key>cur->_key){parent = cur;cur = cur->_right;}else{// 值不能相同直接返回return false;}}if(parent->_key<key){parent->_left = new Node(key);}else{parent->_right = new Node(key);}return true;
}

查找元素

查找元素就比较简单了,要查找的值小于cur的当前值,那么就向左树查找,若大于当前值,就向右数查找.

代码实现:

bool find(const K& key)
{Node* cur = _root;while(cur){if(key<cur->_key){cur = cur->_left;		}else if(key>cur->_key){cur = cur->_right;}else{return true;}}return false;
}

删除元素

二叉搜素树中,删除节点是最比较复杂的.分为了3种情况

  1. 要被删除的目标节点的左树是空:每次cur指针在找目标节点时,每次cur迭代之前,都需要记录cur的当前位置,也就是用parent指针来记录.

    • 当删除的节点是parent的右边时:就需要parent的右指针指向目标节点的右子树.
    • 当删除的节点是parent的左边时:就需要parent的左指针指向目标节点的右子树.
      在这里插入图片描述
  2. 要被删除的节点的右树是空:每次cur指针在找目标节点时,每次cur迭代之前,都需要记录cur的当前位置,也就是用parent指针来记录.

    • 当删除的节点是parent的右边时:就需要parent的右指针指向目标节点的左子树.
    • 当删除的节点是parent的左边时:就需要parent的左指针指向目标节点的左子树.
      在这里插入图片描述
  3. 要被删除的节点的左右都不是空的时候:
    此时就需要用替换法了,
    例如:我们要删除下图中的值为8的节点.删除节点但是不能破环二叉搜索树的结构,所以就需要找到一个值在3和10的节点来替换这里的值为8的节点.那么这值如何找呢?由于二叉搜索树的结构可知,左树的值小于根的值,右树的值总是大于根的值.所以我们可以在左树中找到最大的值或者是在右树中找到最小的值(这两个值的任意一个值都是符合要求的,即大于3小于10的)来替换要被删除节点的位置的值,如下图,就可以将7复制到8这个位置,紧接着删除原本的7所在的节点,就删除成功了.

    注意:删除原本值为7的节点时,一定属于第一种和第二种情况之一,因为:左树的最大值的右指针一定为空,右数的最小值的左树一定为空.
    在这里插入图片描述

代码实现:

bool erase(const K& key)
{Node* cur = _root;Node* parent = _root;while(cur){if(key<cur->_key){parent = cur;cur = cur->_left;}else if(key>cur->_key){parent  = cur;cur = cur ->_right;}else{// 删除的节点的左树为空if(cur->_left == nullptr){if(cur == _root){_root = _root->_right;}else{if(parent->_right == cur){parent ->_right = cur->_right;}else{parent->_left = cur->_right;}}delete cur;return true;}// 删除的节点的右树为空else if(cur->_right == nullptr){if(cur == _root ){_root = _root ->_left;}else{if(parent->_right == cur){parent->_right = cur->_left;}else{parent->_left = cur->_left;}}delete cur;return true;}// 左右都不为空,替换法else{// 以右边的最小值为例子Node* rightMinParent = cur;Node* rightMin = cur->_right;while(rightMin->_left){rightMin = rightMin ->_left;}cur->_key = rightMin->_key;if(rightMinParent->_left == rightMin){rightMinParent->_left = rightMin->_right;}else{rightMinParent->_right = rightMin->_right;}delete rightMin;return true;}}}return false;
}

二叉树的中序遍历

由于类的成员函数不能递归调用,所以创建一个私有函数_Inorder,接着在public中定义Inorder方法,调用这个_Inorder犯法即可.

void Inorder()
{_Inorder(_root);
}
void _Inorder(Node* root)
{if(root==nullptr) return;_Inorder(root->_left);cout<<root->_key<<endl;_Inorder(root->_right);
}

二叉搜索树的递归找数字

bool _Find(Node* root,const K& key)
{if(root == nullptr) return false;else if(root->_key == key) return true;else if (root->_key < key){_Find(root->_right, key);}else{_Find(root->_left, key);}
}

二叉搜索树删除元素的另一种方法

这里的root定义成引用即可,root必定是节点的左指针或者右指针的引用.这里直接改变引用的值即可.就不用找父节点了.更方便一点.

		bool _Erase(Node*& root, const K& key){if (root == nullptr){return false;}if (root->_key < key){return _Erase(root->_right, key);}else if (root->_key > key){return _Erase(root->_left, key);}else{Node* del = root;if (root->_right == nullptr){root = root->_left;}else if (root->_left == nullptr){root = root->_right;}else{// 替换法Node* rightMin = root->_right;while (rightMin->_left){rightMin = rightMin->_left;}swap(rightMin->_key, root->_key);// 将当前root位置和rightMin位置的值进行交换,接着在root的右边的树中删除keyreturn _Erase(root->_right, key);}delete del;return true;}}

二叉搜索树插入数据的第二种方式

// 注意这里的&是不可少的,不然要使用二级指针进行操作了.bool _Insert(Node*& root, Node* parent, const K& key){if (root == nullptr){root = new Node(key);}if (root->_key > key){return _Insert(root->_left, parent, key);}else if (root->_key < key){return _Insert(root->_right, parent, key);}else if (root->_key == key){return false;}}

二叉搜索树的构造方法

拷贝构造

先拷贝根,再拷贝左右子树

Node* Copy(Node* root)
{if(root == nullptr)return nullptr;Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;
}
// 在构造函数中:
BinarySearchTree(BinarySearchTree<K>& t){this->_root = Copy(t._root);
}

赋值拷贝

注意:要使用如下这种方法,参数必须是类实体,不能是类引用,返回值必须是类引用.

BinarySearchTree<K>& operator=(const BinarySearchTree<K> t)
{swap(t._root,this->_root);return *this;
}

默认构造

BinarySearchTree() = default;

析构函数

先写一个destroy函数

~BinarySearchTree()
{Destroy(_root);
}
void Destroy(Node* root)
{if(root== nullptr)return ;Destroy(root->_left);Destroy(root->_right);delete root;
}

源码

#include<iostream>
using namespace std;
namespace key 
{template<class K>struct BinarySearchTreeNode {typedef BinarySearchTreeNode<K> Node;Node* _left;Node* _right;K _key;BinarySearchTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}};template<class K>class BinarySearchTree {typedef BinarySearchTreeNode<K> Node;public:bool Erase(const K& key) // 删除指定的节点.{Node* cur = _root;Node* parent = _root;while (cur){if (cur->_key == key){if (cur->_right == nullptr){if (cur == _root) {_root = _root->_left;}else{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;return true;}else if (cur->_left == nullptr){if (cur == _root) {_root = _root->_right;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;return true;}// 左右都不为空的时候使用替换法else{Node* rightMinParent = cur; // 这里要用cur进行初始化// 右边的最小值Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMin == rightMinParent->_left)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;delete rightMin;return true;}}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{parent = cur;cur = cur->_right;}}return false;}void Inorder(){_Inorder(_root);}bool Insert(const K& k){if (_root == nullptr){_root = new Node(k);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_key == k){return false;}else if (cur->_key > k){parent = cur;cur = cur->_left;}else{parent = cur;cur = cur->_right;}}//保存父节点if (k < parent->_key){parent->_left = new Node(k);}else{parent->_right = new Node(k);}return true;}bool Find(const K& k){Node* cur = _root;while (cur){if (cur->_key == k){return true;}else if (cur->_key > k){cur = cur->_left;}else{cur = cur->_right;}}return false;}bool FindR(const K& key) //递归找数字{return _Find(_root, key);}bool InsertR(const K& key){return _Insert(_root, _root, key);}bool EraseR(const K& key){return _Erase(_root, key);}~BinarySearchTree(){Destroy(_root);}// 自动生成默认的构造BinarySearchTree() = default;// 拷贝构造BinarySearchTree(const BinarySearchTree<K>& t){this->_root = Copy(t._root);}// 赋值拷贝BinarySearchTree<K>& operator=(const BinarySearchTree<K> t){swap(_root, t._root);return *this;}private:Node* Copy(const Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}void Destroy(Node*& root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}bool _Erase(Node*& root, const K& key){if (root == nullptr){return false;}if (root->_key < key){return _Erase(root->_right, key);}else if (root->_key > key){return _Erase(root->_left, key);}else{Node* del = root;if (root->_right == nullptr){root = root->_left;}else if (root->_left == nullptr){root = root->_right;}else{// 替换法Node* rightMin = root->_right;while (rightMin->_left){rightMin = rightMin->_left;}swap(rightMin->_key, root->_key);// 将当前root位置和rightMin位置的值进行交换,接着在root的右边的树中删除keyreturn _Erase(root->_right, key);}delete del;return true;}}// 注意这里的&是不可少的,不然要使用二级指针进行操作了.bool _Insert(Node*& root, Node* parent, const K& key){if (root == nullptr){root = new Node(key);}if (root->_key > key){return _Insert(root->_left, parent, key);}else if (root->_key < key){return _Insert(root->_right, parent, key);}else if (root->_key == key){return false;}}Node* _root = nullptr;void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);cout << root->_key << " ";_Inorder(root->_right);}bool _Find(Node* root, const K& key)        {if (root == nullptr)return false;        else if (root->_key == key)return true;        else if (root->_key < key)        {_Find(root->_right, key);        }else        {_Find(root->_left, key);        }}};
}

结束

本篇文章就到这里就结束啦,若有不足,请在评论区指正,下期再见,

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/591619.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于muduo网络库实现的集群聊天服务器

目录 项目内容开发环境安装说明技术介绍项目目录数据库设计项目介绍启动服务器启动客户端注册账号登录成功一对一聊天业务创建群聊业务加入群聊业务群聊业务添加好友业务离线消息存储业务 特殊说明Gitee地址 &#xff01;&#xff01;&#xff01;项目是照着腾讯课堂施磊老师的…

文件操作讲解

目录 一.为什么使用文件 二.什么是文件 2.1程序文件 2.2数据文件 2.3文件名 三.文本文件和二进制文件 fwrite函数 fclose函数 四.文件的打开和关闭 4.1流和标准流 4.2文件指针 4.3文件的打开和关闭 五.文件的顺序读写 5.1文件的顺序读写函数 5.1.1fgetc函数…

OpenCV-python安装教程

先安装opencv-contrib-python pip install opencv-contrib-python 再换源安装opencv-python pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple 如果出现 使用这个&#xff0c;3.6环境下不能安装opencv的最新版本 pip install opencv-python4.5.5.62…

基于SpringBoot+Vue的电影票管理系统的设计和实现【附源码】

1、系统演示视频&#xff08;演示视频&#xff09; 2、需要交流和学习请联系

机台数据传输共享存在哪些问题?机台数据管控怎么做?

一些金融机构、大型制造业以及晶圆制造厂里面&#xff0c;都会存在大量的机台设备&#xff0c;这些机台会产⽣庞⼤⽽属性不同的数据&#xff0c;这些数据需要定期的进行采集和利用。机台数据在传输分享过程中&#xff0c;会面临各种问题和调整&#xff0c;所以需要做好机台数据…

CSS——精灵图

CSS——精灵图 目录 CSS——精灵图什么是精灵图&#xff1f;导入精灵图裁剪精灵图使用精灵图方式1方式2 什么是精灵图&#xff1f; 精灵图&#xff08;Spritesheet&#xff09;是指将多个小图标、图像或动画合并到一个大图像中的技术。在网页设计和游戏开发中&#xff0c;精灵…

使用 CloudDM 操作 PostgrgSQL 数据库

CloudDM 简介 CloudDM 是 ClouGence 公司推出的一款一站式数据库管理工具&#xff0c;使用它可以方便地访问和管理 MySQL、Oracle、PostgreSQL、阿里云 RDS、Greenplum、TiDB、Redis、StarRocks、Doris、SelectDB、SQL SERVER、ClickHouse、OceanBase 、PolarDB-X 、IBM Db2 等…

AI结合机器人的入门级仿真环境有哪些?

由于使用真实的机器人开发和测试应用程序既昂贵又费时&#xff0c;因此仿真已成为机器人应用程序开发中越来越重要的部分。在部署到机器人之前在仿真中验证应用程序可以通过尽早发现潜在问题来缩短迭代时间。通过模拟&#xff0c;还可以更轻松地测试在现实世界中可能过于危险的…

图的深度优先遍历DFS得到各节点的度

在 一文中&#xff0c;我们通过了广度优先搜索来得到图结构中各结点的度&#xff0c;在这一篇文章中&#xff0c;我们要通过深度优先搜索来得到图结构中各结点的度。 初始化 初始化&#xff0c;在下面的代码中&#xff0c;我们创建了一个具有6个结点的图结构&#xff0c;以及…

MHA高可用-解决MySQL主从复制的单点问题

目录 一、MHA的介绍 1&#xff0e;什么是 MHA 2&#xff0e;MHA 的组成 2.1 MHA Node&#xff08;数据节点&#xff09; 2.2 MHA Manager&#xff08;管理节点&#xff09; 3&#xff0e;MHA 的特点 4. MHA工作原理总结如下&#xff1a; 二、搭建 MySQL MHA 实验环境 …

NoSQL之Redis

目录 一、关系型数据库与非关系型数据库 1.关系数据库 2.非关系数据库 2.1非关系型数据库产生背景 3.关系型数据库与非关系型数据区别 &#xff08;1&#xff09;数据存储方式不同 &#xff08;2&#xff09;扩展方式不同 &#xff08;3&#xff09;对事物性的支持不同 …

VSCode常用修改默认设置(settings.json)

❓ 问题1 我现在在vscode中鼠标选中某个单词&#xff0c;相同的单词都会自动出现一个高亮背景色&#xff0c;我需要怎么关闭这个功能呢&#xff1f; ⚠️ 注意 selectionHighlight 这个是鼠标双击后的高亮匹配&#xff0c;可以保留默认开启的配置&#xff0c;不用去改它。 …