二叉搜索树
一、二叉搜索树概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
例如下图为一个二叉搜索树:
二、二叉搜索树操作
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树的具体实现