【C++】二叉搜索树

二叉搜索树

  • 1.二叉搜索树概念
  • 2.二叉搜索的实现
    • 2.1结点
    • 2.1基本框架
    • 2.2插入
    • 2.3查找
    • 2.4删除
    • 2.5打印
  • 3.二叉搜索树递归实现
    • 3.1查找
    • 3.2插入
    • 3.3删除
  • 4.二叉搜索树默认成员函数
    • 4.1构造
    • 4.2析构
    • 4.3拷贝构造
    • 4.4赋值重载
  • 6.二叉搜索树的应用
    • 6.1K模型
    • 6.2KV模型
  • 7.二叉搜索树的性能分析

在这里插入图片描述

喜欢的点赞,收藏,关注一下把!在这里插入图片描述

1.二叉搜索树概念

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

1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
3. 它的左右子树也分别为二叉搜索树

注意二查搜索树是一个单值树。 如下图:
在这里插入图片描述

2.二叉搜索的实现

2.1结点

template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

	BSTreeNode(const K& key)
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{}
};

2.1基本框架

template<class T>
class BSTree
{
	typedef BSTreeNode<K> Node;

public:
	//构造等。。

	bool Insert(const K& key)
	{}

	bool Erase(const K& key)
	{}

	bool Find(const K& key)
	{}

private:
	//先不写构造
	Node* _root=nullptr;
};

二叉搜索树,没有提供改的成员函数。更准确来说二叉搜索树K模型不能去任意修改key值,可能会破坏二叉搜索树的结构。KV模型可以修改值这个我们下面说。

2.2插入

二叉搜索树的插入不像是普通二叉树插入那样,插入结点之后还需要保持二叉搜索树的形状。因此我们需要先找到要插入结点的位置,然后再插入。

bool Insert(const K& key)
{
	//树为空
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
	
	//寻址
	Node* cur = _root;
	while (cur)
	{
		if (_root->_key < key)
			cur = cur->_right;
		else if (_root->_key > key)
			cur = cur->_left;
		else //相等
			return false;
	}
	//插入
	cur = new Node(key);
	return true;
}

我这样插入,有没有什么问题?
在这里插入图片描述
我们需要一个parent的指针,指向它的父亲结点,然后把9插入到父亲结点指针里,这样才能真正插入到树里。
在这里插入图片描述

但是这样写需要注意的是需要知道是插入父亲的左指针,还是右指针。

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;

}

2.3查找

bool Find(const K& key)
	{
		if (_root == nullptr)
			return false;

		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删除

删除是二叉搜索树中最难的,情况很多,都需要考虑到。
在这里插入图片描述

  1. 删7(叶子结点),可以直接删除,但是直接删除7,父亲结点右指针就野指针了。因此我们需要把父亲结点指针置为空。指向7左孩子还是右孩子都可以。
    在这里插入图片描述

  2. 删14(只有一个左孩子,右为空),需要找到14的父亲结点,然后让父亲结点右指针指向14的左孩子结点。
    在这里插入图片描述

  3. 那还有一种情况肯定是只有一个右孩子,左为空,让父亲指针指向这个右孩子。

其实上面三种情况,可以总结成两种情况。

1. 左孩子为空
2. 右孩子为空

叶子结点,让父亲指向左孩子右孩子都可以。

左为空,父亲指针指向右孩子,右为空,父亲指针指向左孩子。

bool Erase(const K& key)
	{
		if (_root == nullptr)
			return false;

		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
			{
				//1.左为空
				if (cur->_left == nullptr)
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else if (parent->_right == cur)
					{
						parent->_right = cur->_right;
					}
					delete cur;
				}
				//2.右为空
				else if (cur->_right == nullptr)
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else if (parent->_right == cur)
					{
						parent->_right = cur->_left;
					}
					delete cur;
				}
				//3.左右都不为空
				else
				{

				}
				return true;
			}
			
		}
		return false;	
	}

先看前左为空,和右为空两种情况,请问有没有上面bug?
如果要被删除的是根节点呢?当前代码行不行?
在这里插入图片描述
是不是要报野指针的错误,根本没有parent指针。
那该如何解决呢?
是不是让根结点往下走一步就行了。
在这里插入图片描述

	bool Erase(const K& key)
	{
		if (_root == nullptr)
			return false;

		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
			{
				//1.左为空
				if (cur->_left == nullptr)
				{
					//被删结点是根结点
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_right;
						}
						else if (parent->_right == cur)
						{
							parent->_right = cur->_right;
						}
					}
					
					delete cur;
				}
				//2.右为空
				else if (cur->_right == nullptr)
				{
					//被删结点是根结点
					if (cur == _root)
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;
						}
						else if (parent->_right == cur)
						{
							parent->_right = cur->_left;
						}
					}				
					delete cur;
				}
				//3.左右都不为空
				else
				{

				}
				return true;
			}
			
		}
		return false;	
	}

