高阶数据结构 | 二叉搜索树(Binary Search Tree)

1.二叉搜索树的概念

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

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树
  • 总结就是只要符合根节点大于一边,小于一边即可
    在这里插入图片描述

2.二叉搜索树的特性

  • 二叉搜索树的中序遍历一定是有序的
    在这里插入图片描述

3.二叉搜索树的简单实现

3.1二叉搜索树的查找

  • 从根节点出发,如果大于根节点,则查找右子树,小于根节点,则查找左子树,如果遍历完还没有找到,则返回空
    在这里插入图片描述
//查找
	Node* find(const T& val)
	{
		//从根节点开始查找
		Node* cur = _root;
		while (cur)
		{
			if (cur->_val == val)
				return cur;
			else if (cur->_val < val)
				cur = cur->_right;
			else
				cur = cur->_left;
		}
		//不存在返回空
		return nullptr;
	}

3.2二叉搜索树的插入

  • 插入分两种情况,第一种是如果为空树,就直接插入
    在这里插入图片描述

  • 第二种,如果不是空树,则需要先搜索,再插入,并且判断数据是否存在
    在这里插入图片描述

//插入:1.空树(直接插入) 2.非空树(搜索+插入)还要判断数据是否存在
	bool insert(const T& val)
	{
		//判断是否为空树
		if (_root == nullptr)
		{
			_root = new Node(val);
			return true;
		}

		//搜索插入的位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->_val == val)
				return false;
			else if (cur->_val < val)
				cur = cur->_left;
			else
				cur = cur->_right;
		}
		//确定新插入数据的左右位置
		cur = new Node(val);
		if (parent->_val < val)
			parent->_right = cur;
		else
			parent->_left = cur;
		return true;
	}

3.3二叉搜索树的删除

  • 首先需要查找元素是否在树中,如果不存在直接返回
  • 如果元素存在,则一共存在以下几种情况:
  • 第一种:叶子节点(删除1,4,9)
    在这里插入图片描述
  • 第二种:删除的节点只有一个子树(删除 7),让需要被删除的节点的父亲直接指向子树
    在这里插入图片描述
  • 第三种:待删除的节点左右子树均有
    在这里插入图片描述
//删除
	bool erase(const T& val)
	{
		//1.判断根节点
		if (_root == nullptr)
			return false;
		Node* cur = _root;
		Node* parent = nullptr;
		//查找
		while (cur)
		{
			if (cur->_val == val)
				break;
			else if (cur->_val < val)
			{
				parent = cur;//更新节点
				cur = cur->_right;
			}			
			else
			{
				parent = cur;
				cur = cur->_left;
			}			
		}
		//判断cur是否为空
		if (cur == nullptr)
			return false;
		//删除:叶子or非叶子?
		//1.叶子节点
		if (cur->_left == nullptr && cur->_right == nullptr)
		{
			//判断删除的是否为根节点
			if (cur == _root)
			{
				_root = nullptr;
			}
			else
			{
				if (parent->_left == cur)
					parent->_left == nullptr;
				else
					parent->_right == nullptr;			
			}
			delete cur;
		}
		//2.非叶子节点
		//左边为空,右边不为空
		else if (cur->_left == nullptr)
		{
			if (cur == _root)
			{
				_root = cur->_right;
			}
			else
			{
				if (parent->_left == cur)
					parent->_left = cur->_right;
				else
					parent->_right = cur->_right;
			}
			delete cur;
		}
		//左边不为空,右边为空
		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;
		}
		//左右都不为空
		else
		{
			//左右孩子都存在
			//1.找左子树最右节点
			Node* child = cur->_left;
			parent = cur;
			while (child->_right)
			{
				parent = child;
				child = child->_right;
			}

			//2.最右节点的值覆盖cur位置的值
			cur->_val = child->_val;

			//3.重新链接
			if (parent->_left == child)
				parent->_left = child->_left;
			else
				parent->_right = child->_left;
			
			//4.删除最右节点
			delete child;
		}
		return true;
	}

