C++数据结构——二叉搜索树详解

目录

一,关于二叉搜索树

1.1 概念

1.2 基本结构

二,二叉搜索树接口实现

2.1 插入

2.2 查找

2.3 打印

2.4* 删除

三,二叉搜索树接口递归实现

3.1 查找

3.2 插入

3.3 删除

 四,二叉搜索树的默认成员函数

五,测试代码

六,二叉搜索树的应用

6.1 KeyValue

6.2 改造二叉搜索树

6.3 测试代码

6.3.1 查找单词

6.3.2 统计水果出现的次数


一,关于二叉搜索树

1.1 概念

二叉搜索树又称二叉排序树,具有以下性质:

①一节点左子树节点的值都小于该节点的值

②一节点右子树的值都大于该节点的值

③一节点的左右子树也是二叉搜索树

简单来说就是左孩子节点比我小,右孩子节点比我大,所以以中序遍历二叉搜索树时打印的结果是从小到大的,所以二叉搜索树又被称为二叉排序树 

1.2 基本结构

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:
    //接口以及默认成员函数实现

private:
	Node* _root = nullptr;
}

二,二叉搜索树接口实现

2.1 插入

二叉搜索树的插入不难,如果数为空直接新增根节点,如果不为空,比我小走左边,比我大走右边,走到空的时候新增节点并完成链接,如下代码和注释

bool Insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
	//先找到合适的插入位置
	Node* parent = nullptr; //创建parent记录cur上一个节点位置,方便后面链接
	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);
    //cur是局部变量,出了函数作用域后没了,不能直接cur赋值新节点
    //所以需要把前后链接起来,这时候轮到我们的parent登场了
	if (key > parent->_key) //插入的值比父节点大,走右边
	{
		parent->_right = cur;
	}
	else //插入的值比父节点小,走左边
	{
		parent->_left = cur;
	}
	return true;
}

2.2 查找

由于二叉搜索树的性质,每次查找一个树的时候只需要走树的高度次就可以查到了,查找效率非常高,所以二叉搜索树还有个名称叫做二叉查找树

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; //查找失败
}

2.3 打印

打印我们以中序遍历打印,所以我们使用递归实现打印接口,如下代码:

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

private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}

2.4* 删除

由于二叉搜索树关联式容器的特殊性质,删除一个节点会改变整个容器的结构与性质,所以每个关联式容器的删除操作需要做非常多的处理

对于二叉搜索树的删除,我们大致分为下面几个情况:

①:删除一个节点,需要让被删除节点的父节点指向被删除节点的孩子节点

②:删除一个节点,需要让被删除节点的父节点指向被删除节点的孩子节点

③:在被删除节点的右子树找一个最大值的节点替换两个节点的值,再进行删除(或者找左子树最大的点替换)

如下图 

 具体实现结合下面代码和注释:

bool Erase(const K& key)//难
{
	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//找到了,开始删除
		{
			// 1、左为空
			if (cur->_left == nullptr)//要删除的节点的左子树为空
			{
				if (cur == _root)//极端情况,要干掉的是根
				{
					_root = cur->_right;
				}
				else
				{
					if (cur == parent->_left) //cur是父亲的左
					{
						parent->_left = cur->_right;
                        //让父亲的左指向要删除节点的右,因为cur的左为空
					}
					else //cur是父亲的右
					{
						parent->_right = cur->_right; 
                        //让父亲的右指向要删除节点的右,因为cur的左为空
					}
				}
				delete cur;
				cur = nullptr;
			}
			// 2、右为空
			else if (cur->_right == nullptr)//要删除的节点的右子树为空
			{
				if (_root == cur)//极端情况,要干掉的是根
				{
					_root = cur->_left;
				}
				else
				{
					if (cur == parent->_left) //cur是父亲的左
					{
						parent->_left = cur->_left; 
                        //让父亲的左指向要删除节点的左,因为cur的右为空
					}
					else //cur是父亲的右
					{
						parent->_right = cur->_left;
                        //让父亲的右指向要删除节点的左,因为cur的右为空
					}
				}
				delete cur;
				cur = nullptr;
			}
			// 3、左右都不为空
			else
			{
				// 找右子树最小节点 或 找左子树的最大节点 替代要删除的值
				Node* pminRight = cur;
				Node* minRight = cur->_right;
				//找右树最小节点
				while (minRight->_left)
				{
					pminRight = minRight;
					minRight = minRight->_left;
				}
				cur->_key = minRight->_key;//交换要删除的值
				if (pminRight->_left == minRight)
                {
					pminRight->_left = minRight->_right;
				}
                else //这里看不懂可以结合上面的那个图中的要删除3和8的场景来理解
                {
					pminRight->_right = minRight->_right;
				}
                delete minRight;
			}
			return true;
		}
	}
	//没找到,要删除的值不存在
	return false;
}

