当前位置: 首页 > news >正文

c++进阶之----二叉搜索树

一、概念与性质

二叉搜索树(Binary Search Tree,BST)是一种特殊的二叉树结构,具有以下性质:

1)若任意节点的左子树不为空,则左子树上所有节点的值均小于它的根节点的值。

2)若任意节点的右子树不为空,则右子树上所有节点的值均大于它的根节点的值。

3)任意节点的左子树、右子树均为二叉搜索树。

 二、二叉搜索树的特性


1. 中序遍历有序

      对 BST 进行中序遍历(左 → 根 → 右),结果是一个升序序列。
   - 示例中的树中序遍历结果:`1, 3, 4, 6, 7, 8, 10, 13, 14`

2.性能分析

最优情况下,其为完全二叉树(或者接近完全二叉树),其高度为: log 2 N
最差情况下,二叉搜索树退化为单支树(或者类似单支),如图,其高度为: N
所以综上所述,二叉搜索树增删查改时间复杂度为: O ( N )

3. 高效查找:利用有序性,每次比较可排除一半子树。


4. 动态操作:支持插入、删除、查找操作,时间复杂度与树的高度相关。

三、代码详解

1.树的创建

大致思路和我们之前创建二叉树是一样的,先造结点,之后再组装成树,只不过在这里我们没有再写一个初始化函数的必要了,因为我们可以利用c++的初始化列表在造结点的时候变完成初始化!具体代码如下:

template<class k>
struct BSTNode
{
	k _key;
	BSTNode<k>* _left;
	BSTNode<k>* _right;

	BSTNode(const k& key)
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{}
};

template<class k>
class BSTree
{
	typedef BSTNode<k> Node;
public:
private:
	Node* _root = nullptr;
};

2.中序遍历

 这里和之前二叉树中序遍历代码基本一样,只不过我们将其进行了一下封装,详见代码注释!

void InOrder()
{
	_InOrder(_root);
	cout << endl;
}
//由于我们之后要遍历这棵树,还要传参,很麻烦,而且_root还是私有成员,我们不如在把这个函数封装一下,利于调用
void _InOrder(Node* root)
{
	if (root == nullptr)
	{
		return;
	}
	_InOrder(root->_left);
	cout << root->_key << " ";
	_InOrder(root->_right);
}

3.插入功能的实现

由搜索二叉树的概念及性质我们知道,对于任意一个节点X来说,其左子树任意结点的值要小于X结点的值,其右子树任意的结点值要大于结点X,我们可以用这个性质进行插入功能的实现。具体方法如下:

1)首先确定要在什么位置插入,由于这是树状结构,如果我们只用一个指针的话即便找到位置也无法插入,因为指针不能倒着走回去,所以在这里我们可以借用之前的双指针法,依次定位追踪父节点和子节点,在结合上文所说的,大于当前结点向右看,小于向左看,从而确定插入的位置 

2)找到插入的位置之后,直接将要插入的数据打包成结点,并根据BSTree的性质,判断插在左子树还是右子树

注:插入值跟当前结点相等的值,可以往右走,也可以往左走,找到空位置,插入新结点。

(要注意的是要保持逻辑一致性,插入相等的值不要一会往右走,一会往左走)

代码实现如下:

bool Insert(const k& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		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这个位置插入结点
	cur = new Node(key);
	if (parent->_key < key)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	return true;
}

 测试一下:

void test1()
{
	int a[]= { 8, 3, 1, 10, 1, 6, 4, 7, 14, 13 };
	BSTree<int> bst;
	for (auto e : a)
	{
		bst.Insert(e);
	}
	bst.InOrder();
}
int main()
{
	test1();     //输出1 3 4 6 7 8 10 13 14
	return 0;
}

4.查找功能的实现

查找功能的代码较为简单,利用BStree的性质遍历即可,代码如下:

void 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 true;
		}
	}
	return false;
}

5.删除功能的实现

要删除的结点X分为如下几种情况:

1)X的左右孩子均为空

2)X的左右孩子中有一个为空

3)X的左右孩子均不为空

解决方法如下:首先找到要删除的结点,

若为第一种情况,则直接删除

若为第二种情况,下一步要判断结点X是左孩子还是右孩子,并将X的父节点与X的子节点链接起来

若为第三种情况, 由于直接删的代价太大,我们还是借助堆的删除的方法,交换法,找左子树的最大结点(最右结点)或者右子树的最小结点(最左结点),将二者交换,之后准备删除工作,首先要判断的是X结点在其父节点的哪一侧,(具体原因详见代码)之后删除结点,并将其父节点与X的子节点连接起来即可

