【数据结构】二叉搜索树

发布于:2025-04-20 ⋅ 阅读:(28) ⋅ 点赞:(0)

 引入

当我们用有序数组储存数据时,查找某个数据可以用折半查找,时间复杂度为LogN;但是当我们要插入或删除数据时需要一步一步挪动数据,消耗非常大。为此引入了二叉搜索树这个数据结构。

二叉搜索树的规则

二叉搜索树又称二叉排序树,只要它不是空树,就满足一下规则:

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

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

3.它的左右子树也分别为二叉搜索树

4.二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看应用场景。

二叉搜索树的性能

最优情况下,二叉搜索树为完全二叉树,高度为log2N。

最差情况下,二叉树搜索树为单只树,高度为N。

所以综合来看,二叉搜索树的增删查改时间复杂度为:O(N)

这样的效率和数组相比优势不明显,无法满足我们的需求,后面将会学习 AVL和红黑树来解决效率问题。这两种二叉搜索树的增删查改的时间复杂度为:O(logN)。二分查找也能达到这个时间复杂度;为什么还要用二叉搜索树呢?这里就要提到能用二分查找的前提条件了:1.要保证有序2.要支持下标随机访问。而且用二分的结构大多在插入删除时需要挪动数据,消耗非常大。

二叉搜索树的插入

1.插入前树为空:
直接新增一个节点,赋值给root指针。

2.插入前树不为空:

插入值和当前节点值进行比较,比当前节点值小则与左子树值比较,为空时插入;比当前节点值大则与右子树值比较,为空时插入。

当插入值与当前节点值相等时,自由安排往左往右都可以;但是要注意逻辑连贯性,第一次往左了下次也得往左。

bool Insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		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);
	if (parent->_key < key)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	return true;
}

二叉搜索树的查找

1.从根节点开始比较,查找x,比查找值大则往右走,反之往左走。

2.若最后找到空节点则未找到。

3.如果未支持插入相等值,找到第一个就返回

4.如果支持插入相等的值,那么可能存在多个要查找的x,返回中序的第一个x。

如果要查找3,找到第一个3后,继续向下查找,直至找到空节点,返回最后一个找到的值为3的节点,为中序的第一个节点。

代码实现:

bool find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key > key)
		{
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			cur = cur->_right;
		}
		else
		{
			return true;
		}
		return false;
	}
}

二叉搜索树的删除

要删除二叉搜索树中的元素,首先要找到查找二叉搜索树中的元素,如果找不到,即不存在,返回false。因为一个节点有两个子节点,排列组合就有四种情况。

1.要删除的节点左右孩子都为空

这种情况最简单,直接删除就可以(把该节点的父节点指向此节点的路径指向空),不会影响整棵树的访问顺序。

2.节点左孩子为空,右孩子不为空

把父节点指向此节点的右孩子,直接删除此节点。

3.节点右孩子为空,左孩子不为空

把父节点指向左节点,直接删除该节点。

4.左右均不为空

这种情况最难处理,此节点的两个孩子要想办法安放。在讲堆和top-k问题时【数据结构】 -- 堆 (堆排序)(TOP-K问题)_高度为 k的二叉树最大的结点数为( )。-CSDN博客,我们插入一个元素,可以插入到末尾,然后再向上调整。其实这里有点类似这个的逆过程。

按照中序遍历,访问此节点前访问的元素时左子树中最大的元素,访问此节点后访问的第一个元素是右子树中最小的元素。用他俩中任意一个来替换此节点,中序遍历的结果都一样(具体用哪一个替换看自己喜好)。替换之后,此节点左右都为空,满足情况1,直接删除就好了。


	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//cur->_key == key
			{
				//左右至少有一个为空的情况,直接改变指向
				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 {
					//两个子树都有孩子的情况,用右子树最小或左子树最大来替换
					// ⼀定要把cur给rightMinP,否会报错。

					//注意右子树的第一个值就是最小值的情况
					Node* rightMinP = cur;
					Node* rightMin = cur->_right;
					while (rightMin->_left)//找出最左边的,就是右子树最小的
					{
						rightMinP = rightMin;
						rightMin = rightMin->_left;
					}
					cur->_key = rightMin->_key;//把右子树中最小值替换到cur->_key
					if (rightMinP->_left == rightMin)
						rightMinP->_left = rightMin->_right;
					else//右子树的第一个值就是最小值的情况
						rightMinP->_right = rightMin->_right;
					delete rightMin;

					return true;
				}
			}
		}
		return false;
	}

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

key搜索场景

一个节点只储存key这一个数据,这样实现的二叉搜索树只能实现增删查,不支持改动。

key/value搜索场景

每一个key,都有与之对应的值value,value可以是任意类型对象。树的结构中纪要储存key,也要储存value。这种二叉搜索树,还是按照key值来建立和搜索,key的值同样不能改变,但是value的值是可以改变的。就像按学号生成一个班级的二叉树,一个节点代表一个同学,就可以用这个节点的value记录该同学的成绩,每次考试成绩是可以改变的,value就更新。

代码实现:


#include<iostream>

using namespace std;
#include<string>

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()
//{
//	BSTree<string, string> dict;
//	//BSTree<string, string> copy = dict;
//	dict.Insert("left", "左边");
//	dict.Insert("right", "右边");
//	dict.Insert("insert", "插⼊");
//	dict.Insert("string", "字符串");
//	string str;
//	while (cin >> str)
//	{
//		auto ret = dict.Find(str);
//		if (ret)
//		{
//			cout << "->" << ret->_value << endl;
//		}
//		else
//		{
//			cout << "无此单词,请重新输⼊" << endl;
//		}
//	}
//	return 0;
//}


int main()
{


	string arr[] = { "蔡徐坤","陈立农","宋亚轩","范丞丞","黄明昊" };
	BSTree<string, int> countTree;
	for (const auto& str : arr)
	{
		// 先查找idol在不在搜索树中
		// 1、不在,说明idol第⼀次出现,则插⼊<idol, 1>
		// 2、在,则查找到的结点中idol对应的次数++
		//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;
	}
}


网站公告

今日签到

点亮在社区的每一天
去签到