[C/C++]详解STL容器5--二叉搜索树的介绍及模拟实现

Posted TT在长大

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[C/C++]详解STL容器5--二叉搜索树的介绍及模拟实现相关的知识,希望对你有一定的参考价值。

本文对二叉搜索树进行介绍,并对其核心功能进行了模拟实现。

目录

一、二叉搜索树的概念

二、 二叉搜索树操作

1. 二叉搜索树的查找

2.二叉搜索树的插入

3. 二叉搜索树的删除

三、二叉搜索树的性能分析

四、二叉搜索树的实现

1.class

2.class

总结


一、二叉搜索树的概念

二叉搜索树又称二叉排序树,除空树外,它具有以下性质:
        1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
        2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
        3. 它的左右子树也分别为二叉搜索树

二、 二叉搜索树操作

1. 二叉搜索树的查找

若根结点的关键字等于查找的关键字,查找成功,

若小于根结点的关键字的值,递归查找左子树,

若大于根结点的关键字的值,递归查找右子树,

若子树为空,则查找失败,

实现代码(非递归)如下:

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;
		

递归实现:

Node* _FindR(Node* root, const K& key)
		
			if (root == nullptr)
				return nullptr;

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


Node* FindR(const K& key)
		
			return _FindR(_root, key);
		

2.二叉搜索树的插入

插入的具体过程如下:

        a. 树为空,则直接插入;

        b. 树不空,按二叉搜索树性质查找插入位置,插入新节点;

实现代码(非递归)如下:

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->_left = cur;
			
			else 
				parent->_right = cur;
			

			return true;
		

    递归实现:

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;
		

bool InsertR(const K& key)
		
			return _InsertR(_root, key);
		

3. 二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
        a. 要删除的结点无孩子结点
        b. 要删除的结点只有左孩子结点
        c. 要删除的结点只有右孩子结点
        d. 要删除的结点有左、右孩子结点
        实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
        情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点
        情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点
        情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中。删除带两个节点的首先需要找到待删除节点的 后继节点 和 该后继节点的父节点,(一个节点的后继节点是指,这个节点在中序遍历序列中的下一个节点,相应的,前驱节点是指这个节点在中序遍历序列中的上一个节点),删除节点的后继节点一定是删除节点右子树的最左侧节点。

实现代码(非递归)如下:

bool Erase(const K& key)
		
			Node* parent = nullptr;
			Node* cur = _root;

			while (cur)
			
				if (cur->_key > key) 
					parent = cur;
					cur = cur->_left;
				
				else if (cur->_key < key) 
					parent = cur;
					cur = cur->_right;
				
				else 
				
					//找到

					//1.左为空,把孩子交给父亲管理,删除自己
					if (cur->_left == nullptr) 
					
						if (cur == _root) 					
							cur = cur->_right;
						
						else
						
							if (parent->_right == cur)				//确定在父节点那一侧
								parent->_right = cur->_right;
							else 
								parent->_left = cur->_right;
						

						delete cur;
					
					else if (cur->_right == nullptr)
					
						if (cur == _root) 
							cur = cur->_left;
						
						else
						
							if (parent->_right == cur)				//确定在父节点那一侧
								parent->_right = cur->_left;
							else
								parent->_left = cur->_left;
						

						delete cur;
					
					else
					
						//左右都不为空,需要找左子树的最大值,或者右子树的最小值进行替代

						Node* minRight = cur->_right;
						Node* minParent = cur;

						while (minRight->_left) 
							minParent = minRight;
							minRight = minRight->_left;
						

						cur->_key = minRight->_key;

						if (minParent->_left == minRight)
							minParent->_left = minRight->_right;		//只可能有右子树
						else
							minParent->_right = minRight->_right;		//存在可能,此时minParent就是cur,此时minParent->_right == minRight,则需要minParent->_right接管。

						delete minRight;


						/*
						//方法2:调用自己去删除最小节点
						Node* minRight = cur->_right;

						while (minRight->_left) 
							minRight = minRight->_left;
						

						K min = minRight->_key;

						this->Erase(min);			//调用自己删除找到的
						
						cur->_key = min;*/
					

					return true;
				
			

			return false;
		

递归方法:

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
			
				// 找root就是要删除的节点
				if (root->_left == nullptr)
				
					Node* del = root;
					root = root->_right;
					delete del;
				
				else if (root->_right == nullptr)
				
					Node* del = root;
					root = root->_left;
					delete del;
				
				else
				
					Node* minRight = root->_right;

					while (minRight->_left) 
						minRight = minRight->_left;
					

					K min = minRight->_key;

					_EraseR(root->_right, min);			//调用自己删除找到的

					root->_key = min;
				
			
		

