C++进阶:二叉搜索树

◆博主名称:少司府

欢迎来到少司府的博客☆*: .。. o(≧▽≦)o .。.:*☆

数据结构系列个人专栏:

初阶数据结构_少司府的博客-CSDN博客

C++系列 个人专栏:C++初阶C++进阶

⭐水滴石穿非一日,功不唐捐终可期

目录

1、二叉搜索树的概念及性能

[1.1 概念](#1.1 概念)

[1.2 性能分析](#1.2 性能分析)

2、二叉搜索树的实现

[2.1 插入](#2.1 插入)

[2.2 中序遍历](#2.2 中序遍历)

[2.3 查找](#2.3 查找)

[2.4 删除](#2.4 删除)

三、二叉搜索树key和key/value使用场景

[7.1 key搜索场景](#7.1 key搜索场景)

[7.2 key/value搜索场景](#7.2 key/value搜索场景)

[7.3 代码实现](#7.3 代码实现)


1、二叉搜索树的概念及性能

1.1 概念

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

1)、若左子树不为空,则左子树 所有节点的值小于等于根节点

2)、若右子树不为空,则右子树 所有节点的值大于等于根节点

3)、它的左右子树也是二叉搜索树

4)、二叉搜索树可以支持插入相等的值,也可以不支持,具体看场景定义。

1.2 性能分析

1)、理想情况下,二叉搜索树类似于完全二叉树,高度是:log2 N

2)、最差情况下,二叉搜索树退化为单叉树,高度是:N

因此,二叉搜索树查找数据的时间复杂度是:O(N)

2、二叉搜索树的实现

在实现二叉搜索树诸多功能之前,我们先来写一下基本框架:

cpp 复制代码
template<class K>
struct BSTNode
{
	K _key;
	BSTNode<K>* _left;
	BSTNode<K>* _right;
	BSTNode(const K& key)
		:_key(key)
		, _left(nullptr)
		, _right(nullptr)
	{ }
};

首先需要实现的是每一个树节点的定义,和普通的二叉树没有区别。

cpp 复制代码
// Binary Search Tree
// Key 关键字
template<class K>
class BSTree
{
	//typedef BSTNode<K> node;
	using Node = BSTNode<K>;
public:
    
private:
	Node* _root = nullptr;
};

如图,C++11支持用using来重命名,具体作用和 typedef 类似。

2.1 插入

插入的具体过程如下:

1)、树为空,直接 new 一个节点

2)、不为空,根据搜索二叉树的定义,比根节点大的插右边,小的插左边

cpp 复制代码
	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->_right;
			else if (cur->_key > key)
				cur = cur->_left;
			else
				return false;
		}

		cur = new Node(key);
		if (parent->_key < key)
			parent->_right = cur;
		else
			parent->_left = cur;

		return true;
	}

如图,用两个指针,cur 指向插入的位置,parent指向父亲节点。

2.2 中序遍历
cpp 复制代码
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);
	}

中序遍历逻辑和之前一样,需要注意的是:C++中private修饰的成员无法在类外进行调用,但是我们可以通过递归实现功能。相当于多封装一层,这样就不用显示传根节点指针

当然也可以通过实现一个 GetRoot() 函数来完成该功能。

2.3 查找

查找的逻辑:

1)、从根节点开始查找val,val 比节点的值大往右边找,比节点的值小往左边找

2)、最多查找高度次,走到空没找到,则不存在val

3)、如果允许相等的值存在,则查找的是中序的第一个val

cpp 复制代码
	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.4 删除

删除的逻辑:

首先要查找元素是否在二叉搜索树中 ,如果不在,直接返回false。如果在,则需要分为以下几种情况:

1)、要删除的节点N左右孩子为空(叶子节点 ):把该节点的父亲对应的孩子指针置空,并删除该节点N

2)、要删除的节点N左孩子空,右孩子不为空:该节点的父亲节点对应孩子指针指向该节点N的右孩子,并删除该节点

3)、要删除的节点N右孩子空,左孩子不为空:该节点的父亲节点对应孩子指针指向该节点N的左孩子,并删除该节点

4)、要删除的节点N左右孩子均不为空:我们这里用替代法----找到N的左子树最大值(左子树最右节点)或者右子树最小值(右子树最左节点)val,找到的最小(最大)节点记为R,把N节点的值key替换为val,然后删除R(满足二叉搜索树的规则)

