二叉搜索树:高效查找与删除的实现

目录

前言:

一.二叉搜索树的定义

二.二叉搜索树的模拟实现以及原理

1.二叉搜索树的查找

2.二叉搜索树的插入

3.二叉搜索树的删除

三.二叉搜索树的应用

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

结言:


前言:

在C语言中我们学习了二叉树,但是当时学习的二叉树并没有任何的实际意义。只不过是一个单单的存储模型。那么今天我们就会讲到我们的二叉树的第一个应用------二叉搜索树。一个专门用于查找的二叉树。今天带大家一起来了解以及对它的模拟实现。

一.二叉搜索树的定义

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

若它的左子树不为空,则左子树上所有节点的值都小于根节点的值,若它的右子树不为空,则右子树上所有节点的值都大于根节点的值,它的左右子树也分别为二叉搜索树

二叉搜索树的任何一个根节点的左子树都小于根,任何一个右子树都大于根。我们可以根据这个特性根据插入数据的循序不同来构建出一颗二叉树。这个数就叫做二叉搜索树。

这就是一颗典型的二叉搜索树。】

二.二叉搜索树的模拟实现以及原理

下面我会边带大家了解这颗树边进行实现,这样有助于大家更好的去理解。

1.二叉搜索树的查找

比如这颗树我们要怎么去对他进行数据的查找呢?

比如说我们想找一个7那么我们怎么判断这颗树里面有没有7或者是7在这颗树的哪个位置,我们搜先在判断7与根节点的关系发现7小于8那么我们就去8的左子树去找,然后我们又发现7大于3所以我们就去右子树去找。就这样大的话往左子树找,小的话就去右子树找。如果检查到空还没有找到那么就说明这颗树中没有此数。

那么我们把这个思想换成代码就是这样的:

cpp 复制代码
bool Search(K& n)
	{
		Node* cur=_root;
		while (cur)
		{
			if (cur->_key > n)
			{
				cur = cur->_left;
			}
			else if (cur->_key < n)
			{
				cur = cur->_right;
			}
			else {
				return true;
			}
		}
		return false;
	}

当然在写这段代码之前我们还要构建出搜索二叉树的一个基本框架:

cpp 复制代码
template<class K>
struct BSTNode {
	K _key;
	BSTNode<K>* _left;
	BSTNode<K>* _right;
	BSTNode(const K& key)
		:_key(key)
		, _left(nullptr)
		, _right(nullptr)
	{}
};
template<class K>
class BSTree
{
	typedef BSTNode<K> Node;
private:
    Node*_root
}

2.二叉搜索树的插入

插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点
本质是跟树的查找没有区别的当插入这颗树本身就有的节点时只需要查找的这个元素返回false代表插入失败就可以了。如果没有这个节点那么就在最后找到的那个节点插入一个新的节点。

cpp 复制代码
bool insert(K& n)
	{
		if (_root == nullptr)
		{
			Node* newnode = new Node(n);
			_root = newnode;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_key > n)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < n)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		Node* newnode=new Node(n);
		if (n > parent->_key)
		{
			parent->_right = newnode;
		}
		else
		{
			parent->_left = newnode;
		}
	}

3.二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回false, 否则要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程
如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点
中,再来处理该结点的删除问题--替换法删除
在二叉搜索树中删除操作是唯一的偏难点,下面给大家画图展示几种情况

像case1和case2只需要直接删除然后让他们的孩子链接到他们的父亲就可以了,3情况下我们需要注意我们并不能直接删除,因为删除之后可能会导致这个二叉搜索树的规则被打破。这时我们有两种解决办法,1.替换删除节点左子树中最大的值,之后再删除。

2.替换删除节点右子树中最小的值,之后再删除。

下面我们来实现一下第二种:

cpp 复制代码
	bool erase(const K&n)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_key > n)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < n)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{

				if (cur->_left == nullptr)
				{
					if (parent == nullptr)
					{
						_root = cur->_right;
					}
					if (parent->_left == cur)
						parent->_left = cur->_right;
					else
						parent->_right = cur->_right;
					delete cur;
					return true;
				}
				else if(cur->_right == nullptr)
				{
					if (parent == nullptr)
					{
						_root = cur->_right;
					}
					if(parent->_right==cur)
					parent->_right = cur->_left;
					else
					parent->_right = cur->_left;

					delete cur;
					return true;
				}
				else
				{
					Node* rightminp = cur;
					Node* rightmin = cur->_right;
					while (rightmin->_left)
					{
						rightminp = rightmin;
						rightmin = rightmin->_left;
					}
					cur->_key = rightmin->_key;
					//rightminp->_left = rightmin->_right;
					if (rightminp->_left == rightmin)
						rightminp->_left = rightmin->_right;
					else
						rightminp->_right = rightmin->_right;
					delete rightmin;
					return true;
				}
			
			}
		}
	}

