【C++】搜索二叉树(保姆教程)

🍅二叉树底层是堆,之前学习的简单二叉树不是大堆就是小堆,今天是二叉树进阶,一定要好好掌握!

目录

☃️1.搜索二叉树介绍

☃️2.底层实现

☃️3.key模型和key,value模型


☃️1.搜索二叉树介绍

右>根>左   这样结构的树就是搜索二叉树,显然,他的主要功能:搜索,因为想要找到x,首先和根比较,如果比根大,那么走右子树(右子树所有节点值都比root大),否则走左子树(左子树所有节点值比root小)

次要功能:排序

☃️2.底层实现

首先我们定义一个命名域,然后把下面写的所有代码封装进去

先定义一个树的节点,他是一个class也行(只不过应该写成public)那还不如写成struct,然后要保存做左孩子和右孩子,还要保存自己的节点值,并且附上初始化,方便后面用val去new,初始化的时候直接初始化列表即可,注意初始化列表的格式

然后就是整个树的类

首先把类型重命名一下,每个树的节点类型应该是node<class K>* 

重命名的时候带不带*都行,我这里没带,后面每个节点需要写成Node*

带上*,后面每个节点写成Node即可

类里面的成员变量只有根节点

 

 首先写一个构造函数

然后插入(非递归)

搜索树不允许插入重复的节点,所以要写成bool类型,不可以是void 

 我们先捋一下思路

发现这个就是来回比较的过程,如果找到了要插入的位置,还需要知道他的父节点(为了链接)

 