三,二叉搜索树接口递归实现

3.1 查找

public:
	bool FindR(const K& key)///递归查找
	{
		return _FindR(_root, key);
	}

private:
	bool _FindR(Node* root,const K& key)//递归查找子函数
	{
		//最多找高度次,O(h) h是树的高度
		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;
	}

3.2 插入

实现插入子递归函数的时候,我们选择用Node* &root做函数参数,原因如下:

插入的目标是插入合适的值,并且和父亲链接起来,比如要在某个节点右边插入一个值,递归时就是 _Insert(root->right,key),我们用Node* &root之后,这个root就间接代表了上一个节点的right指针,然后我们再root = new Node(key),相当于生成一个新节点并直接赋值给父节点的右,间接完成链接,如下代码

public:
	bool InsertR(const K& key)//递归插入
	{
		return _InsertR(_root, key);
	}

private:
    bool _InsertR(Node* &root, const K& key)//递归插入
	{
		if (root == nullptr)
		{
			root = new Node(key);//root是形参,所以前面用引用
			return true;
		}
		if (root->_key < key)
			return _InsertR(root->_right, key);
		else if (root->_key > key)
			return _InsertR(root->_left, key);
		else //相等
			return false;
	}

3.3 删除

删除我们和插入同理,用Node* &root做返回值,利用我们上面的思想完成递归实现,如下代码:

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

private:
	bool _EraseR(Node* &root, const 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//找到了,开始删除
		{
			Node* del = root;
			if (root->_left == nullptr)
			{
				//间接完成链接,这里的root在递归中可以间接认为是上一个节点的right或left,只是用了一个root引用来代替
				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 _EraseR(root->_right, key);
			}
			delete del;
			return true;
		}
	}

 四,二叉搜索树的默认成员函数

public:
    //由于我们自己实现了析构函数,所以编译器不会自动生成默认构造
    //这条语句强制编译器生成默认构造函数
	BSTree() = default;
	BSTree(const BSTree<K>& t)//拷贝构造
	{
		_root = _Copy(t._root);
	}
	~BSTree()//析构
	{
		_Destory(_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;
	}

五,测试代码

int main()
{
	BSTree<int> t1;
	int a[] = { 8,3,1,10,6,4,7,14,13,4,3,4 };
	for (auto e : a)
	{
		t1.Insert(e);
	}
	BSTree<int> t2 = t1;
	t1.InOrder();
	t2.InOrder();
	cout << "----------------" << endl;
	t1.Insert(15);
	t1.Insert(5);
	t2.Erase(8);
	t2.Erase(13);
	cout << t1.Find(15) << endl;
	cout << t2.Find(13) << endl;
	cout << "----------------" << endl;
	t1.InOrder();
	t2.InOrder();
	return 0;
}

六,二叉搜索树的应用

6.1 KeyValue

1,Key模型:只有Key作为关键码,结构中只需存储Key,搜索时只搜索Key

比如:给一个单词word,判断该单词是否拼写正确,方法如下

①以词库中所有单词集合中的每个单词作为Key构建一颗搜索二叉树

②在二叉搜索树中查找该单词的存在,存在则拼写正确,不存在则拼写错误

2,KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。

①比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word,chinese>就构成一种键值对

②再比如统计单词次数,统计成功后,给定单词就可以快速找到其出现的次数,单词与其出现次数就是<word,count>就构成一种键值对

6.2 改造二叉搜索树

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:
	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);//cur是局部变量,出了函数作用域后没了,需要把前后链接起来
		//创建parent记录cur上一个节点位置,方便链接
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		return true;
	}
	void InOrder()
	{
		_InOrder(_root);
	}
	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;
	}
private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << ":" << root->_value << endl;;
		_InOrder(root->_right);
	}
	bool FindR(const K& key)///递归查找
	{
		return _FindR(_root, key);
	}
	Node* _root = nullptr;
};

