【C++】二叉树进阶 -- 详解

news2024/11/20 12:43:50

一、二叉搜索树概念

二叉搜索树 又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

  • 对二叉搜索树进行中序遍历,正好是一个升序序列。(左子树 -> 根 -> 右子树)

二、二叉搜索树操作

  • 增删查的时间复杂度是:O(h)。h 是树的高度,最坏情况是 h 是 N。

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

1、二叉搜索树的查找

        a、从 开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
        b、 最多查找高度次 ,走到空,还没找到,这个值不存在。

2、二叉搜索树的插入

插入的具体过程如下:
  • 树为,则直接新增节点,赋值给 root 指针。
  • 不空按二叉搜索树性质查找插入位置,插入新节点。


3、搜索二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回,否则要删除的结点可能分下面四种情况:
  1. 要删除的结点无孩子结点。
  2. 要删除的结点只有左孩子结点。
  3. 要删除的结点只有右孩子结点。
  4. 要删除的结点有左、右孩子结点。

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

  • 情况 b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点 -- 直接删除。
  • 情况 c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点 -- 直接删除。
  • 情况 d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题 —— 替换法删除。

删除 3(删除的节点有两个孩子)替换法 -- 删除(以下两种方法任选一种)

  1. 左子树的最大节点 —— 2(左子树的最右节点)
  2. 右子树的最小节点 —— 4(右子树的最左节点)
  • 替换节点赋值给删除节点后,删除替换节点。
  • 替换节点要么没有孩子,要么只有一个孩子,可以直接删除。

三、二叉搜索树的实现

template<class K>
struct BSTreeNode
{
    BSTreeNode<K>* _left;
    BSTreeNode<K>* _right;
    K _key;

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

template<class K>
class BSTree
{
    typedef BSTreeNode<K> Node;
public:
    bool Insert(const K& key)
    {
        // 如果树为空,直接插入
        if (nullptr == _root)
        {
            _root = new Node(key);
            return true;
        }

        // 按照二叉搜索树的性质查找key在树中的插入位置
        Node* cur = _root;
        // 记录cur的双亲,因为新元素最终插入在cur双亲左右孩子的位置
        Node* parent = nullptr;
        while (cur)
        {
            parent = cur;
            if (key < cur->_key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else if (key > cur->_key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else
                return false;
        }

        // 插入元素
        cur = new Node(key);
        if (key < parent->_key)
            parent->_left = cur;
        else
            parent->_right = cur;

        return true;
    }

    // 根据二叉搜索树的性质查找:找到值为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)
    {
        // 如果树为空,删除失败
        if (nullptr == _root)
            return false;

        // 查找在data在树中的位置
        Node* cur = _root;
        Node* parent = nullptr;
        while (cur)
        {
            if (key < cur->_key)
            {
                parent = cur;
                cur = cur->_left;
            }
            else if(key > cur->_key)
            {
                parent = cur;
                cur = cur->_right;
            }
            else
            {
                // 开始删除
				// 1、左为空
				if (cur->_left == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
					}
					delete cur;
					cur = nullptr;
				}
				// 2、右为空
				else if (cur->_right == nullptr)
				{
					if (_root == cur)
					{
						_root = cur->_left;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
					cur = nullptr;
				}
				// 3、左右都不为空
				else
				{
					// 找到右子树最小节点进行替换
					Node* minParent = cur;
					Node* min = cur->_right;
					while (min->_left)
					{
						minParent = min;
						min = min->_left;
					}
					swap(cur->_key, min->_key);
					if (minParent->_left == min)
						minParent->_left = min->_right;
					else
						minParent->_right = min->_right;
					delete min;
				}
				return true;
            }
        }
        return false;
    }

        // key不在二叉搜索树中,无法删除
        if (nullptr == cur)
            return false;

        // 分以下情况进行删除
        if (nullptr == cur->_right)
        {
            // 当前节点只有左孩子或者左孩子为空---可直接删除
        }
        else if (nullptr == cur->_right)
        {
            // 当前节点只有右孩子---可直接删除
        }
        else
        {
            // 当前节点左右孩子都存在,直接删除不好删除,可以在其子树中找一个替代结点,比如:
            // 找其左子树中的最大节点,即左子树中最右侧的节点,或者在其右子树中最小的节点,即右子树中最小的节点
            // 替代节点找到后,将替代节点中的值交给待删除节点,转换成删除替代节点
        }
        return true;
    }

    void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}