template <class K>

		class BST
		{
			typedef node<K> Node;
		public:
			BST()
				:_root(nullptr)
			{}
			bool insert(K val)
			{
				if (!_root) //空树,直接new然后返回true
				{
					_root = new Node(val);
					return true;
				}
				Node* parent = _root; //父节点
				Node* cur = _root;//遍历指针
				while (cur) //不走到空一直找
				{
					if (val > cur->_val) //要插入的更大,走右树
					{
						parent = cur; //先把孩子给给父亲
						cur = cur->_right; //孩子往后走
					}
					else if (val < cur->_val)//要插入的更小,走左树
					{
						parent = cur; 
						cur = cur->_left;
					}
					else
					{
						//说明存在这个节点,不可以插入
						return false;
					}
				}
				Node* newnode = new Node(val); //找到要插入的位置,new
				if (parent->_val > val) //判断往父节点的左/右插,节点val更大,说明要插入的更小,应该在左孩子
				{
					parent->_left = newnode;
				}
				else
				{
					parent->_right = newnode; //否则在右孩子
				}

				return true; //插入成功
			}

 还要查找节点函数

具体的搜索思想和刚才是一样的

	bool find(const K& val)
			{
				if (!_root) return false; //空树肯定找不到
				Node* cur = _root; //遍历指针
				while (cur)
				{
					if (cur->_val == val) return true; //找到啦
					else if (val < cur->_val) //搜索思想,不再赘述
					{
						cur = cur->_left;
					}
					else
					{
						cur = cur->_right;
					}
				}
				return false;
			}

 

中序遍历,打印二叉树

注意:这时候需要用到节点,但是根节点是私有的,我们可以写一个getroot的函数,但是最好不要破坏封装性,在函数里调用一个私有成员函数

void InOrder()
			{
				if (!_root) return;
				_InOrder(_root);//调用私有成员函数
				cout << endl; //打印之后换行
			}

 私有成员函数:

 

	void _InOrder(Node* root)
			{
				if (!root) return; //截断条件
				_InOrder(root->_left); //左
				cout << root->_val << " ";//根
				_InOrder(root->_right);//右
			}

 删除值为val的节点,如果没有返回false

要删除一个节点,要把他的父节点和其他节点连接上,所以也要保留父亲

先看搜索节点的部分 

 

判断一下是不是因为没找到目标节点才退出的

正常删除

首先要删节点左为空的情况

 

要删节点右为空,完全类似作为空,不赘述

 

 左右都不为空(删的不是叶子节点)

 

 思考:要是删的是root,不能写成,否则就是空指针的解引用

 最后完整的删除代码

bool Erase(const K& val)
			{
				if (!_root) return false; //空树不删除
				Node* cur = _root; //遍历指针
				Node* parent = _root; //父节点

				while (cur)
				{
					//首先找到要删除的节点,找不到直接false
					if (val > cur->_val) //搜索逻辑
					{
						parent = cur;
						cur = cur->_right;
					}
					else if (val < cur->_val)
					{
						parent = cur;
						cur = cur->_left;
					}
					else
					{
						break;
					}
				}
				if (!cur) 
					return false;//如果循环条件结束是因为每找到节点,直接false
				//左为空
				//右为空
				//都不为空
				if (!cur->_left)
				{
					if (cur == _root)
					{
						_root = _root->_right;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
					}
					delete cur;
				}

				else if (!cur->_right)
				{
					if (cur == _root)
					{
						_root = _root->_left;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
				}
				else
				{
					Node* parent = cur; //保存父节点 
					Node* minRight = cur->_right; //找右树最小
					while (minRight->_left) //找小往左树走
					{
						//此时的父节点是要删除minRight的父节点
						parent = minRight; 
						minRight = minRight->_left;
					}
					cur->_val = minRight->_val; //找到右树最小,把他和要删除节点值交换
				
					if (minRight == parent->_left) //如果最小在父亲的左侧
					{
						parent->_left = minRight->_right; 
						//父亲左连minRight右,因为minRight的左一定是NULL(他是最小的)如果右节点还有一定要连上
					}
					else
					{
						parent->_right = minRight->_right;//父亲右连minRight右
					}
					delete minRight;
				}


				return true;
			}

左右都不为空时找左树最大也可以

	else
				{
					Node* parent = cur; //保存父节点 
					Node* maxleft = cur->_left;
					while (maxleft->_right)
					{
						parent = maxleft;
						maxleft = maxleft->_right;
					}
					cur->_val = maxleft->_val;
					if (maxleft == parent->_left)
						parent->_left = maxleft->_left;
					else
						parent->_right = maxleft->_left;
					delete maxleft;
				}

Find的递归版本,递归需要传根,所以还是写成调用私有吧成员函数的形式

 

	bool  _FindR(Node* root, const K& val)
			{
				if (!root) return false;
				if (root->_val > val)
				{
					//左
					_FindR(root->_left, val);
				}
				else if (root->_val < val)
				{
					_FindR(root->_right, val);
				}
				else
				{
					return true;
				}
			}

 插入的递归版本

看一个错误写法 

 

 问题在图片中写出了,无法连接

那么怎么做?给节点加上引用

 比如要插入2

 

可以递归找到插入位置在1的右

此时root是nullptr,但也是1右孩子的别名,给root开空间,就是给1右孩子开空间

bool _InsertR(Node*& root, const K& val)
			{
				if (!root) //root是他父节点的某个孩子的别名
				{
					root = new Node(val);//把孩子new,自然和父亲有链接
					return true;
				}
				if (root->_val > val)
					_InsertR(root->_left, val);
				else if (root->_val < val)
					_InsertR(root->_right, val);
				else
					return false;
			}

删除递归

 

整体思路和刚才一样的吗,只不过都不为空稍有变化 

 

 

 同样的找左树最大当替死鬼也可以

写一个copy函数(需要根,写成调用私有成员函数)

 

Node* _Copy(Node* root)
			{
				if (!root) return nullptr;

				Node* newnode = new Node(root->_val);
				newnode->_left = _Copy(root->_left);
				newnode->_right = _Copy(root->_right);

				return newnode;
			}

 还有不销毁函数

	void _Delete(Node* root, const K& val)
			{
				if (!root)
					return;
				_Delete(root->_left, val);
				_Delete(root->_right, val);
				delete root;
			}

 基本上最难最重要的就是删除函数,我们已经实现的差不多啦

最后补全析构函数和构造函数

 

	        BST()
				:_root(nullptr)
			{}
			BST(const BST<K>& t)
			{
				_root = Copy(t._root);
			}

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

 

☃️3.key模型和key,value模型

key模型就是刚才讲的搜索树,每个节点的val就是模型里的key,

key value模型是用一个值找另一个信息的过程

比如用手机号查快递,用学号查分数

具体的代码和刚才没有很大区别,只是多了一个模板参数,和节点的变量值

namespace KV
{
	template<class K,class V>
	struct BTNode
	{
		BTNode<K, V>* _left;
		BTNode<K, V>* _right;
		V _val;
		K _key;
		BTNode(const K& key, const V& val)
			:_key(key)
			,_val(val)
			,_right(nullptr)
			,_left(nullptr)
		{}
	};
	template<class K, class V>

	class Tree
	{
		typedef  BTNode<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;
		}

		void Inorder()
		{
			_Inorder(_root);
		}

		void _Inorder(Node* root)
		{
			if (root == nullptr)
				return;

			_Inorder(root->_left);
			cout << root->_key << ":" << root->_val << endl;
			_Inorder(root->_right);
		}
	private:
		Node* _root = nullptr;
	};

比如他可以帮我统计一个字符串数组中每个字符串出现的个数

 

k模型和kv模型是解决问题的两种不同手段 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Selenium基础篇之环境准备

文章目录前言一、Selenium是什么&#xff1f;二、浏览器驱动下载1.安装一个支持的浏览器2.查看浏览器的版本3.下载浏览器驱动4.驱动位置放置4.1 放在代码文件同级目录4.2 随意放置4.3 放在python解释器根目录三、安装selenium1.安装2.查看版本四、使用selenium前言 大家好&…

Linux——进程管理篇(详解fork和exec)

文章目录Linux——进程管理篇&#xff08;详解fork和exec&#xff09;&#x1f697;如何在Linux编写与运行代码编写编译运行&#x1f697;进程管理forksystemexec&#x1f697;总结Linux——进程管理篇&#xff08;详解fork和exec&#xff09; &#x1f680;&#x1f680;这篇…

前端代码复用学习笔记:整洁架构与清晰架构

基础代码的复用往往比较简单&#xff0c;但是业务代码的复用通常是困难的&#xff0c;如果没有特殊的手段去治理项目会逐渐发展为难以维护的巨石应用&#xff0c;按照维基百科记载&#xff0c;代码的复用形式主要有三种&#xff0c;程序库&#xff0c;应用框架&#xff0c;设计…

【手撕八大排序】——插入排序

文章目录插入排序概念插入排序分为2种一 .直接插入排序直接插入排序时间复杂度二.希尔排序希尔排序时间复杂度效率比较插入排序概念 直接插入排序是从一个有序的序列中选择一个合适的位置进行插入&#xff0c;这个合适的位置取决于是要升序排序还是降序排序。 每一次进行排序…

异常体系介绍

1、什么是异常 异常&#xff1a;表示程序出现的问题 误区&#xff1a;不是让我们以后不出现异常&#xff0c;而是出现了异常以后&#xff0c;我们该如何处理 分为Error和Exception两部分&#xff0c;其中Error表示系统级&#xff08;严重的&#xff09;异常&#xff0c;比如说…

【Linux】网络基础(2)

前言 本篇笔记记录我在Linux系统下学习网络基础部分知识&#xff0c;从关于网络的各种概念和关系开始讲起&#xff0c;逐步架构起对网络的认识&#xff0c;对网络编程相关的认知。本篇继承自网络基础1&#xff0c;感兴趣的可以看看哦~ 这篇文章我会记录学习https协议的所思所想…

HashMap扩容为什么每次都是之前的2倍

一. 背景介绍HashMap的底层是通过数组链表红黑树的数据结构来存放数据的。我们知道&#xff0c;当新添加元素的key值出现了hash碰撞&#xff0c;就会在同一个bucket中形成链表或者红黑树。当键值对的数量超过阈值时就会扩容&#xff0c;将以前处于同一个链表或者红黑树上的元素…

pytorch实现深度神经网络与训练

目录 1. 随机梯度下降算法 2.优化器 3. 损失函数 3.1 均方误差损失 3.2 交叉熵损失 4.防止过拟合 4.1 过拟合的概念 4.2 防止过拟合的方法 5. 网络参数初始化 5.1 网络参数初始化方法 5.2 参数初始化方法应用实例 1.针对某一层的权重进行初始化 2.针对一个网络的权…

学习 Python 之 Pygame 开发魂斗罗(一)

学习 Python 之 Pygame 开发魂斗罗&#xff08;一&#xff09;Pygame回忆Pygame1. 使用pygame创建窗口2. 设置窗口背景颜色3. 获取窗口中的事件4. 在窗口中展示图片(1). pygame中的直角坐标系(2). 展示图片(3). 给部分区域设置颜色5. 在窗口中显示文字6. 播放音乐7. 图片翻转与…

全网超详细的vue双向数据绑定的原理

文章目录1. 文章引言2. vue如何实现数据的双向绑定3. 什么是Object.defineProperty4. 什么是setter和getter1. 文章引言 假设&#xff0c;我在文本框中输入文字&#xff0c;在p标签中动态展示我输入的文字&#xff0c;如下图所示&#xff1a; 实现上述效果的代码如下所示&#…

通过百度文心一言大模型作画尝鲜,感受国产ChatGPT的“狂飙”

3月16日下午&#xff0c;百度于北京总部召开新闻发布会&#xff0c;主题围绕新一代大语言模型、生成式AI产品文心一言。百度创始人、董事长兼首席执行官李彦宏&#xff0c;百度首席技术官王海峰出席&#xff0c;并展示了文心一言在文学创作、商业文案创作、数理推算、中文理解、…

【数据结构】万字深入浅出讲解单链表(附原码 | 超详解)

&#x1f680;write in front&#x1f680; &#x1f4dd;个人主页&#xff1a;认真写博客的夏目浅石. &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f4e3;系列专栏&#xff1a;C语言实现数据结构 &#x1f4ac;总结&#xff1a;希望你看完…

LeetCode刷题——贪心法(C/C++)

这里写目录标题[中等]买卖股票的最佳时机 II[中等]移掉k位数字[中等]跳跃游戏[中等]跳跃游戏 II[中等]加油站[中等]划分字母区间[中等]无重叠区间[中等]用最少数量的箭引爆气球[中等]买卖股票的最佳时机 II 原题链接题解 最简单的思路&#xff0c;效率不高&#xff0c;只要明天…

【Linux】Linux项目自动化构建工具make makefile

文章目录1. 背景2.实例3.原理4.项目清理5. 文件属性中的三个时间6. Linux下第一个小程序——进度条6.1 前置知识1&#xff1a;缓冲区6.2前置知识2&#xff1a;回车换行6.3进度条的实现7 Linux下git的”三板斧“1. 背景 一个工程中的源文件不计其数&#xff0c;其按类型、功能、…

【码字必看】一篇文章带你轻松上手MarkDown

文章目录&#x1f36c;前言&#x1f62e;什么是MarkDown&#x1f9d0;为什么要学习MarkDown&#x1f511;使用MarkDown的工具&#x1f4da;MarkDown基础语法&#x1f95d;标题&#x1f965;字体&#xff08;斜体、粗体、粗斜体&#xff09;&#x1f347;各种线&#xff08;分割…

华为nat配置实验:内网能够访问外网,内网服务器80端口映射出去

一 需求分析1.1 需求公司A在北京&#xff0c;公司B在上海&#xff0c;本次实验仅仅模拟局域网内出口路由器的配置&#xff0c;公司A业务流量较大&#xff0c;并且预算有限。公司B模拟外网的一个小型局域网&#xff0c;要求公司B的主机能够访问公司A的web服务器。1.2 分析采用na…

Django 4.0文档学习(一)

本系列文章基于Django4.0版本官方网站文档学习 使用开发工具为pycharm > python -m django --version 4.0文章目录编写你的第一个 Django 应用&#xff0c;第 1 部分创建项目用于开发的简易服务器创建应用编写第一个视图编写你的第一个 Django 应用&#xff0c;第 2 部分数…

移动端适配

​ 是看的b站一个老哥的视频&#xff0c;做的汇总&#xff0c;讲的嘎嘎棒。视频链接&#xff1a;b站链接 视口viewport pc端视口就是可视化的窗口&#xff0c;不包含浏览器工具栏但是移动端&#xff0c;不太一样&#xff0c;布局的视口和可见的视口是不太一样的 移动端的网页…

openstack

云计算架构 openstack整体架构 openstack身份服务-Keystone 管理层次结构 Keystone三大组件 服务&#xff08;Server&#xff09; 身份&#xff08;Identity&#xff09;服务 资源&#xff08;Resource&#xff09;服务 分配&#xff08;Assignment&#xff09;服务 令牌&am…

Kaggle实战入门:泰坦尼克号生生还预测

Kaggle实战入门&#xff1a;泰坦尼克号生生还预测1. 加载数据2. 特征工程3. 模型训练4. 模型部署泰坦尼克号&#xff08;Titanic&#xff09;&#xff0c;又称铁达尼号&#xff0c;是当时世界上体积最庞大、内部设施最豪华的客运轮船&#xff0c;有“永不沉没”的美誉&#xff…
最新文章