【C++】二叉搜索树的模拟实现

🌇个人主页:平凡的小苏
📚学习格言:命运给你一个低的起点,是想看你精彩的翻盘,而不是让你自甘堕落,脚下的路虽然难走,但我还能走,比起向阳而生,我更想尝试逆风翻盘
🛸C++专栏C++内功修炼基地
> 家人们更新不易,你们的👍点赞👍和⭐关注⭐真的对我真重要,各位路 过的友友麻烦多多点赞关注。 欢迎你们的私信提问,感谢你们的转发! 关注我,关注我,关注我,你们将会看到更多的优质内容!!

在这里插入图片描述

一、二叉搜索树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

1.若他的左子树不为空,则左子树上所有节点的值小于根节点的值

2.若他的右子树不为空,则右子树上所有节点的值大于根节点的值

3.它的左右子树也分别为二叉搜索树

二叉搜索树的结构如下

template <class K,class V>
struct BSTreeNode//生成二叉搜索树的节点
{BSTreeNode<K,V>* _left;BSTreeNode<K,V>* _right;K _key;V _value;BSTreeNode(const K& key, const V& value):_left(nullptr),_right(nullptr),_key(key),_value(value){}
};template<class K,class V>
class BSTree//表示整颗二叉搜索树
{typedef BSTreeNode<K,V> Node;
public:BSTree():_root(nullptr){}
private:Node* _root;
};

二、二叉搜索树的查找

a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。

b、最多查找高度次,走到到空,还没找到,这个值不存在。

  • 插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能.

  • 对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二

    叉搜索树的深度的函数,即结点越深,则比较次数越多。

    但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:如下图所示
    在这里插入图片描述

    最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:logN

    最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:O(N)

1、递归版本的二叉搜索树的查找

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

2、非递归版本的二叉搜索树的查找

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

三、二叉搜索树的插入

二叉搜索树的插入需要考虑插入后,需要维持二叉搜索树的形态。

1、二叉搜索树的非递归写法

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->_left = cur;}else{parent->_right = cur;}return true;
}

注意:这里要记录一个父亲节点,来确保是插入在左侧还是插入在右侧

2、二叉搜索树的递归写法

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

递归写法巧就巧在形参是指针的引用,例如我现在要插入9,下层的root是上一层root->_left的别名, 下层root = new Node(key);即为上一层root->_left=new Node(key);这样插入节点就自动和父节点连接上了。

在这里插入图片描述

四、二叉搜索树的删除

二叉搜索树的节点进行删除后,同样需要维持二叉搜索树的形态。

二叉搜索树的删除无非是三种情况:
在这里插入图片描述

1、二叉搜索树非递归版本的删除

bool Erase(const K& key)
{Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else{if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}}else{Node* parent = cur;Node* leftMax = cur->_left;while (leftMax->_right)//找到左树的最大节点,即在左树的最右边{parent = leftMax;leftMax = leftMax->_right;}swap(cur->_key, leftMax->_key);//交换数据//如上图,如果删除的是3,那么就会和1交换,即是如下这种情况,需要将3的左连接到1的左if (parent->_left == leftMax){parent->_left = leftMax->_left;}//如上图如果删除的是8,则会和6交换,则是如下这种情况,需要将10的有连接到6的左else{parent->_right = leftMax->_left;}cur = leftMax;}delete cur;return true;}}return false;
}

1、先通过二叉搜索树的性质找到要删除的节点;

2、找到需要删除的节点后,分三种情况进行讨论:

​ 一、被删除节点的左孩子为空,除了cur等于根节点情况下,其他情况下,父节点的孩子指针由指向被删除节点转为指向被删除节点的右孩子。(如图删除14)

在这里插入图片描述

二、被删除节点的左孩子存在但右孩子为空,除了cur等于根节点情况下,其他情况下,父节点的孩子指针由指向被删除节点转为指向被删除节点的左孩子。(如图删除9)

在这里插入图片描述

三、被删除的节点均不为空,可以选用左树最大节点或者右树最小节点对被删除节点进行值替换,问题转化为第一种或第二种情况。(详见代码注释)

2、二叉搜索树递归版本的删除