三.二叉搜索树的应用

  1. K 模型: K 模型即只有 key 作为关键码,结构中只需要存储 Key 即可,关键码即为需要搜索到
    的值
    比如: 给一个单词 word ,判断该单词是否拼写正确 ,具体方式如下:
    以词库中所有单词集合中的每个单词作为 key ,构建一棵二叉搜索树
    在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
  2. KV 模型:每一个关键码 key ,都有与之对应的值 Value ,即 <Key, Value> 的键值对 。该种方
    式在现实生活中非常常见:
    比如 英汉词典就是英文与中文的对应关系 ,通过英文可以快速找到与其对应的中文,英
    文单词与其对应的中文 <word, chinese> 就构成一种键值对; 再比如统计单词次数 ,统计成功后,给定单词就可快速找到其出现的次数, 单词与其出 现次数就是 <word, count> 就构成一种键值对。

我们日常生活在经常会用到这两种模型当我们进学校扫脸时,大概率就是我们的第一个模型。

当我们去停车厂停车时我们也会用到第二个模型,分别对车牌号以及进入的时间进行存储。

下面我们把我们的代码改造一下,变成我们的<key,value>模型:

cpp 复制代码
#pragma once
#include<iostream>
using namespace std;

template<class K,class V>
struct BSTNode {
	pair<K,V> _kv;
	BSTNode<K,V>* _left;
	BSTNode<K,V>* _right;
	BSTNode(const pair<K, V>& kv)
		:_kv(kv)
		, _left(nullptr)
		, _right(nullptr)
	{}
};
template<class K,class V>
class BSTree
{
	typedef BSTNode<K,V> Node;
public:
	bool Search(K& n)
	{
		Node* cur=_root;
		while (cur)
		{
			if (cur->_kv.first > n)
			{
				cur = cur->_left;
			}
			else if (cur->_kv.first < n)
			{
				cur = cur->_right;
			}
			else {
				return true;
			}
		}
		return false;
	}
	bool insert(const pair<K, V>& kv)
	{
		if (_root == nullptr)
		{
			Node* newnode = new Node(kv);
			_root = newnode;
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_kv.first > kv.first)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < kv.first)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return false;
			}
		}
		Node* newnode=new Node(kv);
		if (kv.first > parent->_kv.first)
		{
			parent->_right = newnode;
		}
		else
		{
			parent->_left = newnode;
		}
	}
	bool erase(const K&n)
	{
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
		
			if (cur->_kv.first > n)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_kv.first < n)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{

				if (cur->_left == nullptr)
				{
					if (parent == nullptr)
					{
						_root = cur->_right;
					}
					if (parent->_left == cur)
						parent->_left = cur->_right;
					else
						parent->_right = cur->_right;
					delete cur;
					return true;
				}
				else if(cur->_right == nullptr)
				{
					if (parent == nullptr)
					{
						_root = cur->_right;
					}
					if(parent->_right==cur)
					parent->_right = cur->_left;
					else
					parent->_right = cur->_left;

					delete cur;
					return true;
				}
				else
				{
					Node* rightminp = cur;
					Node* rightmin = cur->_right;
					while (rightmin->_left)
					{
						rightminp = rightmin;
						rightmin = rightmin->_left;
					}
					cur->_kv.first = rightmin->_kv.first;
					//rightminp->_left = rightmin->_right;
					if (rightminp->_left == rightmin)
						rightminp->_left = rightmin->_right;
					else
						rightminp->_right = rightmin->_right;
					delete rightmin;
					return true;
				}
			
			}
		}
	}
	void order()
	{
		_order(_root);
	}
private:
	void _order(Node *root)
	{
		if (root == nullptr)
		{
			return;
		}

		_order(root->_left);
		cout << root->_kv.first<<' ';
		_order(root->_right);
	}
	Node* _root= nullptr;
};

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

在一般情况下二叉搜索树的时间复杂度是O(logn)不过在极端情况下也可能达到O(n),比如有序插入的数据:

那么这时候我们就需要用到更加复杂的树,比如像AVL树和红黑树都是效率比较高的树,不过他们本质上依然还是一颗搜索二叉树不过是进行了一些优化对效率进行了改进。

结言:

今天我们讲的内容到这里就结束了,后面我们也会对AVL和红黑树进行讲解。不过这些树也是对二叉搜索树的基础上进行的改进,我们以后再说拜拜了!!!

相关推荐
神仙别闹1 小时前
基于C++实现(控制台)应用二维矩阵完成矩阵运算
开发语言·c++·矩阵
兩尛1 小时前
矩阵中非1的数量 (2025B卷
线性代数·算法·矩阵
kupeThinkPoem1 小时前
线段树有哪些算法?
数据结构·算法
sheeta19981 小时前
LeetCode 每日一题笔记 日期:2025.11.30 题目:1590.使数组和能被 P 整除
笔记·算法·leetcode
小许学java1 小时前
数据结构-包装类和泛型
数据结构·泛型·包装类·java-se
似水এ᭄往昔1 小时前
【C++】--二叉搜索树
开发语言·数据结构·c++
兩尛1 小时前
HJ43 迷宫问题
算法
水木姚姚1 小时前
C++程序创建(VS Code)
开发语言·c++
小龙报1 小时前
【算法通关指南:数据结构与算法篇(五)】树的 “自我介绍”:从递归定义到存储绝技(vector vs 链式前向星)
c语言·数据结构·c++·算法·链表·启发式算法·visual studio