6.3 测试代码

6.3.1 查找单词

void TestBSTree1()
{
	BSTree<string, string> dict;
	dict.Insert("sort", "排序");
	dict.Insert("left", "左边");
	dict.Insert("right", "右边");
	dict.Insert("string", "字符串");
	dict.Insert("insert", "插入");
	string str;
	while (cin >> str)
	{
		BSTreeNode<string, string>* ret = dict.Find(str);
		if (ret)
		{
			cout << ":" << ret->_value << endl;
		}
		else
		{
			cout << "->无此单词" << endl;
		}
	}
}

 

6.3.2 统计水果出现的次数

void TestBSTree2()
{
	string arr[] = { "苹果","苹果", "苹果", "苹果", "苹果", "香蕉","草莓","苹果", };
	BSTree<string, int> countTree;
	for (auto& str : arr)
	{
		//BSTreeNode<string, int>* ret = countTree.Find(str);
		auto ret = countTree.Find(str);
		if (ret)//找到水果名
		{
			ret->_value++;
		}
		else//没有找到水果,该水果第一次出现
		{
			countTree.Insert(str, 1);
		}
	}
	countTree.InOrder();
}

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

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

相关文章

国产划片机品牌众多,如何选择优质的供应商?

在半导体行业的发展浪潮中&#xff0c;划片机作为关键设备之一&#xff0c;其性能和质量对于生产过程的高效性和产品的质量具有至关重要的影响。近年来&#xff0c;国产划片机的品牌数量不断增多&#xff0c;为半导体行业提供了更多的选择。然而&#xff0c;如何从众多的品牌中…

2023 英特尔On技术创新大会直播 | AI 融合发展之旅

前言 2023 年的英特尔 On 技术创新大会中国站&#xff0c;主要聚焦最新一代增强 AI 能力的计算平台&#xff0c;深度讲解如何支持开放、多架构的软件方案&#xff0c;以赋能人工智能并推动其持续发展。 大会的目标之一是优化系统并赋能开发者&#xff0c;特别注重芯片增强技术…

个人用户的数据之美:数据可视化助力解读

数据可视化是一种强大的工具&#xff0c;不仅可以为企业和专业人士提供见解&#xff0c;也对个人用户带来了许多实际的帮助。下面我就以一个数据可视化从业者的视角&#xff0c;来谈谈数据可视化对个人用户的益处&#xff1a; 首先对于个人用户来说&#xff0c;数据可视化可以让…

金蝶报表二开

本案例描述&#xff1a; 折旧明细报表中加入字段&#xff1a;存放地点、成本中心部门、使用人组织三个字段。 参考社区案例&#xff1a;报表二次开发添加自定义字段的指导方案 步骤&#xff1a; 1、加入报表插件 继承原报表的类。重写BuilderReportSqlAndTempTable、GetRe…

【Python秘技】用Python实现千图成像,千字成像,编程炫技必备!

一个千图成像&#xff0c;千字成像的程序&#xff0c;开源给大家玩玩。 用她的名字组成她的照片会不会很酷呢&#xff1f; 后续会完善更多功能&#xff0c;打包为程序。 源代码在这里&#xff1a;https://github.com/w-x-x-w/Thousand-Image-Generator 讲解在这里&#xff…

armday1

1到一百的累加

Saliency Prediction in the Deep LearningEra: Successes and Limitations

摘要&#xff1a; 近年来&#xff0c;由于深度学习和大规模注释数据的进步&#xff0c;视觉显著性模型在性能上有了很大的飞跃。然而&#xff0c;尽管付出了巨大的努力并取得了巨大的突破&#xff0c;但模型在达到人类水平的准确性方面仍然存在差距。在这项工作中&#xff0c;…

CTF命令执行部分总结

&#x1f60b;大家好&#xff0c;我是YAy_17&#xff0c;是一枚爱好网安的小白&#xff0c;正在自学ing。 本人水平有限&#xff0c;欢迎各位大佬指点&#xff0c;一起学习&#x1f497;&#xff0c;一起进步⭐️。 ⭐️此后如竟没有炬火&#xff0c;我便是唯一的光。⭐️ 关于…

keep-live原理,react-router如何实现keep-alive

