C++进阶-二叉树进阶(二叉搜索树)

news2024/9/22 4:09:52

1. 二叉搜索树

1.1 二叉搜索树概念

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

  • 1.若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 2.若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 3.它的左右子树也分别为二叉搜索树
    在这里插入图片描述

1.2 二叉搜索树操作

在这里插入图片描述

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};
  1. 二叉搜索树的查找
    a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
    b、最多查找高度次,走到到空,还没找到,这个值不存在。
  2. 二叉搜索树的插入
    插入的具体过程如下:
    a. 树为空,则直接新增节点,赋值给root指针
    b. 树不空,按二叉搜索树性质查找插入位置,插入新节点
    在这里插入图片描述
// 插入节点
// 返回值是布尔型,来判断是否插入成功
// 满足如果key和节点数据相比,小于走左子树,大于走右子树,等于则不插入,返回false
// 而最后结束的时插入到叶子节点
bool Insert(const K& key)
{
	//判断空树时的情况,直接开辟根节点
	if (_root == nullptr)
	{
		// 开辟对象节点空间
		_root = new Node(key);
		return true;
	}

	// 寻找节点位置,从头结点位置开始寻找
	Node* cur = _root;

	// 记录cur的父亲节点
	Node* parent = nullptr;

	// 从头结点开始寻找插入的适当位置
	// 搜索二叉树的原则是满足如果key和节点数据相比
	// 小于走左子树,大于走右子树,等于则不插入,返回false
	// 结束条件找到叶子节点的左子树或者右子树(nullptr)
	while (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;
}
  1. 二叉搜索树的删除
    首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面3种情况
  • 一.删除叶子节点(要删除的节点无孩子节点)
    在这里插入图片描述
  • 二.删除左子树或者右子树为空的节点(要删除的结点只有左孩子结点或者只有右孩子节点)
    在这里插入图片描述
  • 三.删除的节点左右子树都不为空(要删除的节点有左、右孩子节点)
    在这里插入图片描述

首先找到要删除的元素
在这里插入图片描述

//每次保留父亲节点,找到并且记录叶子节点
if (key < cur->_key)
{
				parent = cur;
				cur = cur->left;
}
else if (key > cur->_key)
{
				parent = cur;
				cur = cur->right;
}
else//相等的时候,找到了要删除的位置
{
	...
}

之后,在else的情况中
实质上在删除的时候的情况

  1. 一情况的处理可以与二情况合在一起:
  • cur的左子树为空,如果cur在parent左子树,将cur的右子树给parent的左子树,否则cur在parent的右子树,则将cur的右子树付parent的右子树。
  • cur的右子树为空,如果cur在parent得到左子树,将cur的左子树付给parent的左子树,否则cur在parent的右子树,则将cur的左子树赋给parent的右子树。

else中也要分为要删除节点的左孩子为空或右孩子为空的情况:

  • a.cur的左孩子为空
    (1).其中,也要判断是否是头节点,另外判断
    (2).cur不是头节点的情况
    之后判断cur是parent的哪个孩子
    直接将cur的右孩子变为头节点,相当于删除10
    在这里插入图片描述

根据上面的描述,代码如下

			// 左孩子为空
			if (cur->left == nullptr)
			{
				// 内部也分为两种情况:
				// 1.是头节点
				if (cur == _root)
				{
					// 直接将cur的右孩子当作头节点
					_root = cur->right;
				}
				else
				{
					// 判断cur是parent的哪个孩子
					//cur是parent左孩子
					if (cur == parent->left)
					{
						//cur的右子树赋给parent的左子树
						parent->left = cur->right;
					}
					else// cur是parent右孩子时
					{
						//cur的右子树赋给parent的右子树
						parent->right = cur->right;
					}
				}

				// 删除节点,释放空间
				delete cur;

				}
  • b.cur的左孩子为空的情况与a情况类似
    (1).其中,也要判断是否是头节点,另外判断
    (2).cur不是头节点的情况
    之后判断cur是parent的哪个孩子
// 内部也分为两种情况:
// 1.是头节点
if (cur == _root)
{
				// 直接将cur的左孩子当作头节点
				_root = cur->left;
}
else
{
				// 2.不是头节点
				// 判断cur是parent的哪个孩子
				//cur是parent左孩子
				if (cur == parent->left)
				{
					//cur的右子树赋给parent的左子树
					parent->left = cur->left;
				}
				else// cur是parent右孩子时
				{
					//cur的右子树赋给parent的右子树
					parent->right = cur->left;
				}
}

// 删除节点,释放空间
delete cur;
  • 2.三情况的解决方式:
    删除cur,找一个节点来替换,替换规则:cur的左子树的最大节点,右子树的最小节点,之后交换,直接删除,这种没有问题,在删除头节点会出现问题
    在这里插入图片描述

所以要更改为交换之后,再要判断rightMin也要分为两种情况,rightMin在rightMinParent左孩子还是右孩子。
在这里插入图片描述

				else//二.删除的节点左右子树都不为空
				{
					// 删除cur,找一个节点来替换
					// 替换规则:cur的左子树的最大节点,右子树的最小节点,之后交换

					// 这里用查找右子树的最左节点
					Node* rightMin = cur->right;

					Node* rightMinParent = cur;

					// 开始查找,结束条件左孩子为空,再去找自己,之后右子树
					while (rightMin->left)
					{
						rightMinParent = rightMin;
						rightMin = rightMin->left;
					}

					// 交换
					// 数值交换
					swap(cur->_key, rightMin->_key);


					// rightMin也要分为两种情况
					// 一种是rightMin在rightMinParent左孩子,也就是rightMin左孩子为空
					if (rightMinParent->left == rightMin)
						//将rightMin右孩子赋值给父亲节点的左子树
						rightMinParent->left = rightMin->right;
					else//另外一种是rightMin在rightMinParent右孩子
						rightMinParent->right = rightMin->right;

					delete rightMin;
				}

				return true;
}