3.左右都不为空
在这里插入图片描述
删3,左右都不为空,具体做法如下:
方法1:找左子树最大结点替换删除
在这里插入图片描述
将左边最大孩子1,赋值给3,把删3转换成删1。
方法2:找右子树最小结点替换删除
在这里插入图片描述
左边最大----->左孩子最右结点
右边左小----->右孩子最左结点

//3.左右都不为空
else
{
	//1.右孩子最小
	Node* Minright = cur->_right;
	Node* parent = nullptr;
	while (Minright->_left)
	{
		parent = Minright;
		Minright = Minright->_left;
	}
	parent->_left = Minright->_right;
	delete Minright;

}

上面代码有没有上面问题?
在这里插入图片描述
对于删除3没有问题。那删除8呢?
在这里插入图片描述
10就是右边最小结点,不需要再往下找了。
在这里插入图片描述
循环进不去,parent又会带来野指针得问题。该怎么解决?

所以parent初始值不能置为空,设置为cur最好,并且parent的指针也不能那样随意指向,需要判断,Minright是parent的左孩子还是右孩子。

//3.左右都不为空
else
{
	//1.右孩子最小
	Node* Minright = cur->_right;
	Node* parent = cur;
	while (Minright->_left)
	{
		parent = Minright;
		Minright = Minright->_left;
	}
	if (parent->_left == Minright)
	{
		parent->_left = Minright->_right;
	}
	else
	{
		parent->_right == Minright->_right;
	}
	delete Minright;
}

完整代码

bool Erase(const K& key)
{
	if (_root == nullptr)
		return false;

	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
		{
			//1.左为空
			if (cur->_left == nullptr)
			{
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else if (parent->_right == cur)
					{
						parent->_right = cur->_right;
					}
				}
				
				delete cur;
			}
			//2.右为空
			else if (cur->_right == nullptr)
			{
				if (cur == _root)
				{
					_root = cur->_left;
				}
				else
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else if (parent->_right == cur)
					{
						parent->_right = cur->_left;
					}
				}				
				delete cur;
			}
			//3.左右都不为空
			else
			{
				//1.右孩子最小
				Node* Minright = cur->_right;
				Node* parent = cur;
				while (Minright->_left)
				{
					parent = Minright;
					Minright = Minright->_left;
				}
				cur->_key=Minright->_key;
				if (parent->_left == Minright)
				{
					parent->_left = Minright->_right;
				}
				else
				{
					parent->_right = Minright->_right;
				}
				delete Minright;

			}
			return true;
		}
		
	}
	return false;	
}

如果想替换成左孩子最大,代码完全类似。

//3.左右都不为空
else
{
	//2.左孩子最大
	Node* Maxleft = cur->_left;
	Node* parent = cur;
	while (Maxleft->_right)
	{
		parent = Maxleft;
		Maxleft = Maxleft->_right;
	}
	cur->_key = Maxleft->_key;
	if (parent->_left == Maxleft)
	{
		parent->_left = Maxleft->_left;
	}
	else
	{
		parent->_right = Maxleft->_left;
	}
	delete Maxleft;
}

2.5打印

二叉搜索树中序是一个递增序列。
中序遍历代码很简单。但是注意我们这是在类里面的中序遍历。

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

		InOrder(root->_left);
		cout << root->_key < " ";
		InOrder(root->_right);
	}

在这里插入图片描述

中序需要把_root传就去,但是_root是一个私有成员变量不能访问。
在类里面写递归函数都会遇到这样的问题。
1.写一个Getroot的函数
2.套一层,写个子函数

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

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

		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);

3.二叉搜索树递归实现

递归代码量少,但是理解起来比较困难。一般建议有循环先写循环,然后再考虑写递归。

对于二叉树的递归,我们要有分治的思想,先处理根结点这棵树,然后再左子树右子树,因为左子树和右子树的处理方法和根结点这个树是一样的。

其实二叉搜索树的递归都是一个思想,空要怎么处理,比根大递归往右,比根小递归往左。

3.1查找

	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}