bool Erase(const k& key)
{
	Node* parent = nullptr;
	Node* cur = _root;
	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 (cur == parent->_right)
					{
						parent->_right = cur->_right;
					}
					else
					{
						parent->_left = cur->_right;
					}
				}
				delete cur;
			}
			else if (cur->_right == nullptr)
			{
				if (cur == _root)
				{
					_root = cur->_left;
				}
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
				}
				delete cur;
			}
			else
			{
				// 找右子树的最小节点(最左节点)替代
				Node* replaceparnet = cur;
				Node* replace = cur->_right;
				//找最左节点
				while (replace->_left)
				{
					replaceparnet = replace;
					replace = replace->_left;
				}
				swap(cur->_key, replace->_key);
				//此时还要判断一下replace在哪一侧,因为假如replace在右,而parent还有左孩子
				//此时直接改会导致原来的子树失联
				if (replaceparnet->_left == replace)
				{
					//由于此时replace已经是最左结点(其没有左孩子了,但是右孩子不确定),
					// 所以parent的左孩子和replace的右孩子建立关系
					replaceparnet->_left = replace->_right;
				}
				else
				{
					//解释同上,确定好左右关系即可
					replaceparnet->_right = replace->_right;
				}
				delete replace;
			}
			return true;
		}
	}
	return false;
}

6.总体代码汇总

 这是bstree.h文件

#pragma once
#include<iostream>
using namespace std;
template<class k>
struct BSTNode
{
	k _key;
	BSTNode<k>* _left;
	BSTNode<k>* _right;

	BSTNode(const k& key)
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{}
};

template<class k>
class BSTree
{
	typedef BSTNode<k> Node;
public:
	bool Insert(const k& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			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这个位置插入结点
		cur = new Node(key);
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		return true;
	}


	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}
	//由于我们之后要遍历这棵树,还要传参,而且_root还是私有成员,我们不如在把这个函数封装一下
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}


	void 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 true;
			}
		}
		return false;
	}


	bool Erase(const k& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		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 (cur == parent->_right)
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}
					}
					delete cur;
				}
				else if (cur->_right == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_left;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
				}
				else
				{
					// 找右子树的最小节点(最左节点)替代
					Node* replaceparnet = cur;
					Node* replace = cur->_right;
					//找最左节点
					while (replace->_left)
					{
						replaceparnet = replace;
						replace = replace->_left;
					}
					swap(cur->_key, replace->_key);
					//此时还要判断一下replace在哪一侧,因为假如replace在右,而parent还有左孩子
					//此时直接改会导致原来的子树失联
					if (replaceparnet->_left == replace)
					{
						//由于此时replace已经是最左结点(其没有左孩子了,但是右孩子不确定),
						// 所以parent的左孩子和replace的右孩子建立关系
						replaceparnet->_left = replace->_right;
					}
					else
					{
						//解释同上,确定好左右关系即可
						replaceparnet->_right = replace->_right;
					}
					delete replace;
				}
				return true;
			}
		}
		return false;
	}

private:
	Node* _root = nullptr;
};

这是Test.cpp文件

#include "bstree.h"
void test1()
{
	int a[]= { 8, 3, 1, 10, 1, 6, 4, 7, 14, 13 };
	BSTree<int> bst;
	for (auto e : a)
	{
		bst.Insert(e);
	}
	bst.InOrder();
	bst.Insert(18);
	bst.InOrder();
	bst.Insert(5);
	bst.InOrder();
	bst.Insert(0);
	bst.InOrder();
	bst.Erase(18);
	bst.InOrder();
	bst.Erase(0);
	bst.InOrder();
	bst.Erase(5);
	bst.InOrder();
	for (auto e : a)
	{
		bst.Erase(e);
		bst.InOrder();
	}
}
int main()
{
	test1();     
	return 0;
}

7.扩展

在现实中,我们可能很少遇到结点变量只有一个的情况,比如我们想做一个英汉搜索字典,或者统计一下某个单词或字符出现了多少次,那我们便可以加一个模板参数,具体见代码!

这是.h文件

namespace key_value
{
	template<class k,class v>
	struct BSTNode
	{
		k _key;
		v _value;
		BSTNode<k,v>* _left;
		BSTNode<k,v>* _right;

		BSTNode(const k& key,const v& value)
			:_key(key)
			,_value(value)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