bool _EraseR(Node*& root, const K& key)
{if (root == nullptr){return false;}if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else{Node* del = root;if (root->_left == nullptr){root = root->_right;//因为是引用直接修改它的指向就可以}else if (root->_right == nullptr){root = root->_left;}else{Node* leftmax = root->_left;while (leftmax->_right)//找到左树的最大节点{leftmax = leftmax->_right;}swap(root->_key, leftmax->_key);//交换swap(root->_value, leftmax->_value);return _EraseR(root->_left, key);//再次进行递归删除,这样就转换成了情况1或者情况2 的删除}delete del;return true;}return false;
}

注意:由于引用的语法,循环版本不能使用引用,因为递归使其每个栈帧不一样了,所以可以使用引用

五、二叉搜索树的源码

1、key/value的搜索模型(节点既存key又存value)

key/value搜索模型指每一个key值,都有与之对应的value值,例如英汉互译,一个英文单词可以对应一个翻译字符串。该模型还可以用于统计相同内容出现次数。

2、key/value模型的源码

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <iostream>
#include <algorithm>
using namespace std;template <class K,class V>
struct BSTreeNode//生成二叉搜索树的节点
{BSTreeNode<K,V>* _left;BSTreeNode<K,V>* _right;K _key;V _value;BSTreeNode(const K& key, const V& value):_left(nullptr),_right(nullptr),_key(key),_value(value){}
};template<class K,class V>
class BSTree//表示整颗二叉搜索树
{typedef BSTreeNode<K,V> Node;
public:BSTree():_root(nullptr){}BSTree(const BSTree<K,V>& t){_root = Copy(t._root);}BSTree<K, V>& operator=(BSTree<K, V> t){swap(_root, t._root);return *this;}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->_left = cur;}else{parent->_right = cur;}return true;}void InOrder(){_InOrder(_root);cout << endl;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_key > key){cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}else{return cur;}}return nullptr;}bool Erase(const K& key){Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else{if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (parent->_right == cur){parent->_right = cur->_right;}else{parent->_left = cur->_right;}}}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}}else{Node* parent = cur;Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;}swap(cur->_key, leftMax->_key);if (parent->_left == leftMax){parent->_left = leftMax->_left;}else{parent->_right = leftMax->_left;}cur = leftMax;}delete cur;return true;}}return false;}Node* FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key, const V& value){return _InsertR(_root, key,value);}bool EraseR(const K& key){return _EraseR(_root, key);}~BSTree(){Destroy(_root);}
private:void Destroy(Node*& root){if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;}Node* Copy(Node* root){if (root == nullptr){return nullptr;}Node* copynewnode = new Node(root->_key,root->_value);copynewnode->_left = Copy(root->_left);copynewnode->_right = Copy(root->_right);return copynewnode;}bool _EraseR(Node*& root, const K& key){if (root == nullptr){return false;}if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else{Node* del = root;if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{Node* leftmax = root->_left;while (leftmax->_right){leftmax = leftmax->_right;}swap(root->_key, leftmax->_key);swap(root->_value, leftmax->_value);return _EraseR(root->_left, key);}delete del;return true;}return false;}bool _InsertR(Node*& root, const K& key,const V& value){if (root == nullptr){root = new Node(key,value);return true;}if (root->_key < key){return _InsertR(root->_right, key,value);}else if (root->_key > key){return _InsertR(root->_left, key,value);}else{return false;}}Node* _FindR(Node* root, const K& key){if (root == nullptr){return nullptr;}if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return root;}}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}
private:Node* _root;
};void BSTreeTest()
{string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };BSTree<string, int> countTree;for (auto& str : arr){auto ret = countTree.FindR(str);if (ret == nullptr){countTree.InsertR(str, 1);}else{ret->_value++;}}countTree.InOrder();
}

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

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

相关文章

AI介绍——chat gpt/文心一言/claude/bard/星火大模型/bing AI