private:
	bool _FindR(Node* root, const K& key)
	{
		if (root == nullptr)
			return false;

		if (root->_key < key)
			return _FindR(root->_right, key);
		else if (root->_key > key)
			return _Findr(root->_left, key);
		else
			return true;
	}

3.2插入

如果为空就申请结点插入,比根大往右,比根小往左,和根相等插入失败。

	bool _InsertR(Node* root, const K& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}

		if (root->_key < key)
			return _InsertR(root->_right, key);
		else if (root->_key > key)
			return _InsertR(root->_left,key);
		else
			return false;
	}

这个代码还不完整,有问题。
假如插入为空的时候,插入8,再插入3。
在这里插入图片描述

插入3的时候,根本没有插上。
这里只修改一处地方就成了。

//给指针加个别名
bool _InsertR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}

		if (root->_key < key)
			return _InsertR(root->_right, key);
		else if (root->_key > key)
			return _InsertR(root->_left,_key);
		else
			return false;
	}

在这里插入图片描述

root虽然指向你,但是同时又是父亲的左指针或右指针的别名

在这里插入图片描述

3.3删除

bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr)
			return false;

		if (root->_key < key)
		{
			return _EraseR(root->_right, key);
		}
		else if (root->_key > key)
		{
			return _Erase(root->_left, key);
		}
		else
		{
			//1.左为空
			//2.右为空
			//3.左右都为空
			if (root->_left == nullptr)
			{

			}
			else if (root->_right == nullptr)
			{

			}
			else
			{

			}
			return true;
		}
	}

在这里插入图片描述

这里还需要再去申请一个parent吗?还需要知道7是父亲的左孩子还是右孩子吗?

显然是不用的,刚才说过,root虽然指向你,但是同时又是父亲的左指针或右指针的别名

root是父亲(6)的右指针的别名。这里就已经知道7是父亲的右指针了。并且也不用担心父亲为空的情况。

bool _EraseR(Node*& root, const K& key)
{
	if (root == nullptr)
		return false;

	if (root->_key < key)
	{
		return _EraseR(root->_right, key);
	}
	else if (root->_key > key)
	{
		return _EraseR(root->_left, key);

	}
	else
	{
		//1.左为空
		//2.右为空
		//3.左右都为空
		Node* cur = root;//注意不能直接删除root,因为下面root已经改变了,所以必须保存要被删除的结点
		if (root->_left == nullptr)
		{
			root = root->_right;
		}
		else if (root->_right == nullptr)
		{
			root = root->_left;
		}
		else
		{

		}
		delete cur;
		return true;
	}
}

左右都不为空,可以向非递归那样删除。也可以交换删除。
在这里插入图片描述

bool _EraseR(Node*& root, const K& key)
{
	if (root == nullptr)
		return false;

	if (root->_key < key)
	{
		return _EraseR(root->_right, key);
	}
	else if (root->_key > key)
	{
		return _EraseR(root->_left, key);

	}
	else
	{
		//1.左为空
		//2.右为空
		//3.左右都为空
		Node* cur = root;
		if (root->_left == nullptr)
		{
			root = root->_right;
		}
		else if (root->_right == nullptr)
		{
			root = root->_left;
		}
		else
		{
			Node* Minright = root->_right;
			while (Minright->_left)
			{

				Minright = Minright->_left;
			}
			swap(root->_key, Minright->_key);
			return _EraseR(root->_right, key);
		}
		delete cur;
		return true;
	}
}

非递归删除

在这里插入图片描述

bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr)
			return false;

		if (root->_key < key)
		{
			return _EraseR(root->_right, key);
		}
		else if (root->_key > key)
		{
			return _EraseR(root->_left, key);

		}
		else
		{
			//1.左为空
			//2.右为空
			//3.左右都为空
			Node* cur = root;
			if (root->_left == nullptr)
			{
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			else
			{
				Node* Minright = root->_right;
				Node* parent = root;
				while (Minright->_left)
				{
					parent = Minright;
					Minright = Minright->_left;
				}
				root->_key = Minright->_key;
				if (Minright == parent->_left)
				{
					parent->_left = Minright->_right;
				}
				else
				{
					parent->_right = Minright->_right;
				}
				delete Minright;
				return true;
			}
			delete cur;
			return true;
		}
	}

4.二叉搜索树默认成员函数

4.1构造

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

4.2析构

删除二叉树一般采用后序递归删除。

~BSTree()
{
	Delete(_root);
}
	
private:
	void Delete(Node* root)
	{
		if (root == nullptr)
			return;

		Delete(root->_left);
		Delete(root->_right);
		delete root;
	}

