搜索二叉树

2023-11-08

概念

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

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

在这里插入图片描述

二叉搜索树的性质保证了其中序遍历是有序的,同时还天然去重,所以也称为二叉排序树

实现二叉搜索树

查找

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

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

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

插入

a. 树为空,则直接新增节点,赋值给root指针

b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

// 非递归插入
bool Insert(const K& key)
{
	if (_root == nullptr)	// 空树
	{
		_root = new Node*(key);
		return true;
	}
	
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		parent = cur;

		if (cur->_key > key)
		{
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			cur = cur->_right;
		}
		else	// 找到了,插入失败
		{
			return false;
		}
	}

	// 正常插入
	Node* newNode = new Node(key, value);
	if (key > parent->_key)
		parent->_right = newNode;
	else
		parent->_left = newNode;

	return true;
}


// 递归插入
bool InsertR(K key)
{
	return _InsertR(_root, key);
}
// 注意需要传引用
bool _InsertR(Node*& root, K key)
{
	if (root == nullptr)
	{
		root = new Node(key);
		return true;
	}

	if (key > root->_key)
		return _InsertR(root->_right, key);
	else if (key < root->_key)
		return _InsertR(root->_left, key);
	else 
		return false;
}

删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:

a. 要删除的结点无孩子结点

b. 要删除的结点只有左孩子结点

c. 要删除的结点只有右孩子结点

d. 要删除的结点有左、右孩子结点

看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程
如下:

  • 情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除
  • 情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除
  • 情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题–替换法删除
// 非递归删除
bool Erase(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
		{
			break;
		}
	}

	if (!cur) return false;		// 没有找到


	// 找到开始删除
	// 1. 删除根节点
	// 2. 删除普通节点
	//   细分:
	//		1. 左边没有节点
	//		2. 右边没有节点
	//		3. 左右都有节点:
	//				1. 找到右子树的最左节点或者右子树的最右节点作为替换节点
	//				2. 交换删除节点和替换节点的值
	//				3. 更新链接关系
	//				4. 删除替换节点

	if (cur == _root)	// 删除根节点
	{
		if (cur->_left == nullptr)
		{
			_root = cur->_right;
			delete cur;
		}
		else if (cur->_right == nullptr)
		{
			_root = cur->_left;
			delete cur;
		}
		else
		{
			Node* min = cur->_right;
			Node* minParent = cur;
			while (min->_left)
			{
				minParent = min;
				min = min->_left;
			}

			::swap(cur->_key, min->_key);
			if (min == minParent->_left)
			{
				minParent->_left = min->_right;
			}
			else
			{	
				minParent->_right = min->_right;
			}
			delete min;
		}
	}
	else	// 删除非根节点
	{
		if (cur->_left == nullptr)
		{
			if (parent->_right == cur)
			{
				parent->_right = cur->_right;
			}
			else
			{
				parent->_left = cur->_right;
			}
		}
		else if (cur->_right == nullptr)
		{
			if (parent->_right == cur)
			{
				parent->_right = cur->_left;
			}
			else
			{
				parent->_left = cur->_left;
			}
		}
		else
		{
			Node* min = cur->_right;
			Node* minParent = cur;
			while (min->_left)
			{
				minParent = min;
				min = min->_left;
			}

			swap(cur->_key, min->_key);
			if (min == minParent->_left)
			{
				minParent->_left = min->_right;
			}
			else
			{
				minParent->_right = min->_right;
			}
			delete min;
		}
	}

	return true;
}



// 递归删除
bool EraseR(K key)
{
	return _EraseR(_root, key);
}

bool _EraseR(Node*& root, K key)
{
	if (root == nullptr) return false;

	if (key > root->_key)
		return _EraseR(root->_right, key);
	else if (key < root->_key)
		return _EraseR(root->_left, key);
	else
	{
		if (root->_right == nullptr) 	// 右为空
		{
			Node* del = root;
			root = root->_left;
			delete del;
			return true;
		}
		else if (root->_left == nullptr) 	// 左为空
		{
			Node* del = root;
			root = root->_right;
			delete del;
			return true;

		}
		else	// 左右都不为空
		{
			Node* min = root->_right;
			while (min->_left)
			{
				min = min->_left;
			}

			::swap(root->_key, min->_key);
			return _EraseR(root->_right, key);	// 转换为左右有一个为空的情况
		}
	}
}