在这里插入图片描述

完成的删除的代码如下:

// 删除:有着三种情况
// 三种情况:1.删除叶子节点   2.删除左子树或者右子树为空的节点  3.删除的节点左右子树都不为空
//一情况的处理可以与二情况合在一起:
//cur的左子树为空,如果cur在parent左子树,将cur的右子树给parent的左子树,否则cur在parent的右子树,则将cur的右子树付parent的右子树
//cur的右子树为空,如果cur在parent得到左子树,将cur的左子树付给parent的左子树,否则cur在parent的右子树,则将cur的左子树赋给parent的右子树
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->left == nullptr)
			{
				// 内部也分为两种情况:
				// 1.是头节点
				if (cur == _root)
				{
					// 直接将cur的右孩子当作头节点
					_root = cur->right;
				}
				else
				{
					// 判断cur是parent的哪个孩子
					//cur是parent左孩子
					if (cur == parent->left)
					{
						//cur的右子树赋给parent的左子树
						parent->left = cur->right;
					}
					else// cur是parent右孩子时
					{
						//cur的右子树赋给parent的右子树
						parent->right = cur->right;
					}
				}

				// 删除节点,释放空间
				delete cur;

			}
			else if (cur->right == nullptr)
			{
				// 内部也分为两种情况:
				// 1.是头节点
				if (cur == _root)
				{
					// 直接将cur的左孩子当作头节点
					_root = cur->left;
				}
				else
				{
					// 2.不是头节点
					// 判断cur是parent的哪个孩子
					//cur是parent左孩子
					if (cur == parent->left)
					{
						//cur的右子树赋给parent的左子树
						parent->left = cur->left;
					}
					else// cur是parent右孩子时
					{
						//cur的右子树赋给parent的右子树
						parent->right = cur->left;
					}
				}

				// 删除节点,释放空间
				delete cur;
			}
			else//二.删除的节点左右子树都不为空
			{
				// 删除cur,找一个节点来替换
				// 替换规则:cur的左子树的最大节点,右子树的最小节点,之后交换

				// 这里用查找右子树的最左节点
				Node* rightMin = cur->right;

				Node* rightMinParent = cur;

				// 开始查找,结束条件左孩子为空,再去找自己,之后右子树
				while (rightMin->left)
				{
					rightMinParent = rightMin;
					rightMin = rightMin->left;
				}

				// 交换
				// 数值交换
				swap(cur->_key, rightMin->_key);


				// rightMin也要分为两种情况
				// 一种是rightMin在rightMinParent左孩子,也就是rightMin左孩子为空
				if (rightMinParent->left == rightMin)
					//将rightMin右孩子赋值给父亲节点的左子树
					rightMinParent->left = rightMin->right;
				else//另外一种是rightMin在rightMinParent右孩子
					rightMinParent->right = rightMin->right;

				delete rightMin;
			}

			return true;
		}
	}

	return false;

}