	bool InsertR(const K& key)
	{
		return _InsertR(_root, key);
	}

	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}

	~BSTree()
	{
		_Destory(_root);
	}

    BSTree() = default;

	BSTree(const BSTree<K>& t)
	{
		_root = _Copy(t._root);
	}

	// t2 = t1
	BSTree<K>& operator=(BSTree<K> t)
	{
		swap(_root, t._root);
		return *this;
	}

private:
    Node* _Copy(Node* root)
	{
		if (root == nullptr)
		{
			return nullptr;
		}

		Node* copyRoot = new Node(root->_key);
		copyRoot->_left = _Copy(root->_left);
		copyRoot->_right = _Copy(root->_right);
		return copyRoot;
	}

	void _Destory(Node*& root)
	{
		if (root == nullptr)
		{
			return;
		}

		_Destory(root->_left);
		_Destory(root->_right);
		delete root;
		root = nullptr;
	}

	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* min = root->_right;
				while (min->_left)
				{
					min = min->_left;
				}
				swap(root->_key, min->_key);
				//return Erase(key); // 错误 - 找到的不是我们要的位置
				return _EraseR(root->_right, key);
			}
			delete del;
			return true;
		}
	}

	bool _InsertR(Node*& root, const K& key)
    {
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}

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

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

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

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

		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}

private:
	Node* _root = nullptr;
};

四、二叉搜索树的应用

1、 K模型(判断关键字在不在) :K 模型即只有 key 作为关键码,结构中只需要存储 Key 即可,关键码即为需要搜索到 的值
比如:给一个单词 word,判断该单词是否拼写正确,具体方式如下:
  • 以词库中所有单词集合中的每个单词作为 key,构建一棵二叉搜索树。
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

2、 KV模型 :每一个关键码 key,都有与之对应的值 Value,即 <Key, Value> 的键值对。该种方式在现实生活中非常常见:
比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英
文单词与其对应的中文 <word, chinese> 就构成一种键值对;
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出
现次数就是 <word, count> 就构成一种键值对
// 改造二叉搜索树为KV结构
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 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)
	{
		//...

		return true;
	}

	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;
};

void TestBSTree1()
{
    // 输入单词,查找单词对应的中文翻译
    BSTree<string, string> dict;
    dict.Insert("string", "字符串");
    dict.Insert("tree", "树");
    dict.Insert("left", "左边");
    dict.Insert("right", "右边");
    dict.Insert("sort", "排序");
    // 插入词库中所有单词
    string str;
    while (cin >> str)
    {
        BSTreeNode<string, string>* ret = dict.Find(str);
        if (ret)
		{
			cout << "对应的中文:" << ret->_value << endl;
		}
		else
		{
			cout << "没有此单词" << endl;
		}
    }
}

void TestBSTree2()
{
    // 统计水果出现的次数
    string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
    BSTree<string, int> countTree;
    for (const auto& str : arr)
    {
        // 先查找水果在不在搜索树中
        // 1、不在,说明水果第一次出现,则插入<水果, 1>
        // 2、在,则查找到的节点中水果对应的次数++
        //BSTreeNode<string, int>* ret = countTree.Find(str);
        auto ret = countTree.Find(str);
        if (ret)
		{
			ret->_value++;
		}
		else
		{
			countTree.Insert(str, 1);
		}
    }
    countTree.InOrder();
}

五、二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有 n 个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

  1. 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: logN
  2. 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:N。
如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?
那么我们后面将学习的 AVL 树和红黑树 就可以派上用场了。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

API接口采集商品详情页面数据(H5端和PC端)item_get-获得淘宝商品详情

API接口是一种商业软件开发工具&#xff0c;可以帮助开发者实现业务需求。通过 API接口&#xff0c;开发人员可以快速搭建自己的应用&#xff0c;实现数据采集分析和处理&#xff0c;也可以通过这个接口完成与其它系统的集成与通信。电商API就是各大电商平台提供给开发者访问平…

