二叉搜索树

二叉搜索树

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

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

例如下图为一个二叉搜索树:
在这里插入图片描述
二、二叉搜索树操作
1.二叉搜索树的查找
插入的具体过程如下:
设查找的值为a ;
从根节点开始,
若根节点的值等于a, 则返回pRoot,
若根节点的值大于a,则去根节点的左子树去找,
若根节点的值小于a,则去根节点的右子树去找。

2.二叉搜索树插入
情况一:
该树是空树,则直接将该结点插入到根的位置。
情况二:
不是空树,则找插入的位置,从根节点开始找,若大于根节点得值,则去根的右子树去找,若小于根节点得值,则去跟的左子树去找。若与根结点的值相等,则直接返回flase(二叉搜索树中的值唯一)。详细请看后面的完整代码。

3.二叉搜索树删除结点
分为以下几种情况:
1.空树,直接返回
2.非空
找删除结点的在二叉搜索树中的位置
删除:
a.该节点叶子结点 ---->可以直接删除
b.该节点只有左海子 ---->可以直接删除
c.该节点只有右孩子 —>可以直接删除
d.该节点左右孩子均存在
不同情况的具体处理方式如下:
一、只有右孩子结点:
在这里插入图片描述
情况一:删除8
pParent -> right = pCur -> right;
delete pCur;

在这里插入图片描述
情况二:删除1
pParent ->left = pCur -> rgiht;
delete pCur;
在这里插入图片描述
情况三:该节点是根结点 删除5
_pRoot = pCur -> right;
delete pCur;

二、只有左孩子结点
该情况与只有右孩子的情况相同,这里不再赘述。

三、该结点是叶子结点
判断该结点是其双亲节点的左孩子孩子右孩子,若是左孩子,则另其双亲结点的左指向空,若是右孩子,则让其双亲结点的右指向空。

四、左右孩子都存在
情况:删除7
在其子树中找一个替代结点,1.在左子树中找,找最大值。2.在右子树中找找最小值。两种方式都可。
具体删除过程如下:
在这里插入图片描述
在这里插入图片描述
最终将删除7转换为删除6位置的结点,这种情况的节点删除方式上面已经给出,不再赘述。
二叉搜索树完整代码:

//BSTree.hpp 
#pragma once

#include<iostream>
using namespace std;
template<class T>
struct BSTNode
{
	BSTNode(const T& data = T())
		:_pLeft(nullptr)
		, _pRight(nullptr)
		, _data(data)
	{}
	BSTNode<T>* _pLeft;
	BSTNode<T>* _pRight;
	T _data;
};

template<class T>
class BSTree
{
	typedef BSTNode<T> Node;
public:
	BSTree()
		:_pRoot(nullptr)
	{}

	~BSTree()
	{
		Destory(_pRoot);
	}
	Node* Find(const T& data)
	{
		Node* pCur = _pRoot;
		while (pCur)
		{
			if (data == pCur->_data)
				return pCur;
			else if (data > pCur->data)
			{
				pCur = pCur->_pRight;
			}
			else
				pCur = pCur->_pLeft;
		}
		return nullptr;
	}
	bool Insert(const T& data)
	{
		//空树
		if (nullptr == _pRoot)
		{
			_pRoot = new Node(data);
			return true;
		}
		//非空
		//按照二叉搜索树的性质找当前节点在二叉搜索树中的位置
		Node* pCur = _pRoot;
		Node* pParent = nullptr;	//保存双亲结点
		while (pCur)
		{
			pParent = pCur;
			if (data < pCur->_data)
				pCur = pCur->_pLeft;
			else if (data > pCur->_data)
				pCur = pCur->_pRight;
			else
				return false;	//相同不插
		}
		pCur = new Node(data);
		if (data > pParent->_data)
			pParent->_pRight = pCur;
		else
			pParent->_pLeft = pCur;
		return true;
	}