find的查找代码:

// 查找
bool find(const K& key)
{
	// 判断为空树时
	if (_root == nullptr)
	{
		return false;
	}

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

输出:中序遍历:
这种写法,类外无法访问类内私有成员
在这里插入图片描述

更改代码如下:
可进行无参的访问:private中定义有参的,就可以调用私有成员的_root,在类内的public中重载方法InOrder(),在方法内调用有参的。

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

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

		InOrder(root->left);
		cout << root->_key << " ";
		InOrder(root->right);
	}
	Node* _root = nullptr;//对象指针

};

1.3 二叉搜索树的具体实现

1.3.1 K模型

#pragma once
#include<iostream>
using namespace std;
// K模型


namespace key
{
	// 二叉搜索树的实现形式与list类似
	// 先创建一个节点的类,类中有_key(节点的数据值)、*left(当前节点的左孩子地址)、*right(当前节点的右孩子地址)

	//节点类
	template <class K>
	struct BSTreeNode
	{

		K _key;
		BSTreeNode* left;
		BSTreeNode* right;


		//构造函数
		BSTreeNode(const K& key)
			:_key(key),
			left(nullptr),
			right(nullptr)
		{}
	};

	// 之后用创建的的节点类,来构造二叉搜索树,每一个节点都是一个节点指针
	// 二叉搜索树要保证,左孩子值小于父亲节点,右孩子节点大于父亲阶段,数据大小顺序(由小到大):左孩子,父亲,右孩子
	// 默认定义搜索树不允许冗余
	// 成员变量为节点指针
	template<class K>
	class BSTree
	{
	public:

		// 重命名一下
		typedef BSTreeNode<K> Node;

	public:

		// 构造函数
		BSTree() :_root(nullptr)
		{}

		// 插入节点
		// 返回值是布尔型,来判断是否插入成功
		// 满足如果key和节点数据相比,小于走左子树,大于走右子树,等于则不插入,返回false
		// 而最后结束的时插入到叶子节点
		bool Insert(const K& key)
		{
			//判断空树时的情况,直接开辟根节点
			if (_root == nullptr)
			{
				// 开辟对象节点空间
				_root = new Node(key);
				return true;
			}

			// 寻找节点位置,从头结点位置开始寻找
			Node* cur = _root;

			// 记录cur的父亲节点
			Node* parent = nullptr;

			// 从头结点开始寻找插入的适当位置
			// 搜索二叉树的原则是满足如果key和节点数据相比
			// 小于走左子树,大于走右子树,等于则不插入,返回false
			// 结束条件找到叶子节点的左子树或者右子树(nullptr)
			while (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;
		}


		// 删除:有着三种情况
		// 三种情况:1.删除叶子节点   2.删除左子树或者右子树为空的节点  3.删除的节点左右子树都不为空
		//一情况的处理可以与二情况合在一起:
		//cur的左子树为空,如果cur在parent左子树,将cur的右子树给parent的左子树,否则cur在parent的右子树,则将cur的右子树付parent的右子树
		//cur的右子树为空,如果cur在parent得到左子树,将cur的左子树付给parent的左子树,否则cur在parent的右子树,则将cur的左子树赋给parent的右子树
		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->left == nullptr)
					{
						// 内部也分为两种情况:
						// 1.是头节点
						if (cur == _root)
						{
							// 直接将cur的右孩子当作头节点
							_root = cur->right;
						}
						else
						{
							// 判断cur是parent的哪个孩子
							//cur是parent左孩子
							if (cur == parent->left)
							{
								//cur的右子树赋给parent的左子树
								parent->left = cur->right;
							}
							else// cur是parent右孩子时
							{
								//cur的右子树赋给parent的右子树
								parent->right = cur->right;
							}
						}

						// 删除节点,释放空间
						delete cur;

					}
					else if (cur->right == nullptr)
					{
						// 内部也分为两种情况:
						// 1.是头节点
						if (cur == _root)
						{
							// 直接将cur的左孩子当作头节点
							_root = cur->left;
						}
						else
						{
							// 2.不是头节点
							// 判断cur是parent的哪个孩子
							//cur是parent左孩子
							if (cur == parent->left)
							{
								//cur的右子树赋给parent的左子树
								parent->left = cur->left;
							}
							else// cur是parent右孩子时
							{
								//cur的右子树赋给parent的右子树
								parent->right = cur->left;
							}
						}

						// 删除节点,释放空间
						delete cur;
					}
					else//二.删除的节点左右子树都不为空
					{
						// 删除cur,找一个节点来替换
						// 替换规则:cur的左子树的最大节点,右子树的最小节点,之后交换

						// 这里用查找右子树的最左节点
						Node* rightMin = cur->right;

						Node* rightMinParent = cur;

						// 开始查找,结束条件左孩子为空,再去找自己,之后右子树
						while (rightMin->left)
						{
							rightMinParent = rightMin;
							rightMin = rightMin->left;
						}

						// 交换
						// 数值交换
						swap(cur->_key, rightMin->_key);


						// rightMin也要分为两种情况
						// 一种是rightMin在rightMinParent左孩子,也就是rightMin左孩子为空
						if (rightMinParent->left == rightMin)
							//将rightMin右孩子赋值给父亲节点的左子树
							rightMinParent->left = rightMin->right;
						else//另外一种是rightMin在rightMinParent右孩子
							rightMinParent->right = rightMin->right;

						delete rightMin;
					}

