二叉搜索树

目录

二叉搜索树概念

二叉树搜索树的模拟实现

[1.插入 Insert](#1.插入 Insert)

[2.Erase 删除结点(难点)](#2.Erase 删除结点(难点))

3.InOder (中序遍历)

4.Find

递归实现方式

完整代码

总结


二叉搜索树概念

其又称二叉排序树、二叉查找树。

满足以下性质:
非空左子树的key值小于根结点的key值;

非空右子树的key值大于根结点的key值;

左、右子树都是二叉搜索树。

二叉树搜索树的模拟实现

1.插入 Insert

在树中,寻找适合key插入的位置,找到插入返回true ,已有重复key返回false.

思路:

比较key 和根结点的_key 如果key大于_key 往根的右子树去找

如果key小于_key 往根的左子树去找

如果树中不存在已有的key 则必定走到空结点 ,链接上key的值即可

如果树中存在key 结束查找 返回false

画图辅助理解

在树中,插入key为五的结点:

定义一个cur临时结点

1--比较cur->_key 与key(下面简称比较) key小于cur->_key 往左子树中查找 cur=cur->left

2--比较 key>cur->_key 往右子树查找 cur=cur->right

3--比较 key<cur->_key 往左子树查找 cur=cur->left

4--比较 key>cur->_key 往右子树查找 cur=cur->right

5--cur为空 找到可以链接的位置 链接上结点

以下有俩点需要注意:

1.如果树为空 ,则需要先创建树

2.链接结点,需要保存cur的父亲

下面代码演示

		bool Insert(const K& key)
		{
			if (_root == nullptr)
			{
				_root = new Node(key);
				return true;
			}
			
			//找到适合插入的位置
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur) 
			{
				parent = cur;  //更新parent
				if (cur->_key > key)
				{
					cur = cur->_left;
				}
				else if (cur->_key < key) //更新parent
				{
					cur = cur->_right;
				}
				else
					return false;
			}

			//准备插入  插入点一定是空结点
			cur = new Node(key);

			//判断插入父亲的左还是右
			if (parent->_key > key) parent->_left = cur;
			else parent->_right = cur;

			//插入成功
			return true; 

		}

2.Erase 删除结点(难点)

关于二叉搜索树的删除相对不容易,需要考虑多种情况,下面就来详细解释。

上图列举四种具有代表性的删除结点

分别是:

key结点的左和右子树都为空 key的左子树为空 key的右子树为空 key的左和右子树都不为空

对于左子树右子树都为空 我们可以归于左子树或者右子树为空的一种中

下文 只需要对 2 3 4三种方式进行剖析

这三种方式都必须先找到cur

找cur的方式与inser相同 ,在此不做介绍

1)cur的左为空

--1--判断cur是否为根结点 是则将cur->left赋值给_root

--2--判断cur是父亲的左还是右 ,链接cur->right

2)cur的右为空

该方式与1)类似

需要判断cur是否为root结点

不为root则需要判断是父亲的左还是右结点

在此不做过多的介绍

3)cur的左和右子树都不为空

如果我们简单的删除cur ,则破坏搜索二叉树,因此我们引入替换法

在cur的子树中,找到一个合适的值替换,使树仍为搜索二叉树

找替换结点的方式:
左子树中,找最大

右子树中,找最小

如果我们要删除key为3的结点 因为cur的结点有左子树和右子树 所以需要找替换

我们找cur右子树的最小值 即右子树最左边的元素 4

交换 3 和 4的值 然后删除 rightmin的结点

要删除rightmin结点 需要注意该结点的右子树需要被链接到cur的父亲上

代码剖析

		bool Erase(const K& key)
		{
			//1.k的左为空
			//2.k的右为空
			//3.k左右都存在,替换删除

			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				parent = cur; //更新父亲
				if (cur->_key < key) cur = cur->_right;
				else if (cur->_key > key) cur = cur->_left;

				//找到了
				else
				{
					//1.左为空
					if (cur->_left == nullptr)
					{

						//删除点为根
						if (cur == _root) _root = cur->_right;

						//判断是父亲的左、右链接
						if (parent->_right == cur)  //右链接
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}

					}

					//2.右为空
					else if (cur->_right == nullptr)
					{
						//删除点为根
						if (cur == _root) _root = cur->_left;

						//判断是父亲的左、右链接
						if (parent->_right == cur)  //右链接
						{
							parent->_right = cur->_left;
						}
						else
						{
							parent->_left = cur->_left;
						}
					}

					//3.替换
					else
					{
						//找右数的最小 右子树的最左边元素
						Node* pminright = cur;
						Node* minright = cur->_right;
						
						//找到左树空就找到了
						while (minright->_left)
						{
							pminright = minright;  //保存父亲结点
							minright = minright->_right;	
						}

						cur->_key = minright->_key;  //交换

						//判断是父亲的左子树还是右子树要被删除
						if (pminright->_left ==minright )
							pminright->_left = minright->_right;
						else 
							pminright->_right= minright->_right;
					}
				}
			}
			delete(cur);
			return true;
		}

