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

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树和红黑树就可以上场了。

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

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

相关文章

Jenkins教程-15-常用插件-Blue Ocean

上一小节我们学习了Jenkins定时任务构建的方法&#xff0c;本小节我们讲解一下Jenkins常用插件Blue Ocean的使用方法。 Blue Ocean 提供了一套可视化操作界面来帮助创建、编辑 Pipeline 任务。 Blue Ocean 特性&#xff1a; 流水线编辑器&#xff1a;用于创建贯穿始终的持续交…

一、redis-万字长文读懂redis

高性能分布式缓存Redis `第一篇章`1.1缓存发展史&缓存分类1.1.1 大型网站中缓存的使用带来的问题1.1.2 常见缓存的分类及对比与memcache对比1.2 数据类型选择&应用场景1.2.1 string1.2.2 hash1.2.3 链表1.2.4 set1.2.5 sortedset有序集合类型1.2.6 总结1.3 Redis高级应…

mysql在linux系统下重置root密码

mysql在linux系统下重置root密码 登录服务器时候mysql密码忘记了&#xff0c;没办法只能重置&#xff0c;找了一圈&#xff0c;把行之有效的方法介绍在这里。 错误展示&#xff1a; 我还以为yes就可以了呢&#xff0c;这是不行的意思。 关掉mysql服务 sudo systemctl stop …

百度、谷歌、必应收录个人博客网站

主要是给各个搜索引擎提交你的sitemap文件&#xff0c;让别人能搜到你博客的内容。 主题使用的Butterfly。 生成sitemap 安装自动生成sitemap插件。 npm install hexo-generator-sitemap --save npm install hexo-generator-baidu-sitemap --save在站点配置文件_config.yml…

Redhat 安装 docker 网络连接超时问题

目录 添加阿里云的Docker CE仓库 更新YUM缓存 安装 Docker Engine 启动并设置Docker自启动 验证 Docker 安装 [userlocalhost ~]$ sudo yum-config-manager --add-repohttps://download.docker.com/linux/centos/docker-ce.repo 正在更新 Subscription Management 软件仓库…

PHP中的运算符与表达式:深入解析与实战应用

目录 一、基础概念 1.1 运算符的定义 1.2 表达式的定义 二、PHP运算符的分类 2.1 算术运算符 2.2 赋值运算符 2.3 比较运算符 2.4 逻辑运算符 2.5 位运算符 2.6 字符串运算符 2.7 错误控制运算符 三、表达式的优先级与结合性 3.1 优先级 3.2 结合性 四、实战应…

挑战全网最清晰解决文本文件乱码方案

标题 文本文件出现乱码之全网最清晰解决方案乱码出现的原因解决方案第一步&#xff1a;获取文件的原始编码格式。第二步&#xff0c;获取当前系统的格式第三步&#xff0c;将文件的内容以当前系统编码格式进行译码并且输出到新的文件中第四步&#xff0c;删除原文件&#xff0c…

【SOLID原则前端中的应用】接口隔离原则(Interface Segregation Principle,ISP)- vue3示例

接口隔离原则&#xff08;Interface Segregation Principle&#xff0c;ISP&#xff09;在Vue 3中的应用 接口隔离原则&#xff08;Interface Segregation Principle&#xff0c;ISP&#xff09;规定&#xff0c;客户端不应该被迫依赖于它不使用的方法。 换句话说&#xff0c;…

【Python_GUI】tkinter常用组件——文本类组件

文本时窗口中必不可少的一部分&#xff0c;tkinter模块中&#xff0c;有3种常用的文本类组件&#xff0c;通过这3种组件&#xff0c;可以在窗口中显示以及输入单行文本、多行文本、图片等。 Label标签组件 Label组件的基本使用 Label组件是窗口中比较常用的组件&#xff0c;…

JavaEE初阶-网络原理1