bool EraseR(const K& key)
		
			return _EraseR(_root, key);
		

三、二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树。

最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:log2n

最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N / 2

四、二叉搜索树的实现

这里实现了class<K>, class<K, V>两种:

1.class<K>

namespace K

	template<class K>
	struct BSTreeNode					//节点类
	
		BSTreeNode<K>* _left;			//左子树指针
		BSTreeNode<K>* _right;			//右子树指针

		K _key;				//值

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

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

	private:
		Node* _root;		//根节点


		void _Destory(Node* root)
		
			if (root == nullptr)
				return;
			

			_Destory(root->_left);
			_Destory(root->_right);

			delete root;
		

		Node* _Copy(Node* root)					//深拷贝
		
			if (root == nullptr) 					
				return nullptr;
			

			Node* copyNode = new Node(root->_key);
			copyNode->_left = _Copy(root->_left);
			copyNode->_right = _Copy(root->_right);

			return copyNode;
		

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

			_InOrder(root->_left);			//中序遍历
			cout << root->_key << ' ';
			_InOrder(root->_right);
		

		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;
		

		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
			
				// 找root就是要删除的节点
				if (root->_left == nullptr)
				
					Node* del = root;
					root = root->_right;
					delete del;
				
				else if (root->_right == nullptr)
				
					Node* del = root;
					root = root->_left;
					delete del;
				
				else
				
					Node* minRight = root->_right;

					while (minRight->_left) 
						minRight = minRight->_left;
					

					K min = minRight->_key;

					_EraseR(root->_right, min);			//调用自己删除找到的

					root->_key = min;
				
			
		

		Node* _FindR(Node* root, const K& key)
		
			if (root == nullptr)
				return nullptr;

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

	public:
		BSTree()				//构造函数
			:_root(nullptr)
		

		BSTree(const BSTree<K>& t)
		
			_root = _Copy(t._root);
		

		BSTree<K> operator=(BSTree<K> t)
		
			swap(_root, t._root);
			return *this;
		

		~BSTree()
		
			_Destory(_root);
			_root = nullptr;
		

		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->_left = cur;
			
			else 
				parent->_right = cur;
			

			return true;
		

		bool Erase(const K& key)
		
			Node* parent = nullptr;
			Node* cur = _root;

			while (cur)
			
				if (cur->_key > key) 
					parent = cur;
					cur = cur->_left;
				
				else if (cur->_key < key) 
					parent = cur;
					cur = cur->_right;
				
				else 
				
					//找到

					//1.左为空,把孩子交给父亲管理,删除自己
					if (cur->_left == nullptr) 
					
						if (cur == _root) 					
							cur = cur->_right;
						
						else
						
							if (parent->_right == cur)				//确定在父节点那一侧
								parent->_right = cur->_right;
							else 
								parent->_left = cur->_right;
						

						delete cur;
					
					else if (cur->_right == nullptr)
					
						if (cur == _root) 
							cur = cur->_left;
						
						else
						
							if (parent->_right == cur)				//确定在父节点那一侧
								parent->_right = cur->_left;
							else
								parent->_left = cur->_left;
						

						delete cur;
					
					else
					
						//左右都不为空,需要找左子树的最大值,或者右子树的最小值进行替代

						Node* minRight = cur->_right;
						Node* minParent = cur;

						while (minRight->_left) 
							minParent = minRight;
							minRight = minRight->_left;
						

						cur->_key = minRight->_key;

						if (minParent->_left == minRight)
							minParent->_left = minRight->_right;		//只可能有右子树
						else
							minParent->_right = minRight->_right;		//存在可能,此时minParent就是cur,此时minParent->_right == minRight,则需要minParent->_right接管。

						delete minRight;


						/*
						//方法2:调用自己去删除最小节点
						Node* minRight = cur->_right;

						while (minRight->_left) 
							minRight = minRight->_left;
						

						K min = minRight->_key;

						this->Erase(min);			//调用自己删除找到的
						
						cur->_key = min;*/
					

					return true;
				
			

			return false;
		

		void InOrder()					//中序遍历
		
			_InOrder(_root);			//递归实现
			cout << endl;
		

		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 InsertR(const K& key)
		
			return _InsertR(_root, key);
		

		bool EraseR(const K& key)
		
			return _EraseR(_root, key);
		

		Node* FindR(const K& key)
		
			return _FindR(_root, key);
		
	;

测试:

void Test1()

	K::BSTree<int> t;
	int arr[] = 5, 3, 4, 6, 9, 7, 1, 2, 8, 0;

	for (auto e : arr) 
	
		t.InsertR(e);
	

	t.InOrder();

	t.EraseR(5);
	t.InOrder();

	t.EraseR(0);
	t.InOrder();