					return true;
				}
			}

			return false;

		}

		// 查找
		bool find(const K& key)
		{
			// 判断为空树时
			if (_root == nullptr)
			{
				return false;
			}

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

		// 中序输出(由小到大排序)

		//类外不能访问私有成员	  t1.InOrder(t1._root);
		/*void InOrder(Node *root)
		{
			 判断是否空树
			if (root == nullptr)
			{
				return;
			}

			InOrder(root->left);
			cout << root._key << " ";
			InOrder(root->right);
		}*/

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

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

			InOrder(root->left);
			cout << root->_key << " ";
			InOrder(root->right);
		}
		Node* _root = nullptr;//对象指针

	};

}



1.3.2 KV模型

#pragma once
#include<iostream>


//KV模型(key_value模型)

namespace key_value
{
	//节点类
	template <class K, class V>
	struct BSTreeNode
	{

		K _key;
		BSTreeNode<K, V>* left;
		BSTreeNode<K, V>* right;
		V _value;


		//构造函数
		BSTreeNode(const K& key, const V& value)
			:_key(key),
			left(nullptr),
			right(nullptr),
			_value(value)
		{}
	};

	// 之后用创建的的节点类,来构造二叉搜索树,每一个节点都是一个节点指针
	// 二叉搜索树要保证,左孩子值小于父亲节点,右孩子节点大于父亲阶段,数据大小顺序(由小到大):左孩子,父亲,右孩子
	// 默认定义搜索树不允许冗余
	// 成员变量为节点指针
	template<class K,class V>
	class BSTree
	{
	public:

		// 重命名一下
		typedef BSTreeNode<K,V> Node;

	public:

		// 构造函数
		BSTree() :_root(nullptr)
		{}

		// 插入节点
		// 返回值是布尔型,来判断是否插入成功
		// 满足如果key和节点数据相比,小于走左子树,大于走右子树,等于则不插入,返回false
		// 而最后结束的时插入到叶子节点
		bool Insert(const K& key, const V& value)
		{
			//判断空树时的情况,直接开辟根节点
			if (_root == nullptr)
			{
				// 开辟对象节点空间
				_root = new Node(key, value);
				return true;
			}

			// 寻找节点位置,从头结点位置开始寻找
			Node* cur = _root;

			// 记录cur的父亲节点
			Node* parent = nullptr;

			// 从头结点开始寻找插入的适当位置
			// 搜索二叉树的原则是满足如果key和节点数据相比
			// 小于走左子树,大于走右子树,等于则不插入,返回false
			// 结束条件找到叶子节点的左子树或者右子树(nullptr)
			while (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, value);

			if (key < parent->_key)
			{
				parent->left = cur;
			}
			else
			{
				parent->right = cur;
			}

			return true;
		}