4.3拷贝构造

构建一个二叉树一般建议前序递归创建

BSTree(const BSTree<K>& t)
{
	_root = Copy(t._root);
}

private:
	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;

		Node* newnode = new Node(root->_key);
		newnode->_left = Copy(root->_left);
		newnode->_right = Copy(root->_right);
		return newnode;
	}

4.4赋值重载

传统写法

BSTree<K>& operator=(const BSTree<K>& t)
{
	if (this != &t)
	{
		Delete(_root);
		_root = Copy(t._root);
	}
	return *this;
}

现代写法,复用拷贝构造函数

//现代写法
BSTree<K>& operator=(BSTree<K> t)
{
	swap(_root, t._root);
	return *this;
}

6.二叉搜索树的应用

6.1K模型

K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。

K模型不允许插入相同的key值,也不允许修改key,否则可能破坏K模型的结构。

假设让写一段程序,用来检测英语作家,写作内容是否拼写错误。
这时可以采用K模型。

  1. 把词库中的单词作为Key,构建一颗搜索二叉树
  2. 在二叉搜索树中检索每个单词是否在这个树中,不再就报错。

Key的搜索模型------>判断在不在?

6.2KV模型

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

通过Key查找或修改Value。 K模型不允许修改,KV模型允许修改。

KV模型就是增加了一个V类型。Find成员函数的返回值需要修改一下。

template<class K,class V>
	struct BSTreeNode
	{
		BSTreeNode<K,V>* _left;
		BSTreeNode<K,V>* _right;
		K _key;
		V _value;

		BSTreeNode(const K& key,const V& value)
			:_key(key)
			,_value(value)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};


template<class K,class V>
class BSTree
{
	typedef BSTreeNode<K,V> Node;

public:
	//构造
	BSTree()
		:_root(nullptr)
	{}

	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()
	{
		Delete(_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)
		{
			//往下走之前,记录一下父亲
			parent = cur;
			if (cur->_key < key)
				cur = cur->_right;
			else if (cur->_key > key)
				cur = cur->_left;
			else
				return false;
		}
		cur = new Node(key,value);
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		return true;

	}

	bool Erase(const K& key)
	{
		if (_root == nullptr)
			return false;

		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
			{
				//1.左为空
				if (cur->_left == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_right;
						}
						else if (parent->_right == cur)
						{
							parent->_right = cur->_right;
						}
					}

					delete cur;
				}
				//2.右为空
				else if (cur->_right == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;
						}
						else if (parent->_right == cur)
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
				}
				//3.左右都不为空
				else
				{
					//1.右孩子最小
					/*Node* Minright = cur->_right;
					Node* parent = cur;
					while (Minright->_left)
					{
						parent = Minright;
						Minright = Minright->_left;
					}
					if (parent->_left == Minright)
					{
						parent->_left = Minright->_right;
					}
					else
					{
						parent->_right = Minright->_right;
					}
					delete Minright;*/
					//2.左孩子最大
					Node* Maxleft = cur->_left;
					Node* parent = cur;
					while (Maxleft->_right)
					{
						parent = Maxleft;
						Maxleft = Maxleft->_right;
					}
					cur->_key = Maxleft->_key;
					if (parent->_left == Maxleft)
					{
						parent->_left = Maxleft->_left;
					}
					else
					{
						parent->_right = Maxleft->_left;
					}
					delete Maxleft;

				}
				return true;
			}

		}
		return false;

	}

	Node* Find(const K& key)
	{
		if (_root == nullptr)
			return nullptr;

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


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

private:

	Node* Copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;

		Node* newnode = new Node(root->_key,root->_value);
		newnode->_left = Copy(root->_left);
		newnode->_right = Copy(root->_right);
		return newnode;
	}

	void Delete(Node* root)
	{
		if (root == nullptr)
			return;

		Delete(root->_left);
		Delete(root->_right);
		delete root;
	}


	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;

		_InOrder(root->_left);
		cout << root->_key << ":" << root->_value << endl;
		_InOrder(root->_right);
	}

private:

	Node* _root;
};

假设,实现一个简单的英汉词典dict,可以通过英文找到与其对应的中文。
我们可以采用KV模型实现。

