C++二叉搜索树

目录

一、二叉搜索树概念

二、二叉搜索树的操作

1.查找操作

2.插入操作

3.中序遍历

4.删除操作

5.完整代码

三、二叉搜索树的K模型和KV模型

1.K模型

2.KV模型

四、二叉搜索树的查找的时间复杂度


一、二叉搜索树概念

如果一个二叉树具有如下性质,那么就叫它二叉搜索树:

1.左子树上的所有节点值都小于根节点

2.右子树上的所有节点值都小于根节点

3.左右子树也为二叉搜索树

二叉搜索树(Binary Search Tree,BST)又叫二叉排序树或者二叉查找树

叫二叉排序树是因为:二叉搜索树中序遍历时得到的数据为升序排序

(通常来说,二叉搜索树不允许数据冗余,即数据重复)

二、二叉搜索树的操作

二叉搜索树节点的结构:

template<class T>
struct BSTreeNode {
	T _val;
	struct BSTreeNode* _left;
	struct BSTreeNode* _right;
	//构造函数
	BSTreeNode(T val)
		:_val(val),
		_left(nullptr),
		_right(nullptr)
	{}
};
1.查找操作

从根节点开始找,值比根节点小则向左查找,值比根节点大则向右查找。最多查找高度次

//查找
bool Find(const K& key)
{
	Node* cur = _root;
	else
	{
		while (cur)
		{
			if (key < cur->_key)
				cur = cur->_left;
			else if (key > cur->_key)
				cur = cur->_right;
			else
				return true;
		}
	}
	return false;
}
2.插入操作

二叉搜索树只能插入可以比较大小的数据

//插入
bool Insert(const K& key)
{
	Node* cur = _root;
	Node* parent = nullptr;
	if (cur == nullptr)//树为空
	{
		_root = new Node(key);
		return true;
	}
	else//树不为空
	{
		while (cur)
		{
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;//二叉搜索树不允许数据冗余
			}
		}
		Node* newnode = new Node(key);
		if (key < parent->_key)
			parent->_left = newnode;
		else if (key > parent->_key)
			parent->_right = newnode;
		return true;
	}
}
3.中序遍历

中序遍历时一个递归操作,所以一定会有根节点作为参数。但是二叉搜索树对象调用中序遍历函数时,无法将其根节点不方便传入函数(除非再设计一个根节点的接口),所以给中序遍历函数套壳,将根节点传入实现函数,再给实现函数套壳,二叉搜索树调用套壳函数,即调用实现函数

//中序遍历(排序)(递归套壳)
void InOrder()
{
	_InOrder(_root);
	cout << endl;
}
//中序遍历(排序)(实现原理)
void _InOrder(Node* root)
{
	if (root == nullptr)
		return;
	_InOrder(root->_left);
	cout << root->_key << " ";
	_InOrder(root->_right);
}
4.删除操作

首先查找要删除的数据是否在二叉树中,如果不在则返回false

如果在,则有以下四种情况(将要删除的节点设为delete):

1.delete没有孩子节点:delete为左节点,则将父亲节点的左孩子置为空;delete为右节点,则将父亲节点的右孩子置为空

2.delete只有左孩子节点:delete为左节点,则将父亲节点的左孩子指向delete的左孩子;delete为右节点,则将父亲节点的右孩子指向delete的左孩子

3.delete只有右孩子节点:delete为右节点,则将父亲节点的左孩子指向delete的右孩子;delete为右节点,则将父亲节点的右孩子指向delete的右孩子

4.delete左右孩子节点都有:将左子树的最大节点(左子树中最右边的节点)或者右子树的最小节点(右子树中最左边的节点)的值与delete的值进行交换,再删除delete

(如何寻找左子树的最大节点?左子树的最大节点就是左子树中最右边的节点)

(如何寻找右子树的最小节点?右子树的最小节点就是右子树中最左边的节点)