3.4实现的源代码整合

#include<iostream>
using namespace std;
template<class T>
struct Node
{
	T _val;
	Node* _left;
	Node* _right;

	Node(const T& val = T())
		:_val(val)
		,_left(nullptr)
		,_right(nullptr)
	{}
};

template<class T>
class BST
{
public:
	typedef Node<T> Node;
	BST()//构造
		:_root(nullptr)
	{}
	BST(const BST<T>& val)//拷贝构造---深拷贝
	{
		//结构也要拷过来,走到什么位置,就把对应的节点创建出来
		_root = copy(bst._root);
	}
	//递归拷贝节点
	Node* copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* cur = new(root->_val);
		cur->_left = copy(root->_left);
		cur->_right = copy(root->_right);
		return cur;
	}
	~BST()
	{
		if (_root)
			destroy(_root);
	}
	//销毁
	void destroy(Node* root)
	{
		if (root)
		{
			destroy(root->_left);
			destroy(root->_right);
			delete root;
			root = nullptr;
		}
	}

	//查找
	Node* find(const T& val)
	{
		//从根节点开始查找
		Node* cur = _root;
		while (cur)
		{
			if (cur->_val == val)
				return cur;
			else if (cur->_val < val)
				cur = cur->_right;
			else
				cur = cur->_left;
		}
		//不存在返回空
		return nullptr;
	}
	

	//插入:1.空树(直接插入) 2.非空树(搜索+插入)还要判断数据是否存在
	bool insert(const T& val)
	{
		//判断是否为空树
		if (_root == nullptr)
		{
			_root = new Node(val);
			return true;
		}

		//搜索插入的位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->_val == val)
				return false;
			else if (cur->_val < val)
				cur = cur->_left;
			else
				cur = cur->_right;
		}
		//确定新插入数据的左右位置
		cur = new Node(val);
		if (parent->_val < val)
			parent->_right = cur;
		else
			parent->_left = cur;
		return true;
	}
	//删除
	bool erase(const T& val)
	{
		//1.判断根节点
		if (_root == nullptr)
			return false;
		Node* cur = _root;
		Node* parent = nullptr;
		//查找
		while (cur)
		{
			if (cur->_val == val)
				break;
			else if (cur->_val < val)
			{
				parent = cur;//更新节点
				cur = cur->_right;
			}			
			else
			{
				parent = cur;
				cur = cur->_left;
			}			
		}
		//判断cur是否为空
		if (cur == nullptr)
			return false;
		//删除:叶子or非叶子?
		//1.叶子节点
		if (cur->_left == nullptr && cur->_right == nullptr)
		{
			//判断删除的是否为根节点
			if (cur == _root)
			{
				_root = nullptr;
			}
			else
			{
				if (parent->_left == cur)
					parent->_left == nullptr;
				else
					parent->_right == nullptr;			
			}
			delete cur;
		}
		//2.非叶子节点
		//左边为空,右边不为空
		else if (cur->_left == nullptr)
		{
			if (cur == _root)
			{
				_root = cur->_right;
			}
			else
			{
				if (parent->_left == cur)
					parent->_left = cur->_right;
				else
					parent->_right = cur->_right;
			}
			delete cur;
		}
		//左边不为空,右边为空
		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;
		}
		//左右都不为空
		else
		{
			//左右孩子都存在
			//1.找左子树最右节点
			Node* child = cur->_left;
			parent = cur;
			while (child->_right)
			{
				parent = child;
				child = child->_right;
			}

			//2.最右节点的值覆盖cur位置的值
			cur->_val = child->_val;

			//3.重新链接
			if (parent->_left == child)
				parent->_left = child->_left;
			else
				parent->_right = child->_left;
			
			//4.删除最右节点
			delete child;
		}
		return true;
	}

	//中序遍历:是有序的
	void inorder()
	{
		_inorder(_root);
		cout << endl;
	}
	void _inorder(Node* root)
	{
		if (root)
		{
			_inorder(root->_left);
			cout << root->_val << " ";
			_inorder(root->_right);
		}
	}