		// 删除:有着三种情况
		// 三种情况:1.删除叶子节点   2.删除左子树或者右子树为空的节点  3.删除的节点左右子树都不为空
		//一情况的处理可以与二情况合在一起:
		//cur的左子树为空,如果cur在parent左子树,将cur的右子树给parent的左子树,否则cur在parent的右子树,则将cur的右子树付parent的右子树
		//cur的右子树为空,如果cur在parent得到左子树,将cur的左子树付给parent的左子树,否则cur在parent的右子树,则将cur的左子树赋给parent的右子树
		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->left == nullptr)
					{
						// 内部也分为两种情况:
						// 1.是头节点
						if (cur == _root)
						{
							// 直接将cur的右孩子当作头节点
							_root = cur->right;
						}
						else
						{
							// 判断cur是parent的哪个孩子
							//cur是parent左孩子
							if (cur == parent->left)
							{
								//cur的右子树赋给parent的左子树
								parent->left = cur->right;
							}
							else// cur是parent右孩子时
							{
								//cur的右子树赋给parent的右子树
								parent->right = cur->right;
							}
						}

						// 删除节点,释放空间
						delete cur;

					}
					else if (cur->right == nullptr)
					{
						// 内部也分为两种情况:
						// 1.是头节点
						if (cur == _root)
						{
							// 直接将cur的左孩子当作头节点
							_root = cur->left;
						}
						else
						{
							// 2.不是头节点
							// 判断cur是parent的哪个孩子
							//cur是parent左孩子
							if (cur == parent->left)
							{
								//cur的右子树赋给parent的左子树
								parent->left = cur->left;
							}
							else// cur是parent右孩子时
							{
								//cur的右子树赋给parent的右子树
								parent->right = cur->left;
							}
						}

						// 删除节点,释放空间
						delete cur;
					}
					else//二.删除的节点左右子树都不为空
					{
						// 删除cur,找一个节点来替换
						// 替换规则:cur的左子树的最大节点,右子树的最小节点,之后交换

						// 这里用查找右子树的最左节点
						Node* rightMin = cur->right;

						Node* rightMinParent = cur;

						// 开始查找,结束条件左孩子为空,再去找自己,之后右子树
						while (rightMin->left)
						{
							rightMinParent = rightMin;
							rightMin = rightMin->left;
						}

						// 交换
						// 数值交换
						swap(cur->_key, rightMin->_key);


						// rightMin也要分为两种情况
						// 一种是rightMin在rightMinParent左孩子,也就是rightMin左孩子为空
						if (rightMinParent->left == rightMin)
							//将rightMin右孩子赋值给父亲节点的左子树
							rightMinParent->left = rightMin->right;
						else//另外一种是rightMin在rightMinParent右孩子
							rightMinParent->right = rightMin->right;

						delete rightMin;
					}

					return true;
				}
			}

			return false;

		}

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

		// 中序输出(由小到大排序)

		// 类外不能访问私有成员	  t1.InOrder(t1._root);
		//void InOrder(Node *root)
		//{
		//	// 判断是否空树
		//	if (root == nullptr)
		//	{
		//		return;
		//	}

		//	InOrder(root->left);
		//	cout << root._key << " ";
		//	InOrder(root->right);
		//}

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

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

			InOrder(root->left);
			cout << root->_key << ":" << _root->_value;
			InOrder(root->right);
		}
		Node* _root = nullptr;//对象指针

	};





	void TestBSTree2()
	{
		BSTree<string, string> dict;
		dict.Insert("string", "字符串");
		dict.Insert("left", "左边");
		dict.Insert("insert", "插入");
		//...

		string str;
		while (cin >> str)
		{
			BSTreeNode<string, string>* ret = dict.find(str);
			if (ret)
			{
				cout << ret->_value << endl;
			}
			else
			{
				cout << "无此单词,请重新输入" << endl;
			}
		}
	}



	void TestBSTree3()
	{
		// 统计次数
		string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉","苹果","草莓", "苹果","草莓" };
		BSTree<string, int> countTree;
		for (const auto& str : arr)
		{
			auto ret = countTree.find(str);
			if (ret == nullptr)
			{
				countTree.Insert(str, 1);
			}
			else
			{
				ret->_value++;
			}
		}

		countTree.InOrder();
	}
}