void TestBSTree4()
{
	KV::BSTree<string ,string> kv;
	kv.Insert("sort", "排序");
	kv.Insert("search", "搜索");
	kv.Insert("left", "左边");
	kv.Insert("right", "右边");
	string str;
	while (cin >> str)
	{
		auto* ptr = kv.Find(str);
		if (ptr)
		{
			cout << ptr->_key << ":" << ptr->_value << endl;
		}
		else
		{
			cout << "无此单词" << endl;
		}
	}
}

统计每样水果出现的次数

void TestBSTree5()
{
	// 统计水果出现的次数
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
   "苹果", "香蕉", "苹果", "香蕉" };
	KV::BSTree<string, int> count;
	for (const auto& str : arr)
	{
		// 先查找水果在不在搜索树中
		// 1、不在,说明水果第一次出现,则插入<水果, 1>
		// 2、在,则查找到的节点中水果对应的次数++
		auto* ret = count.Find(str);
		if (ret == nullptr)
		{
			count.Insert(str, 1);
		}
		else
		{
			ret->_value++;
		}
	}
	count.InOrder();
}

KV搜索模型------>通过一个值去查找或者修改另一个值。

那以后想用二叉搜索树难道要手撕一个出来吗?
其实后面学的map对应KV模型set对应模型,不用担心。

7.二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

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

在这里插入图片描述
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N

最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

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

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

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

相关文章

Apache服务Rwrite功能使用

Rewrite也称为规则重写&#xff0c;主要功能是实现浏览器访问时&#xff0c;URL的跳转。其正则表达式是基于Perl语言。要使用rewrite功能&#xff0c;Apache服务器需要添加rewrite模块。如果使用源码编译安装&#xff0c;–enable-rewrite。有了rewrite模块后&#xff0c;需要在…

Rust生态系统:探索常用的库和框架

大家好&#xff01;我是lincyang。 今天我们来探索Rust的生态系统&#xff0c;特别是其中的一些常用库和框架。 Rust生态系统虽然相比于一些更成熟的语言还在成长阶段&#xff0c;但已经有很多强大的工具和库支持各种应用的开发。 常用的Rust库和框架 Serde&#xff1a;一个…

Redis篇---第十三篇

系列文章目录 文章目录 系列文章目录前言一、redis的过期策略以及内存淘汰机制二、Redis 为什么是单线程的三、Redis 常见性能问题和解决方案?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看…

绝地求生:想玩以前的老地图

小编是22年8月左右开始玩的&#xff0c;更早以前跟同学偶尔玩过几次&#xff0c;所以萨诺2.0玩过&#xff0c;不过那时候菜的还不如人机&#xff0c;死了都看不到人在哪&#xff0c;所以对地图没啥印象&#xff0c;比较有印象的是地图色调变得很黄昏。 自闭城 遗迹 这是迪厅&am…

机器人算法—ROS TF坐标变换

1.TF基本概念 &#xff08;1&#xff09;什么是TF&#xff1f; TF是Transformations Frames的缩写。在ROS中&#xff0c;是一个工具包&#xff0c;提供了坐标转换等方面的功能。 tf工具包&#xff0c;底层实现采用的是一种树状数据结构&#xff0c;根据时间缓冲并维护多个参考…

【云栖 2023】姜伟华:Hologres Serverless 之路——揭秘弹性计算组

云布道师 本文根据 2023 云栖大会演讲实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a;姜伟华 | 阿里云计算平台事业部资深技术专家、阿里云实时数仓 Hologres 研发负责人 演讲主题&#xff1a;Hologres Serverless 之路——揭秘弹性计算组 实时化成为…

基于SSM 离退休管理平台-计算机毕设 附源码 52629

ssm离退休管理平台 摘 要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用SSM技术和mysql数据库来完成对系统的…

vatee万腾科技先锋之选:vatee创新力驱动着未来发展

在科技潮流的浩荡前行中&#xff0c;Vatee万腾崭新的科技先锋之选正以强大的创新力引领着未来的发展。Vatee万腾凭借其前瞻性的技术理念和卓越的创新实践&#xff0c;成为业界的引领者&#xff0c;为整个科技行业树立了标杆。 Vatee万腾不仅仅是一家科技公司&#xff0c;更是一…

django ModelSerializer自定义显示字段

文章目录 前言一、问题二、解决 前言 最近在复习django的时候&#xff0c;发现了一个有趣的问题&#xff0c;解决了之后特意记录下来&#xff0c;以供以后参考。 一、问题 相信大家使用django的时候&#xff0c;被其DRF的强大功能所折服&#xff0c;因为它能通过简单的代码就…

一、防火墙-安全区域