private:
	Node* _root;
};

4.二叉搜索树两种模型的简单实现

4.1 K模型

  • 只有K作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值
  • 例子:给一个单词word,判断该单词是否正确
  • 以单词集合中的每个单词作为key,构建一棵二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误
template <class K>
struct Node
{
	K _key;
	Node* _left;
	Node* _right;

	Node(const K& key = K())
		:_key(key)
		, _left(nullptr)
		, _right(nullptr)
	{}
};

template <class K>
class BST
{
public:
	typedef Node<K> Node;
    
    //构造函数 
	BST()
		:_root(nullptr)
	{}
	//拷贝构造 
	BST(const BST<K>& key)//拷贝构造---深拷贝
	{
		//结构也要拷过来,走到什么位置,就把对应的节点创建出来
		_root = copy(bst._root);
	}
	//递归拷贝 
	Node* copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* cur = new(root->_key);
		cur->_left = copy(root->_left);
		cur->_right = copy(root->_right);
		return cur;
	}
	//析构函数 
	~BST()
	{
		if (_root)
			destory(_root);
	}
	//销毁 
	void destory(Node* root)
	{
		if (root)
		{
			destory(root->_left);
			destory(root->_right);
			delete root;
			root = nullptr;
		}
	}
	//查找 
	Node* find(const K& key)
	{
		//从根节点开始查找
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key == key)
				return cur;
			else if (cur->_key < key)
				cur = cur->_right;
			else
				cur = cur->_left;
		}
		return nullptr;
	}
    //插入 
	bool insert(const K& key)
	{
		//判断是否为空树
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		//搜索插入的位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->_key == key)
				return false;
			else if (cur->_key < key)
				cur = cur->_right;
			else
				cur = cur->_left;
		}

		cur = new Node(key);
		//确定新插入数据的左右位置
		if (parent->_key < key)
			parent->_right = cur;
		else
			parent->_left = cur;
		return true;
	}
    //删除 
	bool erase(const K& key)
	{
		if (_root == nullptr)
			return false;
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_key == key)
				break;
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}	
			else
			{
				parent = cur;
				cur = cur->_left;
			}	
		}
		if (cur == nullptr)
			return false;
		//删除操作
		// 1. 叶子节点
		if (cur->_left == nullptr && cur->_right == nullptr)
		{
			//判断删除的是否为根节点
			if (cur == _root)
			{
				_root = nullptr;
			}
			else
			{
				if (parent->_left == cur)
					parent->_left = nullptr;
				else
					parent->_right = nullptr;
			}

			delete cur;
		}
		else if (cur->_left == nullptr)
		{
			if (cur == _root)
			{
				_root = cur->_right;
			}
			else
			{
				if (parent->_left == cur)
					parent->_left = cur->_right;
				else
					parent->_right = cur->_right;
			}
			delete cur;
		}
		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;
		}
		else
		{
			//左右孩子都存在

			//1. 找左子树的最右节点
			Node* child = cur->_left;
			parent = cur;
			while (child->_right)
			{
				parent = child;
				child = child->_right;
			}

			//2. 用最右节点的值覆盖当前cur位置的值
			cur->_key = child->_key;

			//3. 重新链接
			if (parent->_left == child)
				parent->_left = child->_left;
			else
				parent->_right = child->_left;
			//4. 删除最右节点
			delete child;
		}

		return true;
	}
	//中序遍历:是有序的
	void inorder()
	{
		_inorder(_root);
		cout << endl;
	}
	void _inorder(Node* root)
	{
		if (root)
		{
			_inorder(root->_left);
			cout << root->_key << " ";
			_inorder(root->_right);
		}
	}

private:
	Node* _root;
};

