[C++][数据结构][二叉搜索树]详细讲解


1.概念

  • 二叉搜索树又称二叉排序树,具有以下性质
    • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
    • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
    • 它的左右子树也分别为二叉搜索树
      请添加图片描述

2.二叉搜索树操作

1.查找

  • 从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找
  • 最多查找高度次,走到到空,还没找到,这个值不存在

2.插入

  • 树为空,则直接新增节点,赋值给root指针
  • 树不空,按二叉搜索树性质查找插入位置,插入新节点

3.删除

  • 首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:

    • 要删除的节点无孩子节点
    • 要删除的节点只有左孩子节点
    • 要删除的节点只有右孩子节点
    • 要删除的节点有左、右孩子节点
  • 看起来看起来有待删除节点有4中情况,实际ab/bc可以合并起来,因此真正的删除过程如下:

    • b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点 – 直接删除
    • c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点 – 直接删除
    • d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题 – 替换法删除
      • 左子树的最大节点 – > 左子树最右节点
      • 右子树的最小节点 --> 右子树最左节点
        请添加图片描述

3.二叉搜索树的实现

// Key模型
namespace Key
{
	template <class K>
	struct BSTreeNode // 为了能在类外访问,用struct
	{
		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;
	public:
		//BSTree()
		//{}

		// C++11的用法:强制编译器生成默认的构造函数
		BSTree() = default;

		BSTree(const BSTree<K>& t)
		{
			_root = _Copy(t._root);
			return *this;
		}

		~BSTree()
		{
			_Destroy(_root);
		}

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

		// 二叉搜索树的非递归玩法
		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为空,则找到了插入位置
			cur = new Node(key);
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;
		}

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

			return false;
		}

		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 (cur == _root) // 删除根节点
						{
							_root = cur->_right;
						}
						else
						{
							if (cur == parent->_left)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_right = cur->_right;
							}
						}

						delete cur;
						cur = nullptr;
					}
					else if (cur->_right == nullptr) // 右为空,只有左孩子
					{
						if (cur == _root) // 删除根节点
						{
							_root = cur->_left;
						}
						else
						{
							if (cur == parent->_left)
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}

						delete cur;
						cur = nullptr;
					}
					else // 左右都不为空,有两个孩子
					{
						// 替换法删除,找右子树最小节点进行替换
						Node* minParent = cur;
						Node* min = cur->_right;

						while (min->_left)
						{
							minParent = min;
							min = min->_left;
						}

						std::swap(min->_key, cur->_key);
						if (min == minParent->_left)
						{
							minParent->_left = min->_right; // 正常找,min肯定为左孩子
						}
						else
						{
							minParent->_right = min->_right; // min就是cur的右孩子
						}

						delete min;
						min = nullptr;
					} 

					return true;
				} // end of delete
			} // end of find key

			return false;
		}

		void InOrder()
		{
			_InOrder(_root);
			std::cout << std::endl;
		}
///
		// 二叉搜索树的递归玩法
		bool InsertR(const K& key)
		{
			return _InsertR(_root, key);
		}

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

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

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

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

			return copyRoot;
		}

		void _Destroy(Node*& root)
		{
			if (root == nullptr)
			{
				return;
			}

			_Destroy(root->_left);
			_Destroy(root->_right);
			delete root;
			root = nullptr; // 传引用,置空有效防止野指针
		}

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

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

		bool _InsertR(Node*& root, const K& key) // 这里Node*&神之一手,传引用,可以修改root
		{
			if (root == nullptr)
			{
				root = new Node(key);
				return true;
			}

			if (root->_key < key)
			{
				return _InsertR(root->_right, key); // 传引用,可以修改下层root,且链接关系不断
			}
			else if (root->_key > key)
			{
				return _InsertR(root->_left, key);
			}
			else
			{
				return false;
			}
		}

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

		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
			{
				// 删除
				Node* del = root;
				if (root->_left == nullptr) // 左为空,只有右孩子
				{
					root = root->_right; // root就是上一层root->_right/_left的引用,修改root就是修改上一层的链接关系
				}
				else if (root->_right == nullptr) // 右为空,只有左孩子
				{
					root = root->_left;
				}
				else // 两个都不为空,有两个孩子
				{
					// 替换法删除,找右子树最小节点进行替换
					Node* min = root->_right;
					while (min->_left)
					{
						min = min->_left;
					}

					std::swap(min->_key, root->_key);
					return _EraseR(root->_right, key); // 将问题再此划归到只有一个孩子的/没孩子的情况
				}

				delete del;
				del = nullptr;
				return true;
			}
		}

	private:
		Node* _root = nullptr;
	};
}

4.二叉搜索树的应用

1.K模型

  • 即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值
  • 比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
    • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
    • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误

2.KV模型

  • 每一个关键码key,都有与之对应的值Value,即<Key, Value>键值对

  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文就构成一种键值对

  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是就构成一种键值对

    // KeyValue模型
    namespace KeyValue
    {
    	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)
    			:_left(nullptr)
    			, _right(nullptr)
    			, _key(key)
    			, _value(value)
    	};
    
    	template<class K, class V>
    	class BSTree
    	{
    		typedef BSTreeNode<K, V> Node;
    	public:
    		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)
    		{
    			//...
    
    			return true;
    		}
    
    		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);
    		}
    	private:
    		Node* _root = nullptr;
    	};
    }
    

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

  • 插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能
  • 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为logN
  • 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为N
    请添加图片描述

相关推荐

  1. 数据结构——搜索

    2024-06-16 13:50:01       37 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-16 13:50:01       14 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-16 13:50:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-16 13:50:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-16 13:50:01       18 阅读

热门阅读

  1. Web前端收入来源:探索多元化的盈利渠道

    2024-06-16 13:50:01       5 阅读
  2. yolov10 学习笔记

    2024-06-16 13:50:01       6 阅读
  3. js面试题

    2024-06-16 13:50:01       6 阅读
  4. ndk-build

    2024-06-16 13:50:01       5 阅读
  5. AI学习指南机器学习篇-KNN基本原理

    2024-06-16 13:50:01       7 阅读
  6. XML XSLT:技术与应用解析

    2024-06-16 13:50:01       5 阅读
  7. 【C++】priority_queue的用法(模板参数的实例)

    2024-06-16 13:50:01       6 阅读
  8. 决策树算法介绍 - 原理与案例实现

    2024-06-16 13:50:01       8 阅读
  9. Web前端设计培训机构:深度解析与实战指南

    2024-06-16 13:50:01       8 阅读