性能分析

二叉搜索树的所有操作都是基于查找上进行的,查找效率代表了二叉搜索树中各个操作的性能。

查找的效率是树的深度: O ( h ) O(h) O(h)

根据不同的数据,能得到结构不同的二叉搜索树:

在这里插入图片描述

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: O ( l o g 2 N ) O(log_2^N) O(log2N)

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

后面我们学习的红黑树和AVL树就是在二叉搜索树的基础上通过旋转进行优化的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

搜索二叉树 的相关文章

  • 在应用程序版本中使用 svn 修订号

    在 VS2010 解决方案 不是 NET 中 我希望将 svn 修订号作为应用程序版本的一部分包含在内 我们目前不使用 makefile 仅使用 VS 解决方案 项目设置 我想在编译时获取工作副本修订号 将其存储到变量中 以便稍后在代码中使
  • 如何向 SDL 线程发送附加参数?

    是的 我知道如何创建 SDL 线程 int myfunc void data my code SDL CreateThread mythread SDL CreateThread myfunc NULL 但如果我想做类似的事情怎么办 int
  • 管道破裂错误

    我在 FTP 实现中的打开的数据套接字上使用 write 来发送文件 但写入一些数据后 它会挂起一段时间 之后它会返回 损坏的管道 错误 对此的任何帮助将不胜感激 我的进程从一个缓冲区读取数据包并写入套接字 我在增加带宽时注意到了这个问题
  • 在 C++ 中将惰性生成器实现为forward_iterator

    MyGenerator 表示 可能 有限的整数序列 计算成本很高 所以我不想预先生成它们并将它们放入容器中 struct MyGenerator bool HasNext int Next 要打印全部 MyGenerator generat
  • 如何使用 C 将带有 2 个变量的 IF 语句转换为 switch 函数?

    我有一个 IF 语句 我想将其转换为 Switch 语句 但它有 2 个变量 在C上可以实现吗 这是一个石头 剪刀 布的游戏 R代表石头 P代表布 S代表剪刀 char play1 play2 printf nPlayer 1 Enter
  • 在执行方法的括号内声明变量

    默认情况下 变量在方法执行之前定义 例如 DateTime myDate if DateTime TryParse date out myDate 我们可以实现内联声明 并且该变量可以在外部使用 例如 if DateTime TryPars
  • 易失性限定符是否会取消该内存的缓存?

    在本文中 http www drdobbs com parallel 易失性 vs 易失性 212701484 pgno 2 http www drdobbs com parallel volatile vs volatile 212701
  • 方法参数数组默认值[重复]

    这个问题在这里已经有答案了 在 C 中 可以在方法中使用默认参数值 例如 public void SomeMethod String someString string value Debug WriteLine someString 但现
  • 为什么零长度 stackalloc 会让 C# 编译器乐意允许条件 stackalloc?

    下面的 修复 让我很困惑 这里的场景是根据大小有条件地决定是否使用堆栈还是租用缓冲区 然而 这是一个相当小众但有时必要的优化 使用 明显 实现 数字 3 推迟明确的分配 直到我们真正想要分配它 编译器抱怨 CS8353 类型为 Span 的
  • 表达式树序列化器

    我想在客户端使用 Linq 表达式 序列化它们并在服务器端执行它们 为此我想使用 http expresstree codeplex com http expressiontree codeplex com 但我想针对自己的 WCF 调用执
  • 论文中的概率密度函数,使用 C++ 实现,未按预期工作

    所以我正在实现一个启发式算法 并且我遇到了这个函数 我有一个 1 到 n 的数组 C 上的 0 到 n 1 w e 我想选择一些要复制到另一个数组的元素 给定参数 y 0 根据作者的说法 l 是一个随机数 0 所以我编写了函数的第一部分 对
  • C++:如何通过时间和本地时间获取实际时间?

    我正在寻找一种在 C 中以 HH MM SS 方式节省时间的方法 我在这里看到它们有很多解决方案 经过一番研究后我选择了time and localtime 然而 似乎localtime函数有点棘手 因为它says http rabbit
  • 自定义 web.config 部分处理程序

    我之前设计过一个自定义部分处理程序 但我遇到了一个我似乎无法想到的问题 我有一个像这样的配置部分
  • 来自指针的 Typedef const 引用[重复]

    这个问题在这里已经有答案了 可能的重复 为什么允许将指针强制转换为引用 https stackoverflow com questions 5924248 why is it allowed to cast a pointer to a r
  • 如何在 Windows 8 中使用 StreamWriter 写入文件?

    我在创建时遇到问题StreamWriter在windows 8中 通常我只是创建一个实例 只是传递一个字符串作为参数 但在Windows 8中 我收到一个错误 表明它应该接收一个Stream 但我注意到Stream是一个抽象类 有人知道吗编
  • 当代码依赖于两个对象的子类型时,是否有设计模式可以处理

    我会尽力尽可能明确 以防有比回答我的问题更好的解决方案 我正在使用 C 工作 我有一个报告模板 可以包含任意数量的打开的 功能 功能可能是信息表 饼图 条形图 列表等 我将报告生成为文本文件或 PDF 将来可能有其他选项 到目前为止我有一个
  • 如何将 Ctrl+,(control 加逗号)指定为 WPF 菜单项的键盘快捷键?

    Question I would like to assign the keyboard shortcut Ctrl control plus comma to the Preferences menu item How do I do t
  • 在 fork() 之后寻求有关“文件描述符”的简单描述

    Unix 环境中的高级编程 第二版 作者 W Richard Stevens 第 8 3 节 fork 函数 描述如下 父级和子级共享相同的文件偏移量非常重要 考虑一个分叉子进程 然后等待子进程完成的进程 假设两个进程都写入标准输出作为其正
  • ‘+= new EventHandler’和‘-= new EventHandler(anEvent)’之间的区别

    我看到一些代码使用 新的事件处理程序 anEvent 你能告诉我有什么不同吗 新的事件处理程序 Thanks 一个将委托添加到订阅者集合中 另一个将其删除 例如 如果您之前订阅了某个事件 但您希望在关闭表单时删除引用 则可以使用 版本 您将
  • 无限循环消耗 100% CPU

    我陷入了需要生成某个 Hz 的定义频率的情况 我尝试过多媒体计时器和互联网上提供的所有其他东西 但到目前为止 带有一些 if else 条件的无限循环给了我最好的结果 但这种方法的问题是它消耗了几乎所有的CPU 没有空间让其他应用程序正常工