4.2 K,V模型

  • 每一个关键码key,都有与之对应的值value,即<Key, Value>的键值对
  • 例子:给一个单词,找到对应的中文,<word,chinese>就构成一种键值对
  • <单词,中文含义>为键值对构造二叉搜索树,注意:二叉搜索树需要比较,键值对比较时只比较Key
  • 查询英文单词时,只需给出英文单词,就可快速找到与其对应的key
template <class K, class V>
struct Node
{
	K _key;
	V _val;
	Node* _left;
	Node* _right;

	Node(const K& key = K(), const V& val = V())
		: _key(key)
		,_val(val)
		, _left(nullptr)
		, _right(nullptr)
	{}
};

template <class K, class V>
class BST
{
public:
	typedef Node<K, V> Node;

	Node* find(const K& key)
	{
		//从根节点开始查找
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key == key)
				return cur;
			else if (cur->_key < key)
				cur = cur->_right;
			else
				cur = cur->_left;
		}
		return nullptr;
	}

	BST()
		:_root(nullptr)
	{}

	Node* copy(Node* root)
	{
		if (root == nullptr)
			return nullptr;
		Node* cur = new Node(root->_key, root->_val);
		cur->_left = copy(root->_left);
		cur->_right = copy(root->_right);
		return cur;
	}

	BST(const BST<K, V>& bst)
	{
		_root = copy(bst._root);
	}

	void destory(Node* root)
	{
		if (root)
		{
			destory(root->_left);
			destory(root->_right);
			delete root;
			root = nullptr;
		}
	}

	~BST()
	{
		if (_root)
			destory(_root);
	}

	bool insert(const K& key, const V& val)
	{
		//判断是否为空树
		if (_root == nullptr)
		{
			_root = new Node(key, val);
			return true;
		}

		//搜索插入的位置
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (cur->_key == key)
				return false;
			else if (cur->_key < key)
				cur = cur->_right;
			else
				cur = cur->_left;
		}

		cur = new Node(key, val);
		//确定新插入数据的左右位置
		if (parent->_key < key)
			parent->_right = cur;
		else
			parent->_left = cur;
		return true;
	}

	bool erase(const K& key)
	{
		if (_root == nullptr)
			return false;
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_key == key)
				break;
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				parent = cur;
				cur = cur->_left;
			}
		}
		if (cur == nullptr)
			return false;
		//删除操作
		// 1. 叶子节点
		if (cur->_left == nullptr && cur->_right == nullptr)
		{
			//判断删除的是否为根节点
			if (cur == _root)
			{
				_root = nullptr;
			}
			else
			{
				if (parent->_left == cur)
					parent->_left = nullptr;
				else
					parent->_right = nullptr;
			}

			delete cur;
		}
		else if (cur->_left == nullptr)
		{
			if (cur == _root)
			{
				_root = cur->_right;
			}
			else
			{
				if (parent->_left == cur)
					parent->_left = cur->_right;
				else
					parent->_right = cur->_right;
			}
			delete cur;
		}
		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;
		}
		else
		{
			//左右孩子都存在

			//1. 找左子树的最右节点
			Node* child = cur->_left;
			parent = cur;
			while (child->_right)
			{
				parent = child;
				child = child->_right;
			}

			//2. 用最右节点的值覆盖当前cur位置的值
			cur->_key = child->_key;
			cur->_val = child->_val;

			//3. 重新链接
			if (parent->_left == child)
				parent->_left = child->_left;
			else
				parent->_right = child->_left;
			//4. 删除最右节点
			delete child;
		}

		return true;
	}
	private:
	Node* _root;
};

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

  • 首先二叉树的基本操作都是基于查找的,查找的效率就代表了其他操作的性能
  • 对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多,总结一句话就是:二叉搜索树的平均查找长度与结点在树中的深度成正比
  • 最优情况下:二叉树为完全二叉树,其平均比较次数为log2 N
    在这里插入图片描述
  • 最坏情况下,二叉搜索树退化为单只树,其平均比较次数为N/2,查找元素相当于在顺序表中搜索,搜索效率降低,为了解决这一问题,加入平衡二叉树的属性,即AVL树。
  • 21
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值