cpp 复制代码
bool Erase(const K& key)
{
	Node* parent = nullptr;
	Node* cur = _root;

	while (cur)
	{
		parent = cur;
		if (cur->_key < key)
			cur = cur->_right;
		else if (cur->_key > key)
			cur = cur->_left;
		else
		{
			// 删除
			// 左为空
			if (cur->_left == nullptr)
			{
				if (cur == _root)
					_root = _root->_right;
				else if (parent->_left == cur)
					parent->_left = cur->_right;
				else
					parent->_right = cur->_right;
				delete cur;
			}

			// 右为空
			else if (cur->_right == nullptr)
			{
				if (cur == _root)
					_root = _root->_left;
				else if (parent->_left == cur)
					parent->_left = cur->_left;
				else
					parent->_right = cur->_left;
				delete cur;
			}

			// 左右都不为空
			else
			{
				// 找右子树最小节点
				Node* replace = cur->_right;
				Node* replaceParent = cur; // 要删除的节点是根节点
				while (replace->_left)
				{
					replaceParent = replace;
					replace = replace->_left;
				}

				cur->_key = replace->_key;

				if (replaceParent->_left == replace)
					replaceParent->_left = replace->_right;
				else
					replaceParent->_right = replace->_right;

				delete replace;
			}
			
			return true;
		}
	}
	return false;
}

三、二叉搜索树key和key/value使用场景

7.1 key搜索场景

只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断 key在不在。key的搜索场景实现的⼆叉树搜索树⽀持增删查,但是不⽀持修改,修改key破坏搜索树结构了。

7.2 key/value搜索场景

每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存 储key还要存储对应的value,增/删/查还是以key为关键字⾛⼆叉搜索树的规则进⾏⽐较,可以快速查找到key对应的value。key/value的搜索场景实现的⼆叉树搜索树⽀持修改,但是不⽀持修改key,修改key破坏搜索树性质了,可以修改value。

场景:简单中英互译字典,树的结构中(结点)存储key(英⽂)和vlaue(中⽂),搜索时输⼊英⽂,则同时 查找到了英⽂对应的中⽂。

7.3 代码实现
cpp 复制代码
namespace keyValue
{
	template<class K, class V>
	struct BSTNode
	{
		// pair<K, V> _kv;
		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() = default;
		BSTree(const BSTree<K, V>& t)
		{
			_root = Copy(t._root);
		}
		BSTree<K, V>& operator=(BSTree<K, V> t)
		{
			swap(_root, t._root);
			return *this;
		}
		~BSTree()
		{
			Destroy(_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 = 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)
		{
			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
				{
					if (cur->_left == nullptr)
					{
						if (parent == nullptr)
						{
							_root = cur->_right;
						}
						else
						{
							if (parent->_left == cur)
								parent->_left = cur->_right;
							else
								parent->_right = cur->_right;
						}
						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						if (parent == nullptr)
						{
							_root = cur->_left;
						}
						else
						{
							if (parent->_left == cur)
								parent->_left = cur->_left;
							else
								parent->_right = cur->_left;
						}
						delete cur;
						return true;
					}
					else
					{
						Node* rightMinP = cur;
						Node* rightMin = cur->_right;
						while (rightMin->_left)
						{
							rightMinP = rightMin;
							rightMin = rightMin->_left;
						}
						cur->_key = rightMin->_key;
						if (rightMinP->_left == rightMin)
							rightMinP->_left = rightMin->_right;
						else
							rightMinP->_right = rightMin->_right;
						delete rightMin;
						return true;
					}
				}
			}
			return false;
		}
		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);
	}
	void Destroy(Node* root)
	{
		if (root == nullptr)
			return;
		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
	}
	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* newRoot = new Node(root->_key, root->_value);
		newRoot->_left = Copy(root->_left);
		newRoot->_right = Copy(root->_right);
		return newRoot;
	}
private:
	Node* _root = nullptr;
};
}

int main()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
	keyValue::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 == NULL)
		{
			countTree.Insert(str, 1);
		}
		else
		{
			ret->_value++;
		}
	}
	countTree.InOrder();
	return 0;
}

本期的分享就到这里,如果觉得博主的文章比较对胃口的话,可以点一个小小的关注~

您的三连是我持续更新的动力~

相关推荐
Rust研习社1 小时前
从 LaunchBadge 到 transact-rs:SQLx 社区迈出可持续治理的第一步
开发语言·后端·rust
8Qi81 小时前
LeetCode 124. 二叉树中的最大路径和(Hard)
算法·leetcode·二叉树·递归
Huangjin007_1 小时前
【C++ STL篇(十四)】哈希表实现:开放定址法与链地址法
c++·哈希算法·散列表
不会就选b1 小时前
数据结构之单链表
数据结构
And_Ii1 小时前
LeetCode 1. 两数之和 python
数据结构·算法·leetcode
承渊政道1 小时前
【MySQL数据库学习】MySQL表的约束(上)
数据库·c++·学习·mysql·bash·数据库架构·数据库系统
minji...1 小时前
Linux高级IO(六)基于ET模式、单reactor反应堆的epoll版本的TCP计算服务器
linux·服务器·网络·c++·epoll·socket套接字·reactor反应堆模式
程序大视界1 小时前
【C++ 从基础到项目实战】C++(九):友元与设计模式初探——打破封装的艺术
开发语言·c++·cpp
hhb_6181 小时前
Bash变量不加引号:空格文件名致命陷阱
开发语言·chrome·bash