文章目录 前言一、UDP报头二、UDP校验和2.1 CRC2.2 md5 前言 学习一个网络协议&#xff0c;最主要就是学习的报文格式&#xff0c;对于UDP来说&#xff0c;应用层数据到达UDP之后&#xff0c;会给应用层数据报前面加上UDP报头。 UDP数据报UDP包头载荷 一、UDP报头 如上图UDP的…

使用ifconfig命令获取当前服务器的内网IP地址

如何使用ifconfig命令获取当前服务器的内网IP地址呢&#xff1f; ifconfig eth0 | grep inet | awk {print $2}

redis运维:sentinel模式如何查看所有从节点

1. 连接到sentinel redis-cli -h sentinel_host -p sentinel_port如&#xff1a; redis-cli -h {域名} -p 200182. 发现Redis主服务器 连接到哨兵后&#xff0c;我们可以使用SENTINEL get-master-addr-by-name命令来获取当前的Redis主服务器的地址。 SENTINEL get-master-a…

最小爬楼梯(dp)

import java.util.Scanner;public class ClimbingStairsCost {public static int minCostClimbingStairs(int[] cost) {int n cost.length; // 获取输入的 cost 数组的长度int[] dp new int[n 1]; // 创建一个用于存储每个台阶最小花费的 dp 数组dp[0] 0; dp[1] 0; // 初始…

解析java128陷阱

一、提要 在java开发时&#xff0c;由于基本类型不能调用方法&#xff0c;在某些方面很不方便&#xff0c;因此产生了包装类。我们把基本类型和对应的包装类的转换叫装箱、拆箱。 1.装箱 基本类型转成包装类对象 关键字valueOf->装箱,可以指定进制&#xff1a; Integer…

C# modbus验证

窗体 还有添加的serialPort控件串口通信 设置程序配置 namespace CRC {public static class CRC16{/// <summary>/// CRC校验&#xff0c;参数data为byte数组/// </summary>/// <param name"data">校验数据&#xff0c;字节数组</param>///…

NLP 面试八股:“Transformers / LLM 的词表应该选多大?“ 学姐这么告诉我答案

NLP 面试八股&#xff1a;“Transformers / LLM 的词表应该选多大?" 学姐这么告诉我答案 原创 看图学 看图学 2024年07月03日 07:55 北京 题目&#xff1a; Transformers/大模型的 token vocabulary 应该选多大&#xff1f; 答案 先说一下结论&#xff1a; 数据量够大…

docker 重要且常用命令大全

本文将总结一些常见的重要的docker命令&#xff0c;以作备忘。后续如果有新的比较常用重要的也会更新进来。欢迎补充。 docker服务管理 首先我们要解释一下&#xff1a;systemctl和docker命令的不同 systemctl&#xff1a;是许多 Linux 发行版中默认的初始化系统和服务管理器。…

提高LabVIEW软件通用性的方法

提高LabVIEW软件通用性的方法 在使用LabVIEW开发软件时&#xff0c;提高软件的通用性非常重要。通用性意味着软件可以在不同的应用场景中使用&#xff0c;具备高度的适应性和灵活性&#xff0c;从而提高软件的价值和用户满意度。以下从多个角度详细探讨如何提高LabVIEW软件的通…

媒体查询:根据设备特征动态调整样式和布局

不知道你会不会有一个疑问&#xff0c;同一个网站&#xff0c;用手机看和用电脑看在首选项和排版会发生一些变化&#xff0c;但我们使用起来仍然非常顺手&#xff0c;这就是响应式设计。响应式设计原理是指网页可以根据不同使用设备的屏幕尺寸&#xff0c;做出相应的调整和变化…

Linux走进网络

走进网络之网络解析 目录 走进网络之网络解析 一、认识计算机 1.计算机的发展 2.传输介质 3.客户端与服务器端的概念 交换机 路由器 二、计算机通信与协议 1. 协议的标准化 2. 数据包的传输过程 OSI 协议 ARP协议 3. TCP/IP:四层模型 4. TCP三次握手和四次挥手…