	//删除结点
	bool Delete(T data)
	{
		if (nullptr == _pRoot)
			return false;

		//按照二叉搜索树的规则找待删除结点的位置
		Node* pCur = _pRoot;
		Node* pParent = nullptr;
		while (pCur)
		{
			if (data == pCur->_data)
				break;
			else if(data < pCur->_data)
			{
				pParent = pCur;
				pCur = pCur->_pLeft;
			}
			else
			{
				pParent = pCur;
				pCur =  pCur->_pRight;
			}
		}
		//二叉搜索树中没有值为data的结点
		if (pCur == nullptr)
			return false;
		/*
		1.叶子结点  -->直接删除
		2.只有左孩子 --> 直接删除
		3.只有右孩子 --> 直接删除
		4.左右孩子均存在  --->找替代结点
		(左子树找最大值,右子树找最大值)
		1可以与2||3 合并
		*/
		//叶子结点和只有右孩子
		if (nullptr == pCur->_pLeft)
		{
			//待删除结点为根节点
			if (pCur == _pRoot)
				_pRoot->_pRight;
			else
			{
				//待删除结点是其双亲的左孩子
				if (pCur == pParent->_pLeft)
					pParent->_pLeft = pCur->_pRight;
				//待删除节点是其双亲的右孩子
				else
					pParent->_pRight = pCur->_pRight;

			}
		}
		else if (nullptr == pCur->_pRight)
		{
			//pCur只有左孩子
			if (pCur == _pRoot)
				_pRoot->_pLeft;
			else
			{

				if (pCur == pParent->_pLeft)
					pParent->_pLeft = pCur->_pLeft;

				else
					pParent->_pRight = pCur->_pLeft;

			}
		}
		else
		{
			//pCur左右孩子均存在
			//找右子树中替代结点
			//(找最小结点即右子树中最左侧结点)
			Node* pFirstofIn = pCur->_pRight;
			pParent = pCur;
			while (pFirstofIn->_pLeft)
			{
				pParent = pFirstofIn;
				pFirstofIn = pFirstofIn->_pLeft;
			}
			pCur->_data = pFirstofIn->_data;
			if (pFirstofIn == pParent->_pLeft)
				pParent->_pLeft = pFirstofIn->_pRight;
			else
				pParent->_pRight = pFirstofIn->_pRight;

			pCur = pFirstofIn;
		}

		delete pCur;
		return true;
	}

	//最左边元素 最小值
	Node* LeftMost()	
	{
		if (nullptr == _pRoot)
			return nullptr;
		Node* pCur = _pRoot;
		while (pCur->_pLeft)
		{
			pCur = pCur->_pLeft;
		}
		return pCur;
	}

	//最右边元素 最大值
	Node* RightMost()
	{
		if (nullptr == _pRoot)
			return nullptr;
		Node* pCur = _pRoot;
		while (pCur->_pRight)
		{
			pCur = pCur->_pRight;
		}
		return pCur;
	}

	void Inorder()
	{
		_Inorder(_pRoot);
	}
private:
	void _Inorder(Node* pRoot)
	{
		if (pRoot)
		{
			_Inorder(pRoot->_pLeft);
			cout << pRoot->_data << " ";
			_Inorder(pRoot->_pRight);
		}
	}

	void Destory(Node*& pRoot)
	{
		if (pRoot)
		{
			Destory(pRoot->_pLeft);
			Destory(pRoot->_pRight);
			delete pRoot;
			pRoot = nullptr;
		}
	}

	
private:
	Node* _pRoot;
};

void TestBSTree()
{
	int array[] = { 5,3,4,1,7,8,2,6,0,9 };
	BSTree<int> t;
	for (auto e : array)
	{
		t.Insert(e);
	}
	
	cout<< t.LeftMost()->_data << " ";
	cout << t.RightMost()->_data << " ";
	cout << endl;
	t.Inorder();
	t.Delete(8);
	t.Inorder();
	cout << endl;

	t.Delete(0);
	t.Delete(1);
	t.Inorder();
	cout << endl;

	t.Delete(5);
	t.Inorder();
}

三、二叉搜索树的性能分析
插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的 深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
在这里插入图片描述
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:logN,对应时间复杂度为O(logN)
最差情况下,二叉搜索树退化为单支树,其平均比较次数为 :N/2,对应时间复杂度为O(N);
问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插入关键码, 都可以是二叉搜索树的性能佳?
答案就是AVL树。
关于AVL的具体详情,请大家看我后面的博客:
AVL树的具体实现

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值