[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
    请添加图片描述
  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
最优二叉搜索树(Optimal Binary Search Tree)是一种动态规划的算法,用于在给定一组键和它们的概率时构建最优的二叉搜索树。 下面是 C 语言实现最优二叉搜索树详细代码: ``` #include <stdio.h> #include <limits.h> #define MAX_KEYS 100 int keys[MAX_KEYS]; int freq[MAX_KEYS]; int n; int sum(int freq[], int i, int j) { int s = 0; for (int k = i; k <= j; k++) { s += freq[k]; } return s; } int opt_cost(int freq[], int i, int j) { if (j < i) { return 0; } if (j == i) { return freq[i]; } int fsum = sum(freq, i, j); int min_cost = INT_MAX; for (int r = i; r <= j; r++) { int cost = opt_cost(freq, i, r-1) + opt_cost(freq, r+1, j) + fsum; if (cost < min_cost) { min_cost = cost; } } return min_cost; } int main() { printf("Enter the number of keys: "); scanf("%d", &n); printf("Enter the keys:\n"); for (int i = 0; i < n; i++) { scanf("%d", &keys[i]); } printf("Enter the frequencies:\n"); for (int i = 0; i < n; i++) { scanf("%d", &freq[i]); } int cost = opt_cost(freq, 0, n-1); printf("Minimum cost of optimal BST is %d.\n", cost); return 0; } ``` 代码中,`sum()` 函数用于计算频率数组 `freq[]` 中下标从 `i` 到 `j` 的元素之和,`opt_cost()` 函数则是实现最优二叉搜索树的核心算法。`opt_cost()` 函数使用递归的方法,枚举所有可能的根节点,计算出每个根节点对应的最小成本,并返回其中的最小值。 在主函数中,输入了键和频率数组,并调用 `opt_cost()` 函数计算出最小成本。 需要注意的是,该代码仅仅是计算了最小成本,并没有构建出最优二叉搜索树的结构。如果需要构建出最优二叉搜索树,需要在递归过程中记录每个子的根节点,然后根据这些根节点的位置来构建的结构。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DieSnowK

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值