1.4 二叉搜索树的应用

  1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
    比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
  2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。
    该种方式在现实生活中非常常见:
    比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
    再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

1.5 二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
在这里插入图片描述

  • 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
  • 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?那么我们后续章节学习的AVL树和红黑树就可以上场了。

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

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

相关文章

代码随想录(day1)二分法

if语句的基本语法 if 要判断的条件: 条件成立的时候&#xff0c;要做的事举例&#xff1a; if nums[middle]<target:leftmiddle1 while语句的基本语法&#xff1a; while 判断条件(condition)&#xff1a;执行语句(statements)举例&#xff1a; while left<right:midd…

【LLM】二、python调用本地的ollama部署的大模型

系列文章目录 往期文章&#xff1a; 【LLM】一、利用ollama本地部署大模型 目录 文章目录 前言 一、ollama库调用 二、langchain调用 三、requests调用 四、相关参数说明&#xff1a; 总结 前言 本地部署了大模型&#xff0c;下一步任务便是如何调用的问题&#xff0c…

前端vue打印后端对象为[object,object]

今天给自己项目进行编写前端页面时&#xff0c;惊讶的发现&#xff0c;自己进行打印后端传递的对象&#xff0c;一直显示未[object,object]&#xff0c;如下图所示&#xff1a; 感觉很奇怪&#xff0c;于是我猜测是不是自己获取的返回数据的问题&#xff0c;在进行添加了datat…

顶会FAST24最佳论文|阿里云块存储架构演进的得与失-4.EBS不同架构性能提升思路

3.1 平均延迟与长尾延迟 虚拟磁盘&#xff08;VD&#xff09;的延迟是由其底层架构决定的&#xff0c;具体而言&#xff0c;取决于请求所经历的路径。以EBS2为例&#xff0c;VD的延迟受制于两跳网络&#xff08;从BlockClient到BlockServer&#xff0c;再至ChunkServer&#x…

顾客排队购买蛋挞问题(算法与数据结构设计)

课题内容和要求 顾客排队买蛋挞问题。有N个顾客排队&#xff0c;每人最多买M个。烘焙员每次烘焙1到K个蛋挞放入盘中&#xff0c;顾客只能购买盘中的蛋挞&#xff0c;未达到M个需重新排队。输出每个顾客购买情况和完成顺序。 例如—— 输入&#xff1a;N9&#xff0c;K5&#x…

游戏软件缺少d3dx9_42.dll怎么修复?五种方法助你轻松解决

D3DX9_42.dll的丢失是一种常见的操作系统异常问题&#xff0c;由于日常使用电脑时的不当操作&#xff0c;可能会导致一些dll文件的丢失&#xff0c;D3DX9_42.dll就是其中之一。对于这种情况&#xff0c;我们可以通过谨慎的修复来解决。以下是一种科学的解决D3DX9_42.dll丢失的方…

【面试题】串联探针和旁挂探针有什么区别?

在网络安全领域中&#xff0c;串联探针和旁挂探针&#xff08;通常也被称为旁路探针&#xff09;是两种不同部署方式的监控设备&#xff0c;它们各自具有独特的特性和应用场景。以下是它们之间的主要区别&#xff1a; 部署方式 串联探针&#xff1a;串联探针一般通过网关或者…

刷题(day01)

1、leetcode485.最大连续1的个数 给定一个二进制数组 nums &#xff0c; 计算其中最大连续 1 的个数。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,0,1,1,1] 输出&#xff1a;3 解释&#xff1a;开头的两位和最后的三位都是连续 1 &#xff0c;所以最大连续 1 的个数是 3.…

Linux环境部署Python Web服务

“姑娘&#xff0c;再见面就要靠运气了&#xff0c;可别装作不认识&#xff0c;那句“好久不见”可干万别打颤…” 将使用 Python 编写的后端 API 部署到 Linux 环境中&#xff0c;可以按照以下详细步骤操作。本文将涵盖环境准备、API 编写、使用 Gunicorn 作为 WSGI 服务器、配…