3. keep-live原理&#xff0c;react-router如何实现keep-alive 先说结论&#xff1a;被keep-alive标签包裹的组件在第一次初始化时&#xff08;渲染从render开始&#xff09;会被缓存起来&#xff08;以vnode的形式&#xff09;&#xff0c;再次访问时&#xff08;actived生命周…

在Python中使用Kafka帮助我们处理数据

Kafka是一个分布式的流数据平台&#xff0c;它可以快速地处理大量的实时数据。Python是一种广泛使用的编程语言&#xff0c;它具有易学易用、高效、灵活等特点。在Python中使用Kafka可以帮助我们更好地处理大量的数据。本文将介绍如何在Python中使用Kafka简单案例。 一、安装K…

[C++]——STL简介

带你了解c的STL 前言&#xff1a;一、什么是STL?二、STL有什么版本&#xff1f;三、STL的组件有哪些&#xff1f;四、如何学习STL?五、总结 前言&#xff1a; 我写这个博客&#xff0c;是为了在学习过程中能够更加有条理&#xff0c;更加全面&#xff0c;更加清晰的学习STL。…

喜报|棱镜七彩获评江苏省专精特新中小企业

近日&#xff0c;江苏省工业和信息化厅发布《关于江苏省2023年专精特新中小企业和2020年度专精特新企业复核通过企业名单的公示》&#xff0c;棱镜七彩成功入选2023年江苏省省级专精特新中小企业名单。 图 2023年省级专精特新中小企业公式名单节选 “专精特新”是国家为鼓励中…

Nodejs 第二十五章(http)

“http” 模块是 Node.js 中用于创建和处理 HTTP 服务器和客户端的核心模块。它使得构建基于 HTTP 协议的应用程序变得更加简单和灵活。 创建 Web 服务器&#xff1a;你可以使用 “http” 模块创建一个 HTTP 服务器&#xff0c;用于提供 Web 应用程序或网站。通过监听特定的端…

docker在线安装nginx

1、查看所有镜像 1、不带容器卷常规启动nginx&#xff0c;命令如下 docker run --name nginx-test -p 8089:80 -d a6bd71f48f68 2、在宿主机创建/usr/local/data/nginxdocker/目录&#xff0c;在此目录下创建html和logs文件夹&#xff0c;然后将容器内的 nginx.conf 和 html 下…

nodejs使用nodejieba

Nodejieba是一个基于Node.js平台的中文分词模块&#xff0c;用于将中文文本切分成有意义的词汇。它是结巴中文分词的Node.js版本&#xff0c;结巴分词是一种开源的中文分词工具&#xff0c;广泛应用于中文自然语言处理领域 优点 高性能&#xff1a; Nodejieba的底层实现采用了…

使用栈的特性实现多位计算器

创建一个栈&#xff1a; //定义一个ArrayStack 表示栈 class ArrayStack2 {private int maxSize; //栈的大小private int[] stack; //定义一个栈private int top -1; //定义一个栈顶指针public ArrayStack2(int size) {maxSize size;stack new int[maxSize];}//栈满public …

matplotlib科研绘图之折线图、柱状图、散点图、误差棒

matplotlib折线图例子1 # -*- coding: utf-8 -*- # Time : 2023/12/19 10:56 # Author : 长沙有肥鱼 # FileName: 21.py # Software: PyCharm # Blog : https://blog.csdn.net/weixin_53660567?spm1010.2135.3001.5343# 导入Matplotlib库 import matplotlib import ma…

【经典LeetCode算法题目专栏分类】【第7期】快慢指针与链表

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 快慢指针 移动零 class…

azkaban编译时报错的解决方案

大数据单机学习环境搭建(11)Azkaban单机部署&#xff0c;关于Azkaban和gradle下载&#xff0c;本文编译不限于单机solo模式。 一.大多数报错处理 1.1首先操作 1)安装 git yum install git -y 2)替换 azkaban 目录下的 build.gradle 文件的 2处 repositories 信息。改为 阿里…

LVS+Keepalived集群的介绍和搭建

目录 LVSKeepalived集群的介绍 Keepalived及其工作原理 Keepalived体系主要模块及其作用 一个合格的集群应该具备的特性 健康检查&#xff08;探针&#xff09;的方式 实验&#xff1a;搭建LVSKeepalived集群 实验准备 实验步骤 LVS 部署 配置节点服务器 实验验证 实…