void Test2()

	K::BSTree<int> t, t1;
	int arr[] =  5, 3, 4, 6, 9, 7, 1, 2, 8, 0 ;

	for (auto e : arr)
	
		t.InsertR(e);
	

	t1 = t;

	t1.InOrder();

	t1.EraseR(5);
	t1.InOrder();

	t1.EraseR(0);
	t1.InOrder();

Test1:

Test2:

2.class<K, V>

namespace KV						//k-v

	template<class K, class V>
	struct BSTreeNode					//节点类
	
		BSTreeNode<K, V>* _left;			//左子树指针
		BSTreeNode<K, V>* _right;			//右子树指针

		K _key;				//值
		V _val;				//类似于PYTHON中的字典

		BSTreeNode(const K& key, const V& val)
			:_left(nullptr)
			, _right(nullptr)
			, _key(key)
			, _val(val)
		
	;

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

	private:
		Node* _root;		//根节点


		void _Destory(Node* root)
		
			if (root == nullptr) 
				return;
			

			_Destory(root->_left);
			_Destory(root->_right);

			delete root;
		

		Node* _Copy(Node* root)					//深拷贝
		
			if (root == nullptr) 
				return nullptr;
			

			Node* copyNode = new Node(root->_key, root->_val);
			copyNode->_left = _Copy(root->_left);
			copyNode->_right = _Copy(root->_right);

			return copyNode;
		

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

			_InOrder(root->_left);			//中序遍历
			cout << root->_key << ':' << root->_val << endl;
			_InOrder(root->_right);
		

		bool _InsertR(Node*& root, const K& key, const V& val)			//引用,可以直接改变父节点的左右值
		
			if (root == nullptr)
			
				root = new Node(key, val);
				return true;
			

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

		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
			
				// 找root就是要删除的节点
				if (root->_left == nullptr)
				
					Node* del = root;
					root = root->_right;
					delete del;
				
				else if (root->_right == nullptr)
				
					Node* del = root;
					root = root->_left;
					delete del;
				
				else
				
					Node* minRight = root->_right;

					while (minRight->_left) 
						minRight = minRight->_left;
					

					K min = minRight->_key;
					K minV = minRight->_val;

					_EraseR(root->_right, min);			//调用自己删除找到的

					root->_key = min;
					root->_val = minV;
				
			
		

		Node* _FindR(Node* root, const K& key)
		
			if (root == nullptr)
				return nullptr;

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

	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()
		
			_Destory(_root);
			_root = nullptr;
		

		void InOrder()					//中序遍历
		
			_InOrder(_root);			//递归实现
			cout << endl;
		

		bool InsertR(const K& key, const V& val)
		
			return _InsertR(_root, key, val);
		

		bool EraseR(const K& key)
		
			return _EraseR(_root, key);
		

		Node* FindR(const K& key)
		
			return _FindR(_root, key);
		
	;

 测试:

void Test3()

	KV::BSTree<string, string> dict;
	dict.InsertR("string", "字符串");
	dict.InsertR("tree", "树");
	dict.InsertR("left", "左边、剩余");
	dict.InsertR("right", "右边");
	dict.InsertR("sort", "排序");
	// ...插入词库中所有单词
	string str;
	while (cin >> str)
	
		KV::BSTreeNode<string, string>* ret = dict.FindR(str);
		if (ret == nullptr)
		
			cout << "单词拼写错误,词库中没有这个单词:" << str << endl;
		
		else
		
			cout << str << "中文翻译:" << ret->_val << endl;
		
	


void Test4()

	// 统计水果出现的次数
	string arr[] =  "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" ;
	KV::BSTree<string, int> countTree;
	for (const auto& str : arr)
	
		// 先查找水果在不在搜索树中
		// 1、不在,说明水果第一次出现,则插入<水果, 1>
		//KV::BSTreeNode<string, int>* ret = countTree.FindR(str);
		auto ret = countTree.FindR(str);
		if (ret == NULL)
		
			countTree.InsertR(str, 1);
		
		else
		
			ret->_val++;
		
	

	countTree.InOrder();

Test3:

Test4:

总结

本文对二叉搜索树进行介绍和模拟实现,需要注意的是删除节点部分的细节。

以上是关于[C/C++]详解STL容器5--二叉搜索树的介绍及模拟实现的主要内容,如果未能解决你的问题,请参考以下文章

[C/C++]详解STL容器4--二叉搜索树的介绍及模拟实现

[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现

[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现

[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现

[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现

[C/C++]详解STL容器6--AVL树的介绍及部分模拟实现