学习防火墙之前&#xff0c;对路由交换应要有一定的认识 1、什么是防火墙2、防火墙的发展史3、安全区域3.1.接口、网络和安全区域的关系3.2.报文在安全区域之间流动方向3.3.安全区域的配置安全区域小实验 3.4.状态检测和会话机制3.4.1.状态检测3.4.2.会话 3.5.状态检测和会话机…

opencv-图像梯度

目标 • 图像梯度&#xff0c;图像边界等 • 使用到的函数有&#xff1a;cv2.Sobel()&#xff0c;cv2.Schar()&#xff0c;cv2.Laplacian() 等 原理 梯度简单来说就是求导。 OpenCV 提供了三种不同的梯度滤波器&#xff0c;或者说高通滤波器&#xff1a;Sobel&#xff0c;Schar…

在Linux服务器中查找mysql的配置文件并修改其内容并保存,清空mysql8.0以上默认开启SSL的配置,防止odbc无法连接的问题

------每个命令输完记得按【enter】回车键------- 1、查找mysql的配置文件命令-mysql的配置文件默认名是my.cnf&#xff1a; find / -name my.cnf 2、查看显示的配置文件内容&#xff1a; cat /etc/my.cnf 3、修改配置文件的内容&#xff1a; 使用vi 或vim 命令 vi /etc…

分享一些简单的英语问候语

昨天和一个朋友聊天&#xff0c;他问我最近有没有某个国家的客户&#xff1f;我说只有一两个&#xff0c;都已经好久没有联系了&#xff0c;上一次问候还是在九月份。他说从十月底开始就收到很多来自当地的询盘&#xff0c;你不妨问下客户最近是否有新的需求&#xff1f; 于是…

探索无限自然之美——Terragen Professional 4渲染软件

Terragen Professional 4是一款强大的自然环境渲染软件&#xff0c;为设计师、艺术家和电影制作人们带来了无限的创作可能性。无论是为游戏、电影、动画还是虚拟现实体验创建逼真的自然场景&#xff0c;Terragen Professional 4都能为您提供令人难以置信的结果。 Terragen Pro…

【C++ 设计模式】面向对象设计原则 Template Method 模式 Strategy 策略模式

一、面向对象设计原则 重新认识面向对象 理解隔离变化 • 从宏观层面来看&#xff0c;面向对象的构建方式更能适应软件的变化&#xff0c; 能将变化所带来的影响减为最小 各司其职 • 从微观层面来看&#xff0c;面向对象的方式更强调各个类的“责任” • 由于需求变化导…

【Web】NodeJs相关例题wp

目录 ①[GKCTF 2020]ez三剑客-easynode ②[MoeCTF 2021]fake game ③[安洵杯 2020]Validator ④ [HZNUCTF 2023 final]eznode ⑤[CNSS] &#x1f3ed; EzPollution_pre ⑥[CNSS]✴️ EzPollution ①[GKCTF 2020]ez三剑客-easynode const express require(express); co…

4.3、Linux进程(2)

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 通过系统调用创建进程--fork函数 结果是什么呢&#xff1f; 为什么会出来三个打印呢? 就是因为父进程调用了fork函数创建出了子进程的task_struct,但是一个进程不止task_struct&#xff0c;还有代码和数据&#xff0c;他们…

【2021集创赛】Diligent杯一等奖:基于Cortex-M3软核的智能识别称量平台

本作品参与极术社区组织的有奖征集|秀出你的集创赛作品风采,免费电子产品等你拿~活动。 杯赛题目&#xff1a;Diligent杯&#xff1a;基于FPGA开源软核的硬件加速智能平台 参赛组别&#xff1a;A组 设计任务&#xff1a; 利用业界主流软核处理器(仅限于Cortex-M系列及 RISC-V系…

Unity 头顶图文字性能优化

如图&#xff1a;常规的排版&#xff0c;会有很多Batches。这是优化后的Batches只有3。 常用解决方案&#xff1a; 1、创建两个Canvas&#xff0c;一个放所有文本Text&#xff0c;一个放所有Image。但这里有会有两个问题&#xff1a;一旦文字夹在两个Image中间&#xff0c;还有…

循环神经网络(RNN)实现股票预测

文章目录 一、前言二、前期工作1. 设置GPU&#xff08;如果使用的是CPU可以忽略这步&#xff09;2. 导入数据 四、数据预处理1.归一化2.设置测试集训练集 五、构建模型六、激活模型七、训练模型八、结果可视化1.绘制loss图2.预测3.评估 一、前言 我的环境&#xff1a; 语言环…