【C++从青铜到王者】第十九篇:C++二叉树进化之二叉搜索树

147 篇文章 171 订阅

在这里插入图片描述

系列文章目录



前言


在这里插入图片描述

一、内容安排说明

  • 二叉树在前面C数据结构阶段已经讲过,本节取名二叉树进阶是因为:
  • map和set特性需要先铺垫二叉搜索树,而二叉搜索树也是一种树形结构。
  • 二叉搜索树的特性了解,有助于更好的理解map和set的特性。
  • 二叉树中部分面试题稍微有点难度,在前面讲解大家不容易接受,且时间长容易忘。
  • 有些OJ题使用C语言方式实现比较麻烦。

因此本节借二叉树搜索树,对二叉树部分进行收尾总结。

二、二叉搜索树

1.二叉搜索树概念

  • 二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树。
  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值。
  • 它的左右子树也分别为二叉搜索树。
    在这里插入图片描述
    int a [] = {5,3,4,1,7,8,2,6,0,9};

2.二叉搜索树操作

1.二叉搜索树的查找

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

	Node* Find(const K& key)  //查找
	{
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return cur;
	}
2.二叉搜索树的插入

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

bool Insert(const K& key)
	{
		if (_root == nullptr)  //空树直接插入
		{
			_root = new Node(key);
			return true;
		} 
		//不是空树找寻要插入的位置
		Node* cur = _root;
		Node* parent = nullptr; //记录cur走过的上一个节点
		while (cur)
		{
			parent = cur;
			if (key > cur->_key)
			{
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		
		cur = new Node(key);

		if (key < parent->_key)    //把cur节点和父节点链接起来
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		return true;
	}
3.二叉搜索树的删除
  • 首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
  • 要删除的结点无孩子结点。
  • 要删除的结点只有左孩子结点。
  • 要删除的结点只有右孩子结点。
  • 要删除的结点有左、右孩子结点。

  • 看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
  • 情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点。
  • 情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点。
  • 情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


在这里插入图片描述

bool erase(const K& key)
	{
		if (_root == nullptr)
		{
			return false;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		//先找到这个节点
		while (cur)
		{
			if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if(key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//1.左为空
				//2.右为空
				//3.左右都不为空


				//找到了开始删除
				if(cur->_left == nullptr)        //左为空,父亲的分情况指向我的右
				{
					if (cur == _root)           //关键全部删除的时候
					{
						_root = cur->_right;
					}
					else
					{
						//parent->_right = cur->_right; 错误写法
						if (parent->_right == cur)
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}
					}
					delete cur;
					//break;
				}
				else if (cur->_right == nullptr)//右为空,父亲的分情况指向我的左
				{
					if (cur==_root)  //关键,全部删除的时候
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
					//break;
				}
				else
				{
					//这里找的右子树最小的节点,也就是右子树最左边的节点
					Node* RightMinParent = cur;
					Node* RightMin = cur->_right;
					//Node* RightMinParent = nullptr;
					while (RightMin->_left)
					{
						RightMinParent = RightMin;
						RightMin = RightMin->_left;
					}

					cur->_key = RightMin->_key; //赋值给_root节点

					//转换删除RightMin(RightMin左为空,父亲指向它的右)
					//RightMinParent->_left = RightMin->_right;错误,只判断一次

					if (RightMin == RightMinParent->_left)
					{
						RightMinParent->_left = RightMin->_right;
					}
					else
					{
						RightMinParent->_right = RightMin->_right;
					}

					delete RightMin;

				}
				return true;
			}
		}
4.二叉搜索树的修改

在这里插入图片描述

3.二叉搜索树实现

#pragma once
#include<iostream>
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     //BinarySearchTree
{
public:
	typedef BSTreeNode<K> Node;
public:
	bool Insert(const K& key)
	{
		if (_root == nullptr)  //空树直接插入
		{
			_root = new Node(key);
			return true;
		} 
		//不是空树找寻要插入的位置
		Node* cur = _root;
		Node* parent = nullptr; //记录cur走过的上一个节点
		while (cur)
		{
			parent = cur;
			if (key > cur->_key)
			{
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}
		
		cur = new Node(key);

		if (key < parent->_key)    //把cur节点和父节点链接起来
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		return true;
	}

	bool erase(const K& key)
	{
		if (_root == nullptr)
		{
			return false;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		//先找到这个节点
		while (cur)
		{
			if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if(key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				//1.左为空
				//2.右为空
				//3.左右都不为空


				//找到了开始删除
				if(cur->_left == nullptr)        //左为空,父亲的分情况指向我的右
				{
					if (cur == _root)           //关键全部删除的时候
					{
						_root = cur->_right;
					}
					else
					{
						//parent->_right = cur->_right; 错误写法
						if (parent->_right == cur)
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}
					}
					delete cur;
					//break;
				}
				else if (cur->_right == nullptr)//右为空,父亲的分情况指向我的左
				{
					if (cur==_root)  //关键,全部删除的时候
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
					//break;
				}
				else
				{
					//这里找的右子树最小的节点,也就是右子树最左边的节点
					Node* RightMinParent = cur;
					Node* RightMin = cur->_right;
					//Node* RightMinParent = nullptr;
					while (RightMin->_left)
					{
						RightMinParent = RightMin;
						RightMin = RightMin->_left;
					}

					cur->_key = RightMin->_key; //赋值给_root节点

					//转换删除RightMin(RightMin左为空,父亲指向它的右)
					//RightMinParent->_left = RightMin->_right;错误,只判断一次

					if (RightMin == RightMinParent->_left)
					{
						RightMinParent->_left = RightMin->_right;
					}
					else
					{
						RightMinParent->_right = RightMin->_right;
					}

					delete RightMin;

				}
				return true;
			}
		}


		return false;
		//有这个节点,就分情况讨论
	}
	void _InOrder(Node* _root)
	{
		if (_root == nullptr)
		{
			return;
		}
		_InOrder(_root->_left);
		std::cout << _root->_key << " ";
		_InOrder(_root->_right);
	}
	void InOrder()     //中序遍历
	{
		_InOrder(_root);   //类里面可以拿到_root
		std::cout << std::endl;
	}
	
	Node* Find(const K& key)  //查找
	{
		Node* cur = _root;
		while (cur)
		{
			if (key > cur->_key)
			{
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}
		return cur;
	}
private:
	Node* _root = nullptr;  //给缺省值是用默认构造函数初始化
};
void TestBSTree()
{
	BSTree<int> BST;
	int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
	for (size_t i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		BST.Insert(a[i]);
	}
	BST.InOrder();

	std::cout << BST.Find(3) << std::endl;


	for (auto e : a)
	{
		BST.InOrder();
		BST.erase(e);
	}


	//BST.erase(8);
	BST.InOrder();
	

	//全部删除有问题,删除7也有问题
}

在这里插入图片描述

4.二叉搜索树应用

在这里插入图片描述

  • K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
  • 以单词集合中的每个单词作为key,构建一棵二叉搜索树。
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
void TestBSTree()
{
	BSTree<std::string, std::string> dict;
	
	dict.Insert("sort", "排序");
	dict.Insert("string", "字符串");
	dict.Insert("tree", "树");
	dict.Insert("vector", "顺序表");

	std::string str;
	while (std::cin >> str)
	{
		BSTreeNode<std::string, std::string>* ret = dict.Find(str);  //节点的指针
		if (ret)
		{
			std::cout << ret->_value << std::endl;
		}
		else
		{
			std::cout << "没有这个单词" << std::endl;
		}
	}
}

在这里插入图片描述

  • KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。比如:实现一个简单的英汉词典dict,可以通过英文找到与其对应的中文,具体实现方式如下:
  • <单词,中文含义>为键值对构造二叉搜索树,注意:二叉搜索树需要比较,键值对比较时只比较Key。
  • 查询英文单词时,只需给出英文单词,就可快速找到与其对应的key。
void TestBSTree()
{
	std::string strArr[] = { "西瓜", "菠萝", "哈密瓜", "香蕉", "苹果", "西瓜", "西瓜", "西瓜", "西瓜", "西瓜", "西瓜", "樱桃" };
	BSTree<std::string, int>countTree;
	for (auto e: strArr)
	{
		BSTreeNode<std::string, int>* ret = countTree.Find(e);
		{
			if (ret == nullptr)
			{
				countTree.Insert(e, 1);
			}
			else
			{
				ret->_value++;
			}
		}
	}
	countTree.InOrder();
}

在这里插入图片描述

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

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


总结

以上就是今天要讲的内容,本文介绍了二叉搜索树的使用,而二叉搜索树查找提供了大量能使我们快速便捷地处理数据的函数和方法,我们务必掌握。另外如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。
在这里插入图片描述

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
判断给定二叉树是否为二叉排序树的方法如下: 1. 对于每个节点,其左子树所有节点的值都小于该节点的值,子树所有节点的值都大于该节点的值。 2. 对于整棵树,每个节点都满足上述条件。 可以采用中序遍历的方法依次比较每个节点的值,如果中序遍历的结果是单调递增的,则该树为二叉排序树。以下是基于此方法C++ 代码实现: ```c++ #include <iostream> #include <vector> #include <stack> using namespace std; // 二叉树结点定义 struct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) {} }; // 中序遍历二叉树 void inorderTraversal(TreeNode* root, vector<int>& v) { stack<TreeNode*> s; TreeNode* p = root; while (p != NULL || !s.empty()) { while (p != NULL) { s.push(p); p = p->left; } if (!s.empty()) { p = s.top(); s.pop(); v.push_back(p->val); p = p->right; } } } // 判断二叉树是否为二叉排序树 bool isBST(TreeNode* root) { if (root == NULL) return true; vector<int> v; inorderTraversal(root, v); for (int i = 1; i < v.size(); i++) { if (v[i] <= v[i-1]) return false; } return true; } // 测试 int main() { TreeNode* root = new TreeNode(5); root->left = new TreeNode(3); root->right = new TreeNode(6); root->left->left = new TreeNode(2); root->left->right = new TreeNode(4); root->right->right = new TreeNode(7); if (isBST(root)) { cout << "该二叉树是二叉排序树" << endl; } else { cout << "该二叉树不是二叉排序树" << endl; } return 0; } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

森明帮大于黑虎帮

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

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

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

打赏作者

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

抵扣说明:

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

余额充值