C#反射基本应用

1、反射 反射是.NET Framework的一个特性&#xff0c;它允许在运行时获取类型的信息以及动态创建对象&#xff0c;调用方法&#xff0c;以及访问字段和属性。 2、代码 using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Sy…

运维锅总详解系统设计原则

本文对CAP、BASE、ACID、SOLID 原则、12-Factor 应用方法论等12种系统设计原则进行分析举例&#xff0c;希望对您在进行系统设计、理解系统运行背后遵循的原理有所帮助&#xff01; 一、CAP、BASE、ACID简介 以下是 ACID、CAP 和 BASE 系统设计原则的详细说明及其应用举例&am…

【小鸡案例】表单focus和blur事件用法

input中有2个属性&#xff0c;一个是focus获取焦点&#xff0c;一个是blur失去焦点。获取焦点就是我们点击输入框时输入框被选中&#xff1b;失去焦点即点击输入框以外的区域&#xff0c;今天就用这两种属性做一个点击输入框的动画效果。 先写个输入框&#xff0c;代码如下&am…

如何使用 pytorch 创建一个神经网络

我已发布在&#xff1a;如何使用 pytorch 创建一个神经网络 SapientialM.Github.io 构建神经网络 1 导入所需包 import os import torch from torch import nn from torch.utils.data import DataLoader from torchvision import datasets, transforms2 检查GPU是否可用 dev…

Nacos 进阶篇---集群:选举心跳健康检查劳动者(九)

一、引言 本章将是我们第二阶段&#xff0c;开始学习集群模式下&#xff0c;Nacos 是怎么去操作的 &#xff1f; 本章重点&#xff1a; 在Nacos服务端当中&#xff0c;会去开启健康心跳检查定时任务。如果是在Nacos集群下&#xff0c;大家思考一下&#xff0c;有没有必要所有的…

[FreeRTOS 基础知识] 任务通知 概念

文章目录 任务通知 定义FreeRTOS 任务通知机制 任务通知 定义 实时操作系统&#xff08;RTOS&#xff09;的任务通知机制是一种用于任务间通信和同步的机制。在FreeRTOS中&#xff0c;任务通知允许一个任务向另一个任务发送通知&#xff0c;表明某个事件已经发生或者某些条件已…

鸿蒙语言基础类库:【@ohos.url (URL字符串解析)】

URL字符串解析 说明&#xff1a; 本模块首批接口从API version 7开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 导入…

【9-2:RPC设计】

RPC 1. 基础1.1 定义&特点1.2 具体实现框架1.3 应用场景2. RPC的关键技术点&一次调用rpc流程2.1 RPC流程流程两个网络模块如何连接的呢?其它特性RPC优势2.2 序列化技术序列化方式PRC如何选择序列化框架考虑因素2.3 应用层的通信协议-http什么是IO操作系统的IO模型有哪…

【Excel技巧大揭秘】如何轻松绕过Excel工作表保护密码?

在日常工作中&#xff0c;我们时常会遇到设置了工作表保护的Excel文件&#xff0c;本意是为了数据安全&#xff0c;但偶尔在急需编辑文件时却遗忘了密码&#xff0c;这无疑让人感到头疼。面对这样的困境&#xff0c;别担心&#xff0c;本文将为您揭秘两种高效解决策略&#xff…

go语言day12 包 init() time包 file包

包 包中的 结构体 及结构体属性 方法 都可以通过设置首字母大小写来实现权限访问&#xff0c;首字母大写任何包中都可以访问&#xff0c;首字母小写只在同包中可以访问。 再导入包go文件时&#xff0c;可以给.go文件取别名。 在导入的包名前加入 _ 意思是调用该包的初始…

OpenHarmony 入门——单元测试UnitTest快速入门

引言 OpenHarmony 的单元测试&#xff08;UnitTest&#xff09;是一个关键的软件开发过程&#xff0c;它确保代码的各个部分能够按预期工作&#xff0c;OpenHarmony的测试框架中提供了很多种的单元测试&#xff0c;今天简单介绍下UnitTest 类型的TDD测试。 OpenHarmony 的TDD …