3.InOder (中序遍历)

规则:左子树 ------根结点------右子树

遍历顺序 1 3 4 6 7 8 10 13 14

搜索二叉树的中序遍历可以将key按照大小遍历出

对于一个类中,我们调用InOder函数 是无法传递私有变量_root

因为写一个子函数提供遍历

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

			_InOrder(root->_left);
			cout << root->_key << ":" << root->_val << endl;
			_InOrder(root->_right);

		}

4.Find

查找树中是否存在值为key的函数,找到返回结点指针,找不到返回空

思路是从根结点开始 ,如果key 小于cur->_key 那么往左子树查找,大于则往右子树查找

		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; //没找到,返回空指针
		}

递归实现方式

关于递归的实现,其思路较简单 方法与循坏类似

写代码有一条不成文的准则:

能用循坏,尽量不用递归,避免栈溢出;

对于二叉搜索树的递归 需要注意:
--1--递归要传入结点参数,要写子函数调用

--2--cur的链接,不需要保存父亲结点 引用cur的指针

完整代码

展示key_value模型应用的代码

#include<iostream>
#include<string>
using namespace std;


namespace key_val
{
	template<class K, class V> //key和value
	//node
	struct BSTreeNode
	{
		BSTreeNode<K, V>* _left;
		BSTreeNode<K, V>* _right;
		K _key;
		V _val;

		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;
	public:
		bool Insert(const K& key, const V& value)
		{
			if (_root == nullptr)
			{
				_root = new Node(key, value);
				return true;
			}
			
			//找到适合插入的位置
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur) 
			{
				parent = cur;  //更新parent
				if (cur->_key > key)
				{
					cur = cur->_left;
				}
				else if (cur->_key < key) //更新parent
				{
					cur = cur->_right;
				}
				else
					return false;
			}

			//准备插入  插入点一定是空结点
			cur = new Node(key, value);

			//判断插入父亲的左还是右
			if (parent->_key > key) parent->_left = cur;
			else parent->_right = 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)
		{
			//1.k的左为空
			//2.k的右为空
			//3.k左右都存在,替换删除

			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				parent = cur; //更新父亲
				if (cur->_key < key) cur = cur->_right;
				else if (cur->_key > key) cur = cur->_left;

				//找到了
				else
				{
					//1.左为空
					if (cur->_left == nullptr)
					{

						//删除点为根
						if (cur == _root) _root = cur->_right;

						//判断是父亲的左、右链接
						if (parent->_right == cur)  //右链接
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}

					}

					//2.右为空
					else if (cur->_right == nullptr)
					{
						//删除点为根
						if (cur == _root) _root = cur->_left;

						//判断是父亲的左、右链接
						if (parent->_right == cur)  //右链接
						{
							parent->_right = cur->_left;
						}
						else
						{
							parent->_left = cur->_left;
						}
					}

					//3.替换
					else
					{
						//找右数的最小 右子树的最左边元素
						Node* pminright = cur;
						Node* minright = cur->_right;
						
						//找到左树空就找到了
						while (minright->_left)
						{
							pminright = minright;  //保存父亲结点
							minright = minright->_right;	
						}

						cur->_key = minright->_key;  //交换

						//判断是父亲的左子树还是右子树要被删除
						if (pminright->_left ==minright )
							pminright->_left = minright->_right;
						else 
							pminright->_right= minright->_right;
					}
				}
			}
			delete(cur);
			return true;
		}

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

			_InOrder(root->_left);
			cout << root->_key << ":" << root->_val << endl;
			_InOrder(root->_right);

		}

	private:
		Node* _root = nullptr;
	
	
	};

总结

本文重点学习搜索二叉树的插入和删除,它具有快速查找的功能,在查找具有O(logN)-O(N)的效率,关于搜索二叉树的实现,有较多的细节,也要仔细甄别。

相关推荐
九圣残炎7 分钟前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
lulu_gh_yu12 分钟前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
丫头,冲鸭!!!32 分钟前
B树(B-Tree)和B+树(B+ Tree)
笔记·算法
Re.不晚36 分钟前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
为什么这亚子2 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
2 小时前
开源竞争-数据驱动成长-11/05-大专生的思考
人工智能·笔记·学习·算法·机器学习
~yY…s<#>2 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode
幸运超级加倍~3 小时前
软件设计师-上午题-16 算法(4-5分)
笔记·算法
yannan201903133 小时前
【算法】(Python)动态规划
python·算法·动态规划
埃菲尔铁塔_CV算法3 小时前
人工智能图像算法:开启视觉新时代的钥匙
人工智能·算法