深度学习中的不确定性综述

领域学者&#xff1a; http://www.gatsby.ucl.ac.uk/~balaji/ 论文标题&#xff1a; A Survey of Uncertainty in Deep Neural Networks 论文链接&#xff1a; https://arxiv.org/pdf/2107.03342.pdf 概要 在过去的十年中&#xff0c;神经网络几乎遍及所有科学领域&#x…

Mac电脑怎么在Dock窗口预览,Dock窗口预览工具DockView功能介绍

DockView是一款Mac电脑上的软件&#xff0c;它可以增强Dock的功能&#xff0c;让用户更方便地管理和切换应用程序。 DockView的主要功能是在 DockQ&#xff0c;栏上显示每个窗口的缩略图&#xff0c;并提供了一些相关的操作选项。当用户将鼠标悬停在Dock栏上的应用程序图标上时…

Centos磁盘问题小纪

场景说明 放个windows的图片镇楼&#xff0c;在给一个centos的来说明问题&#xff0c;咋了&#xff0c;好好的系统&#xff0c;啥也不能干了 来先上一波命令分析下问题 查看挂载 mount 重新挂载数据 mount -o remount, rw / 查看磁盘 df -h 查看分区挂载详情 rw读写权限 mount …

顶级玩家:一招搞定 App 自动化老大难问题

很多人在学习 App 自动化或者在项目中落地实践 App 自动化时&#xff0c;会发现编写的自动化脚本无缘无故的执行失败、不稳定。 而导致其问题很大原因是因为应用的各种弹窗&#xff08;升级弹窗、使用过程提示弹窗、评价弹窗等等&#xff09;&#xff0c;比如这样的&#xff1a…

vue3项目使用highlight.js插件实现了代码块

使用vue的都知道官网的代码块效果: 下面是我们实现了这个功能: 使用highlight.js就可以实现 官网: highlight.js 下载插件后,在main.ts文件中引入: 在文件中: 引入hljs,在onMounted回调中使用,希望使用什么主题就引入什么主题的css, 创建topic和pre这两个需要的, 因为上面j…

✔ ★【备战实习(面经+项目+算法)】 10.22学习时间表(算法刷题:4道)

✔ ★【备战实习&#xff08;面经项目算法&#xff09;】 坚持完成每天必做如何找到好工作1. 科学的学习方法&#xff08;专注&#xff01;效率&#xff01;记忆&#xff01;心流&#xff01;&#xff09;2. 每天认真完成必做项&#xff0c;踏实学习技术 认真完成每天必做&…

如何远程访问Linux本地WBO白板实现随时随地创作?

[TOC]如何远程访问Linux本地WBO白板实现随时随地创作&#xff1f;) 前言 WBO在线协作白板是一个自由和开源的在线协作白板&#xff0c;允许多个用户同时在一个虚拟的大型白板上画图。该白板对所有线上用户实时更新&#xff0c;并且状态始终保持。它可以用于许多不同的目的&am…

基于Python的淘宝商品API接口

淘宝API接口是一个丰富的接口库&#xff0c;提供了各种各样的接口供开发者使用。满足多语言开发。以下是一些常见的淘宝API接口及其代码示例。 1. 搜索商品API接口 该API提供了关键字搜索淘宝商品的功能&#xff0c;可以使用各种编程语言来访问。以下代码段展示了使用Python来…

程序包org.apache.ibatis.mapping不存在 符号找不到

找不到符号 符号: 类 Cursor和程序包org.apache.ibatis.mapping不存在 在idea中没有错误&#xff0c;但是在linux编辑时报了这两个错误&#xff0c;之前有遇见过符号找不到的问题&#xff0c; 当时的问题是编译的import xxx.xxx.xxx.* 识别不成功过&#xff0c;将*改为…

TX Text Control.NET For WPF 32.0 Crack