AI体验 1. AI 介绍&#xff08;注册和使用&#xff09;1.1 Chat GPT1.2 文心一言1.3 Slack 上的 Claude1.3.1 Claude 介绍1.3.2 Claude 使用 1.4 Google的Bard1.4.1 Bard 介绍1.4.2 Bard 使用 1.5 科大讯飞的星火大模型1.5.1 星火大模型 介绍1.5.2 星火大模型 使用 1.6 new bin…

iOS Viper架构(中文版)【看懂这篇就够了】

完整源码地址 一、iOS_Viper iOS的Viper架构&#xff0c;作为一个从业多年的iOS开发者&#xff0c;我个人认为应该要会一点viper 二、前言 viper的设计模式在iOS开发中不流行&#xff0c;甚至是Swift中&#xff0c;也没有用&#xff0c;我认为比较可惜。作为iOSer,当你掌握…

2023年第四届“华数杯”数学建模思路 - 案例:随机森林

## 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 什么是随机森林&#xff1f; 随机森林属于 集成学习 中的 Bagging&#xff08;Bootstrap AGgregation 的简称&#xff09; 方法。如果用图来表示他们之…

electron+vue+ts窗口间通信

文章目录 一. 目的二.逻辑分析三. 代码示例 "types/node": "^20.3.1","vitejs/plugin-vue": "^4.1.0","vueuse/electron": "^10.2.1","electron": "^25.2.0","electron-packager":…

激荡十三年,消费金融进入“体验争夺战”的下半场

消费金融行业又开始涌动着变局。 先是一些老玩家悬着的心&#xff0c;终于落地。过去两年&#xff0c;消费金融是蚂蚁集团整改的关键板块。前不久&#xff0c;蚂蚁集团被监管部门开出71.23亿元的“罚单”&#xff0c;市场普遍认为这是利空出尽的信号。 与此同时&#xff0c;竞…

EventBus 开源库学习(三)

源码细节阅读 上一节根据EventBus的使用流程把实现源码大体梳理了一遍&#xff0c;因为精力有限&#xff0c;所以看源码都是根据实现过程把基本流程看下&#xff0c;中间实现细节先忽略&#xff0c;否则越看越深不容易把握大体思路&#xff0c;这节把一些细节的部分再看看。 …

OA办公自动化系统设计与实现(论文+源码)_kaic

摘要 随着信息化建设的日益深入&#xff0c;无论是政府还是企事业单位&#xff0c;部门之间的信息沟通与协调工作越来越重要。人们迫切需要一个能充分利用网络优势&#xff0c;并可以管理企业的各种重要信息的软件平台&#xff0c;利用该平台快速建立自己的信息网络和办公管理系…

C语言案例 按序输出多个整数-03

难度2复杂度3 题目&#xff1a;输入多个整数&#xff0c;按从小到大的顺序输出 步骤一&#xff1a;定义程序的目标 编写一个C程序&#xff0c;随机输入整数&#xff0c;按照从小到大的顺序输出 步骤二&#xff1a;程序设计 整个C程序由三大模块组成&#xff0c;第一个模块使…

利用XSS在线平台获取用户cookie

//XSS弹窗&#xff1a; <script>alert("xss")</script> XSS漏洞&#xff1a; //XSS弹窗&#xff1a; <script>alert("xss")</script> //XSS在线平台&#xff1a; <ScRipT sRc//7ix7kigpovxdbtd32fuspgffmtmufo3wwzgnzaltddewtb…

应急响应-勒索病毒的处理思路

0x00 关于勒索病毒的描述 勒索病毒入侵方式&#xff1a;服务弱口令&#xff0c;未授权&#xff0c;邮件钓鱼&#xff0c;程序木马植入&#xff0c;系统漏洞等 勒索病毒的危害&#xff1a;主机文件被加密&#xff0c;且几乎难以解密&#xff0c;对主机上的文件信息以及重要资产…

C# PaddleDetection 版面分析

效果 项目 代码 using OpenCvSharp; using OpenCvSharp.Extensions; using Sdcb.PaddleDetection; using Sdcb.PaddleInference; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Lin…

2023-08-06 LeetCode每日一题(24. 两两交换链表中的节点)

2023-08-06每日一题 一、题目编号 24. 两两交换链表中的节点二、题目链接 点击跳转到题目位置 三、题目描述 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0…