经过分析,发现情况1与情况2或者3可以合并(以情况1和情况2合并为例),因为情况2中将父亲节点的孩子指向delete的孩子,中情况1中同样适用,只不过情况1中delete的孩子为空,正好符合将父亲节点的孩子置空

实际写代码过程中,会发现有两个坑:

第一个坑是情况1、2、3,删除节点时,如果该节点是根节点,根节点没有父亲节点,代码出错,所以需要加一个判断条件

第二个坑是情况4,如果出现右子树的最小节点刚好是根节点的右孩子的情况(左子树的最大节点刚好是根节点的左孩子的情况),那么RightMin就是右孩子节点,而其他情况都是左孩子节点,这就需要我们单独判断一下RightMin的节点情况,再进行删除

//删除
bool Erase(const K& key)
{
	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//找到值
		{
			if (cur->_right == nullptr)//情况1和情况2合并(没有孩子节点或者只有左孩子节点)
			{
				if (cur == _root)//特殊情况:删除根节点
					_root = _root->_left;
				else
				{
					if (cur == parent->_left)//左结点
						parent->_left = cur->_left;
					else if (cur == parent->_right)//右结点
						parent->_right = cur->_left;
				}
				delete cur;
			}
			else if (cur->_left == nullptr && cur->_right != nullptr)//情况3(只有右孩子节点)
			{
				if (cur == _root)//特殊情况:删除根节点
					_root = _root->_left;
				else
				{
					if (cur == parent->_left)//左结点
						parent->_left = cur->_right;
					else if (cur == parent->_right)//右结点
						parent->_right = cur->_right;
				}
				delete cur;
			}
			else//情况4(左右孩子节点都有)(用右子树的最小节点替换)
			{
				Node* RightMinParent = cur;
				Node* RightMin = cur->_right;//先确定在右子树中寻找
				while (RightMin->_left)
				{
					RightMinParent = RightMin;
					RightMin = RightMin->_left;
				}
				swap(cur->_key, RightMin->_key);//交换
				if (RightMin == RightMinParent->_left)//RightMin是左孩子
					RightMinParent->_left = RightMin->_right;//删除节点,将其父亲节点连接RightMin的右孩子(因为RightMin是最左节点,所以它一定是父亲节点的左孩子,且一定没有左孩子)
				else//RightMin是右孩子
					RightMinParent->_right = RightMin->_right;//删除节点,将其父亲节点连接RightMin的右孩子(因为RightMin是最左节点,所以它一定是父亲节点的左孩子,且一定没有左孩子)
				delete(RightMin);
			}
			return true;
		}
	}
	return false;
}
5.完整代码
namespace key {
	template<class K>
	struct BSTreeNode {
		K _key;
		BSTreeNode<K>* _left;
		BSTreeNode<K>* _right;

		//构造函数
		BSTreeNode(K key)
			:_key(key),
			_left(nullptr),
			_right(nullptr)
		{}
	};