	template<class k,class v>
	class BSTree
	{
		typedef BSTNode<k,v> Node;
	public:
		~BSTree()
		{
			Destory(_root);
			_root = nullptr;
		}
		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这个位置插入结点
			cur = new Node(key,value);
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}
			return true;
		}


		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
		//由于我们之后要遍历这棵树,还要传参,而且_root还是私有成员,我们不如在把这个函数封装一下
		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			_InOrder(root->_left);
			cout << root->_key << " " << root->_value << endl;
			_InOrder(root->_right);
		}


		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* parent = nullptr;
			Node* cur = _root;
			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 (cur == parent->_right)
							{
								parent->_right = cur->_right;
							}
							else
							{
								parent->_left = cur->_right;
							}
						}
						delete cur;
					}
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							if (cur == parent->_left)
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}
						delete cur;
					}
					else
					{
						// 找右子树的最小节点(最左节点)替代
						Node* replaceparnet = cur;
						Node* replace = cur->_right;
						//找最左节点
						while (replace->_left)
						{
							replaceparnet = replace;
							replace = replace->_left;
						}
						swap(cur->_key, replace->_key);
						swap(cur->_value, replace->_key);
						//此时还要判断一下replace在哪一侧,因为假如replace在右,而parent还有左孩子
						//此时直接改会导致原来的子树失联
						if (replaceparnet->_left == replace)
						{
							//由于此时replace已经是最左结点(其没有左孩子了,但是右孩子不确定),
							// 所以parent的左孩子和replace的右孩子建立关系
							replaceparnet->_left = replace->_right;
						}
						else
						{
							//解释同上,确定好左右关系即可
							replaceparnet->_right = replace->_right;
						}
						delete replace;
					}
					return true;
				}
			}
			return false;
		}

		void Destory(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}
			Destory(root->_left);
			Destory(root->_right);
			delete root;
		}
	private:
		Node* _root = nullptr;
	};
}

 这是英汉词典和统计次数的示例

void test2()
{
	using namespace key_value;
		key_value::BSTree<string, string> dict;
		dict.Insert("left", "左边");
		dict.Insert("right", "右边");
		dict.Insert("insert", "插入");
		dict.Insert("string", "字符串");
		//查字典
		string str;
		while (cin >> str)
		{
			auto ret = dict.Find(str);
			if (ret)
			{
				cout << "->" << ret->_value << endl;
			}
			else
			{
				cout << "无此单词,请重新输入" << endl;
			}
		}
		//数水果
		string arr[] = { "苹果", "西瓜", "苹果","苹果","苹果", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
		key_value::BSTree<string, int> countTree;
		for (auto& e : arr)
		{
			BSTNode<string, int>* ret = countTree.Find(e);
			if (ret == nullptr)
			{
				countTree.Insert(e, 1);
			}
			else
			{
				ret->_value++;
			}
		}
		countTree.InOrder();
	
}

这是测试结果

相关文章:

  • 【射频仿真学习笔记】Cadence的Layout EXL与ADS dynamic link联动后仿
  • 【组态PLC】基于西门子s7-200PLC和组态王中央空调的循环控制系统【含PLC组态源码 M019期】
  • Java注解的原理
  • Kotlin 随记 (1)
  • RAG-202502
  • Mellanox的LAG全称是什么?网卡的创建机制如何?(Link Aggregation Group 链路聚合组)
  • WiFi IEEE 802.11协议精读:IEEE 802.11-2007,6,MAC service definition MAC服务定义
  • TinyEngine v2.2版本发布:支持页面嵌套路由,提升多层级路由管理能力开发分支调整
  • HBase常用的Filter过滤器操作
  • Spring Data JPA vs MyBatis:ORM框架如何选择?
  • 【IntelliJ IDEA】关于设置固定的文件格式(包括注释、版权信息等)的方法
  • 轨迹控制--odrive的位置控制---负载设置
  • lmstdio大模型——本地大模型python函数调用设计
  • Python中有哪些基本数据类型?
  • 软考~系统规划与管理师考试——真题篇——2021年5月——论文——纯享题目版
  • ClickHouse 的分区、分桶和分片详解
  • 计算机毕业设计SpringBoot+Vue.js学科竞赛管理系统(源码+文档+PPT+讲解)
  • qt5的中文乱码问题,QString、QStringLiteral 为 UTF-16 编码
  • 状态模式
  • RK3588部署YOLOv8(1):YOLOv8和YOLOv8-pose转ONNX及Python后处理代码实现
  • 大型长读长RNA测序数据集发布,有助制定精准诊疗策略
  • 南部战区位南海海域进行例行巡航
  • 对谈|李钧鹏、周忆粟:安德鲁·阿伯特过程社会学的魅力
  • 广西干旱程度有所缓解,未来一周旱情偏重地区降水量仍不足
  • 《奇袭白虎团》原型人物之一赵顺合辞世,享年95岁
  • 传智教育连续3个交易日跌停:去年净利润由盈转亏