TX Text Control 支持VISUAL STUDIO 2022、.NET 5 和 .NET 6 支持 .NET WPF 应用程序的文档处理 将文档编辑、创建和 PDF 生成添加到您的 WPF 应用程序中。 视窗用户界面 功能齐全的文档编辑器 TX Text Control 是一款完全可编程的丰富编辑控件&#xff0c;它在专为 Visual Stu…

IS200TPR0S1CBB IS215VCMIH2C BJRL-20012-110001

IS200TPR0S1CBB IS215VCMIH2C BJRL-20012-110001 随着NVIDIA Jetson AGX Orin开发套件的发布&#xff0c;AAEON很高兴能够利用这种强大的模块上系统(SOM)为自己的产品线带来的诸多优势。与NVIDIA Jetson AGX Xavier具有相同的外形和引脚兼容性&#xff0c;但从32 TOPS提高到…

CVPR2023优秀论文 | AIGC伪造图像鉴别算法泛化性缺失问题分析

作者 | 搜索内容技术部 导读 深度伪造检测算法无法检出未知伪造算法生成的攻击数据。以往算法采取手动建模伪造特征的方式提升模型泛化性&#xff0c;然而这种方式限制了算法可行域&#xff0c;影响了模型泛化性进一步提升&#xff0c;同时这类方法参数量巨大&#xff0c;无法满…

代码随想录 Day26贪心算法01-上

目录 前言:贪心无套路 本质: 两个极端 贪心的小例子 贪心无套路!!! LeetCode T455 分发饼干 题目思路: 1.优先考虑胃口:大饼干喂饱大胃口 2.优先考虑饼干:小饼干先喂饱小胃口 前言:贪心无套路 本质: 局部最优去推导全局最优 两个极端 贪心算法的难度一般要么特别简单,要…

新增用户登录和资产登录通知功能,支持指定目录运行作业中心命令,JumpServer堡垒机v3.8.0发布

2023年10月23日&#xff0c;JumpServer开源堡垒机正式发布v3.8.0版本。在这一版本中&#xff0c;JumpServer在“用户登录”和“资产登录”这两个权限控制功能中&#xff0c;新增“通知”动作。目前其支持的动作包括拒绝、接受、审批以及通知四种动作&#xff0c;方便了管理员针…

栩栩如生,音色克隆,Bert-vits2文字转语音打造鬼畜视频实践(Python3.10)

诸公可知目前最牛逼的TTS免费开源项目是哪一个&#xff1f;没错&#xff0c;是Bert-vits2&#xff0c;没有之一。它是在本来已经极其强大的Vits项目中融入了Bert大模型&#xff0c;基本上解决了VITS的语气韵律问题&#xff0c;在效果非常出色的情况下训练的成本开销普通人也完全…

Qt音乐播放器

简介 使用QMediaPlayer和QMediaPlaylist制作的音乐播放器 编译环境 Qt5.6 MGW32 windows10 功能特性 GUI 功能 加载mp3文件&#xff0c;得到歌曲信息&#xff1b;打开文件夹加载或拖拽音乐文件加载滑动条关联播放进度、音量显示/隐藏歌曲列表&#xff0c;编辑歌曲列表&am…

【Proteus仿真】【STM32单片机】自动饲养控制系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真STM32单片机控制器&#xff0c;使用LCD1604显示模块、红外传感器、有害气体检测模块、PCF8591 ADC模块&#xff0c;蜂鸣器、DHT11温湿度、SG90舵机、风扇加热加湿等。 主要功能&a…

java--关系运算符

1.关系运算符 1.判断数据是否满足条件&#xff0c;最终返回一个判断的结果&#xff0c;这个结果是布尔类型的值&#xff1a;true或者false。 注意&#xff1a;在java中判断两个变量内容是否相等一定是""&#xff0c;千万不要把""误写成""或&quo…

性能与效果平衡:选择适合项目的直播实时美颜SDK

当下&#xff0c;越来越多的主播和内容创作者依赖于实时美颜技术&#xff0c;以确保他们在镜头前始终保持最佳状态。为了实现这一目标&#xff0c;许多开发者和团队开始探索主播直播美颜SDK&#xff0c;但在选择适合项目的SDK时&#xff0c;性能与效果的平衡变得尤为重要。 本…