	template<class K>
	class BSTree {
		typedef struct BSTreeNode<K> Node;
	public:
		//构造函数
		BSTree()
			:_root(nullptr)
		{}
		//插入
		bool Insert(const K& key)
		{
			Node* cur = _root;
			Node* parent = nullptr;
			if (cur == nullptr)//树为空
			{
				_root = new Node(key);
				return true;
			}
			else//树不为空
			{
				while (cur)
				{
					if (key < cur->_key)
					{
						parent = cur;
						cur = cur->_left;
					}
					else if (key > cur->_key)
					{
						parent = cur;
						cur = cur->_right;
					}
					else
					{
						return false;//二叉搜索树不允许数据冗余
					}
				}
				Node* newnode = new Node(key);
				if (key < parent->_key)
					parent->_left = newnode;
				else if (key > parent->_key)
					parent->_right = newnode;
				return true;
			}
		}
		//查找
		bool Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_key)
					cur = cur->_left;
				else if (key > cur->_key)
					cur = cur->_right;
				else
					return true;
			}
			return false;
		}
		//中序遍历(排序)(递归套壳)
		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
		//删除
		bool Erase(const K& key)
		{
			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//找到值
				{
					if (cur->_right == nullptr)//情况1和情况2合并(没有孩子节点或者只有左孩子节点)
					{
						if (cur == _root)//特殊情况:删除根节点
							_root = _root->_left;
						else
						{
							if (cur == parent->_left)//左结点
								parent->_left = cur->_left;
							else if (cur == parent->_right)//右结点
								parent->_right = cur->_left;
						}
						delete cur;
					}
					else if (cur->_left == nullptr && cur->_right != nullptr)//情况3(只有右孩子节点)
					{
						if (cur == _root)//特殊情况:删除根节点
							_root = _root->_left;
						else
						{
							if (cur == parent->_left)//左结点
								parent->_left = cur->_right;
							else if (cur == parent->_right)//右结点
								parent->_right = cur->_right;
						}
						delete cur;
					}
					else//情况4(左右孩子节点都有)(用右子树的最小节点替换)
					{
						Node* RightMinParent = cur;
						Node* RightMin = cur->_right;//先确定在右子树中寻找
						while (RightMin->_left)
						{
							RightMinParent = RightMin;
							RightMin = RightMin->_left;
						}
						swap(cur->_key, RightMin->_key);//交换
						if (RightMin == RightMinParent->_left)//RightMin是左孩子
							RightMinParent->_left = RightMin->_right;//删除节点,将其父亲节点连接RightMin的右孩子(因为RightMin是最左节点,所以它一定是父亲节点的左孩子,且一定没有左孩子)
						else//RightMin是右孩子
							RightMinParent->_right = RightMin->_right;//删除节点,将其父亲节点连接RightMin的右孩子(因为RightMin是最左节点,所以它一定是父亲节点的左孩子,且一定没有左孩子)
						delete(RightMin);
					}
					return true;
				}
			}
			return false;
		}
	private:
		Node* _root;

		//中序遍历(排序)(实现原理)
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;
			_InOrder(root->_left);
			cout << root->_key << " ";
			_InOrder(root->_right);
		}
	};
}

三、二叉搜索树的K模型和KV模型

1.K模型

K模型中只有key作为关键码,结构中只需要存储key即可

应用场景:给定一个单词,判断该单词是否拼写正确

将词库中的每个单词作为一个key,构建一棵二叉树搜索树;在二叉搜索树中查找给定单词是否存在,存在则拼写正确,不存在则拼写错误

2.KV模型

KV模型中没有个关键码key都有一个与之对应的值Value,即<Key,Value>键值对

(KV模型是支持修改的,可以修改value值;但是K模型不能修改,修改后就不能保证是二叉搜索树了)

应用场景1:英汉词典中,英文与中文的对应关系,通过英文单词快速找到与其对应的中文,<word,chinese>就是一种键值对

应用场景2:统计单词出现次数,给定一个单词就可以知道它的出现次数,<word,count>也是一种键值对

二叉搜索树的KV模型源代码(在K模型基础上稍加改动即可):

//源代码
namespace key_value {
	template<class K, class V>
	struct BSTreeNode {
		K _key;
		V _value;
		BSTreeNode<K,V>* _left;
		BSTreeNode<K,V>* _right;

		//构造函数
		BSTreeNode(K key, V value)
			:_key(key),
			_value(value),
			_left(nullptr),
			_right(nullptr)
		{}
	};