随机推荐

  • vue中使用vuedraggable实现嵌套多层拖拽排序功能

    前言 vue中实现嵌套多层拖拽功能 官网入口 https www npmjs com package vuedraggable 实现效果 拖动左侧调整一级的顺序 拖动右侧调整二级的顺序 实现步骤 这里使用了插件 vuedraggable 第
  • 可以实操的代码,却在Proteus无法正常运行,sprintf函数所造成的故障

    前言 1 昨天 接了一个写代码的单子 为了防止客户说我的代码有问题 所以就打算将代码放在Proteus上跑 为什么不是硬件上跑呢 因为我的硬件找不到了 2 因为我电脑安装的Proteus总是闪退 下载安装搞了很久没搞好 于是让朋友帮忙验证
  • stata基础--回归,画散点图,异质性分析

    利用stata的内部数据来进行回归 代码 sysuse auto sysuse dir 可以看到所有的数据 su price mpg foreign reg price mpg predict u residual 新变量u 每一个观测的残
  • Kafka详解

    目录 一 消息系统 1 点对点的消息系统 2 发布 订阅消息系统 二 Apache Kafka 简介 三 Apache Kafka基本原理 3 1 分布式和分区 distributed partitioned 3 2 副本 replicat
  • JDWP Transport dt_socket failed to initialize, TRANSPORT_INIT(510) 解决

    重启tomcat 后台出现JDWP Transport dt socket failed to initialize TRANSPORT INIT 510 错误 因为tomca开启了debug 而debug端口占用导致的问题 1 ERROR
  • 湖南文旅数据中心:湖南文旅数据早知道(9月10日)

    湖南文旅数据早知道 9月10日 星期四 省内文旅要闻 昆明文旅推介会在长沙举行 坚持公交优先 湖南122个县市区全面实现交通一卡通互联互通 湖南雪峰启动消费扶贫 文旅产品引领乡村振兴 国内文旅要闻 国内旅游宣传推广典型案例名单发布 中秋国庆
  • Python3中 pyecharts.charts库可视化词云图--《你的答案》的歌词!

    Python3中 pyecharts charts库可视化词云图 你的答案 的歌词 可视化歌曲 你的答案 的歌词 词频自己设计 Project filename PythonDemo WordCount IDE PyCharm Author
  • js判断数组中是否存在某个属性或者对象

    骑士李四记录 场景一 对数组去重 1 判断是否存在字段 可以对数组去重 var arr 1 2 3 4 arr indexOf 3 2 arr indexOf 5 1 应用 去重 var list for let str of arr if
  • 算法岗面试题.收集

    收集一下算法岗面试题 后续将对问题进行自己的解答 蔚来感知算法岗面试题 1 用C 手写NMS 2 从模型和数据的角度分别说明如何解决梯度爆炸的问题 3 Faster R CNN的流程 两阶段主要解决了什么问题 4 YOLO中是怎么解决正负样
  • 解决:在自动化测试中定位到新打开的窗口的元素问题

    原始代码 time sleep 3 self driver find element by id TANGRAM PSP 10 footerULoginBtn click time sleep 2 log info 点击qq账号登陆 sel
  • 数组实现顺序二叉树的前序遍历,中序遍历,后序遍历

    顺序二叉树的满足条件 1 一般指完全二叉树 2 第n个元素的左子树为2 n 1 3 第n个元素的右子树为2 n 2 4 第n个子树的父节点为 n 1 2 注意 n为数组下标所以是从0开始 代码实现 package com yg tree a
  • js的内容

    1 JS的背景 W3C将网页的标准分成了三部分 HTML 页面的结构 CSS 页面的样式 JavaScript 页面的行为 JavaScript JS 发展背景 创建JavaScript的初衷 JavaScript最开始由网景公司创建的 参
  • 基于LEACH和HEED的WSN路由协议研究与改进(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 3 Matlab代码实现 4 参考文献 1 概述 无线传感器网络 Wireless Sens
  • 最详细的ECLIPSE Android SDK下载安装及配置教程

    最近Neo突发神经 想要将学过的一些计算机视觉 机器学习中的算法都放到移动设备上去跑跑 因为移动开发是大势所趋嘛 希望能够通过这样一个实践的过程 找到一些新的灵感 该不会是为了赚钱吧 我自己目前也有一些idea 然后也希望以后能够进行计算机
  • linux看剩余电量命令,Linux终端如何检查笔记本电脑电池的状态和电量

    在Linux的终端检查笔记本电脑电池的状态和电量 通过三种方法从命令行找到笔记本电脑的电池状态 方法1 使用 Upower 命令 大多数Linux发行版中都预装了Upower命令 要使用Upower显示电池状态 请打开终端并运行 upowe
  • vue中使用bus来实现不同组件的传值(更推荐vuex)

    前言 在vue中实现用公共bus来实现不同组件直接的传值 实现方法 1 main js中在window上挂载一个变量EventBus window EventBus new Vue 2 传方法页面 必须在页面的销毁阶段传方法 至于原因 请看
  • Linux中处置挖矿病毒样本演示

    一 病毒特征 1 top 查看cup使用率 CUP使用率极高 也可以看到它的PID 2 查看网络连接数 netstat anpt grep tcp 连接数较高 二 处置 1 kill pid 尝试删除可疑进程 可以删除 但是他还是会自动启动
  • 亲测!推荐一款k8s前端操作界面 Kuboard for K8S

    文章目录 一 前提 二 安装Kuboard for K8S 2 1 安装 2 2 加载Kuboard镜像 2 3 准备kuboard yaml文件 2 4 执行安装命令 三 启动观察 3 1 获取token 3 2 打开浏览器 享受飞一般的
  • Redis集群配置

    目录 1 创建两个桥接虚拟机实例 1 1 修改桥接网络 1 2 修改本地网络配置文件 1 3 测试 2 配置redis集群 2 1 安装redis 2 1 1 安装依赖 2 1 2 下载redis安装包上传服务器并解压 2 1 3 解压文件
  • 搜索二叉树

    全文目录 概念 实现二叉搜索树 查找 插入 删除 性能分析 概念 二叉搜索树 它或者是一棵空树 或者是具有以下性质的二叉树 若它的左子树不为空 则左子树上所有节点的值都小于根节点的值 若它的右子树不为空 则右子树上所有节点的值都大于根节点的