	template<class K, class V>
	class BSTree {
		typedef struct BSTreeNode<K,V> Node;
	public:
		//构造函数
		BSTree()
			:_root(nullptr)
		{}
		//插入
		bool Insert(const K& key, const V& value)
		{
			Node* cur = _root;
			Node* parent = nullptr;
			if (cur == nullptr)//树为空
			{
				_root = new Node(key, value);
				return true;
			}
			else//树不为空
			{
				while (cur)
				{
					if (key < cur->_key)
					{
						parent = cur;
						cur = cur->_left;
					}
					else if (key > cur->_key)
					{
						parent = cur;
						cur = cur->_right;
					}
					else
					{
						return false;//二叉搜索树不允许数据冗余
					}
				}
				Node* newnode = new Node(key, value);
				if (key < parent->_key)
					parent->_left = newnode;
				else if (key > parent->_key)
					parent->_right = newnode;
				return true;
			}
		}
		//查找
		Node* Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_key)
					cur = cur->_left;
				else if (key > cur->_key)
					cur = cur->_right;
				else
					return cur;
			}
			return nullptr;
		}
		//中序遍历(排序)(递归套壳)
		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
		//删除
		bool Erase(const K& key)
		{
			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//找到值
				{
					if (cur->_right == nullptr)//情况1和情况2合并(没有孩子节点或者只有左孩子节点)
					{
						if (cur == _root)//特殊情况:删除根节点
							_root = _root->_left;
						else
						{
							if (cur == parent->_left)//左结点
								parent->_left = cur->_left;
							else if (cur == parent->_right)//右结点
								parent->_right = cur->_left;
						}
						delete cur;
					}
					else if (cur->_left == nullptr && cur->_right != nullptr)//情况3(只有右孩子节点)
					{
						if (cur == _root)//特殊情况:删除根节点
							_root = _root->_left;
						else
						{
							if (cur == parent->_left)//左结点
								parent->_left = cur->_right;
							else if (cur == parent->_right)//右结点
								parent->_right = cur->_right;
						}
						delete cur;
					}
					else//情况4(左右孩子节点都有)(用右子树的最小节点替换)
					{
						Node* RightMinParent = cur;
						Node* RightMin = cur->_right;//先确定在右子树中寻找
						while (RightMin->_left)
						{
							RightMinParent = RightMin;
							RightMin = RightMin->_left;
						}
						swap(cur->_key, RightMin->_key);//交换
						if (RightMin == RightMinParent->_left)//RightMin是左孩子
							RightMinParent->_left = RightMin->_right;//删除节点,将其父亲节点连接RightMin的右孩子(因为RightMin是最左节点,所以它一定是父亲节点的左孩子,且一定没有左孩子)
						else//RightMin是右孩子
							RightMinParent->_right = RightMin->_right;//删除节点,将其父亲节点连接RightMin的右孩子(因为RightMin是最左节点,所以它一定是父亲节点的左孩子,且一定没有左孩子)
						delete(RightMin);
					}
					return true;
				}
			}
			return false;
		}
	private:
		Node* _root;

		//中序遍历(排序)(实现原理)
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;
			_InOrder(root->_left);
			cout << root->_key << ":" << root->_value << endl;
			_InOrder(root->_right);
		}
	};
}
//测试代码
void test1()//英汉翻译
{
	key_value::BSTree<string, string> dict;
	dict.Insert("string", "字符串");
	dict.Insert("tree", "树");
	dict.Insert("left", "左边、剩余");
	dict.Insert("right", "右边");
	dict.Insert("sort", "排序");
	string str;
	while (cin >> str)
	{
		key_value::BSTreeNode<string, string>* ret = dict.Find(str);
		if (ret)
			cout << ret->_value << endl;
		else
			cout << "无此单词,请重新输入" << endl;
	}
}
void test2()//统计单词
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };
	key_value::BSTree<string, int> countTree;
	for (auto e : arr)
	{
		auto ret = countTree.Find(e);
		if (ret)
			ret->_value++;
		else
			countTree.Insert(e, 1);
	}
	countTree.InOrder();
}
int main()
{
	test1();
	test2();
	return 0;
}

四、二叉搜索树的查找的时间复杂度

二叉搜索树的时间复杂度通常为logN,最多查找高度次

但是当插入的数据接近有序时,查找效率会直线下降,所以才出现了AVL树和红黑树来解决这个问题

  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南林yan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值