一、概念
二叉搜索树又称为二叉排序树,主要满足以下性质的二叉树则成为二叉搜索树:
- 若它的左子树不为空,则左子树上所有结点的值都小于根结点的值。
- 若它的右子树不为空,则右子树上所有结点的值都大于根结点的值。
- 它的左右子树都是二叉搜索树。
- 空树为二叉搜索树。
二、二叉搜索树的操作
(1)二叉搜索树的查找
二叉搜索树的查找既是从一条路径一直向下,直到找到所需要的结点值的位置,或者为NULL值。
根据二叉搜索树的性质,我们知道任何结点的左边结点的值一定小于该结点的值,右边的结点的值一定大于该结点的值,因此我们便可采用如下方法实现二叉搜索树的查找。
首先我们需要判断该结点是否为空
不为空则判断如下情况:
- 如果该结点key==需要查找的结点的key,则返回true;
- 如果该结点key < 需要查找的结点的key,则在该结点的右子树中查找;
- 如果该结点key > 需要查找的结点的key,则在该结点的左子树中查找;
否则返回true;
代码实现如下:
Node* pCur = _pRoot;
while (pCur)
{
if (pCur->_key < key)
pCur = pCur->_pRight;
else if (pCur->_key > key)
pCur = pCur->_pLeft;
else if (pCur->_key == key)
return true;
}
return false;
(2)二叉搜素树插入
二叉搜索树的插入可以分为迭代的解法和非迭代的解法,实现原理如下。
首先如果向二叉搜索树中插入新的元素,必须先检查这个元素是否在二叉搜索树中已经存在,如果已经存在,则不进行插入,如果不存在,则将新的元素插入到搜索停止的地方。
非递归版本插入的实现:
- 首先判断根结点是否为空,如果为空则直接构造一个结点,并且把此结点作为根结点。
- 接着采用二叉搜索树中的查找结点的方法,查找所需要插入元素的结点是否存在于二叉搜索树中。
- 判断如果二叉搜索树中已经存在了该值的结点,则返回false,插入失败,如果没有找到结点,则在此位置构造一个结点即可。
二叉搜索树插入的图示
树为空树时
树不为空时
实现代码如下
bool Insert(const k& key, const v& value)
{
if (_pRoot == NULL)
_pRoot = new Node(kev, value);
Node* pCur = _pRoot;
while (pCur)
{
if (pCur->_key < key)
pCur = pCur->_pRight;
else if (pCur->_key > key)
pCur = pCur->_pLeft;
else if (pCur->_key == key)
return false;
}
pCur = new Node(key, value);
return true;
}
递归版本插入的实现:
- 判断传入的结点是否为空,如果为空构造该结点。
- 如果该节点的key小于需要插入结点的key,则传入该节点的右边。
- 如果该结点的key大于需要插入结点的key,则传入该节点的左边。
实现代码如下:
bool InsertR(Node*& _pRoot, const K& key)
{
if (NULL == _pRoot)
{
_pRoot = new Node(key);
return true;
}
if (_pRoot->_key < key)
return InsertR(_pRoot->pRight,key);
if (_pRoot->_key > key)
return InsertR(_pRoot->_pLeft, key);
//右指针的别名
}
(3)二叉搜索树的删除
二叉搜索树的删除情况比较复杂,分为了集中情况,因此我便用图示法讲解:
方法如下:
先查找元素是否在二叉搜索树中,如果不存在,则删除失败,返回false,否则结点会有以下几种情况。
- 要删除的结点无孩子
- 要删除的结点只有左孩子结点
- 要删除的结点只有右孩子结点
- 要删除的结点有左孩子与右孩子
对于上述四种情况我们的删除方法如下:
- 直接删除结点。
- 删除该结点,并且把它的双亲结点指向该节点的左孩子结点。
- 删除该结点,并且把它的双亲结点指向该结点的右孩子结点。
- 在它的右子树中寻找中序遍历下的第一个结点,用它的值和需要删除结点的值进行替换,最后删除这个遍历的结点即可。(或者找左子树最大的结点一样可行)
针对以上情况有以下事例
3.1删除节点只有右孩子
以下情况可把根结点指向它的右边。
3.2 删除结点只有左孩子
以下情况可把根结点指向它的左边。
3.3 删除结点左右孩子都存在
此时有两种方法可以实现这个步骤。
方法一:
交换右树中序遍历第一个结点,然后删除此节点
方法二:
交换左子树key值最大的结点,然后删除此结点
代码实现
同二叉搜索树的插入,我们也来实现二叉搜索树的普通版本的删除和递归版本的删除
普通版本的删除代码:
bool Remove(Node* _Root, const K& key)
{
Node* parent = NULL;
if (_pRoot == NULL)
return false;
if (_pRoot->_key == key)
delete(_pRoot);
Node* cur = _pRoot;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_pRight;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_pLeft;
}
else
break;
}
//此处找到要删除结点,或者为空
if (cur)
{
//结点有右孩子没有左孩子
if (cur->_pRight&&cur->_pLeft == NULL)
{
//此处需要判断结点是双亲的左子树和结点是双亲的右子树
//同时判断根结点
if (parent->_pLeft==cur)
{
parent->_pLeft = cur->_pRight;
}
else if (parent->_pRight == cur)
{
parent->_pRight = cur->_pRight;
}
else//根结点
{
_pRoot = cur->_pRight;
}
delete cur;
return true;
}
//结点有左孩子没有右孩子
if (cur->_pLeft&&cur->_pRight == NULL)
{
if (parent->_pLeft == cur)
{
parent->_pLeft = cur->_pLeft;
}
else if (parent->_pRight == cur)
parent->_pRight = cur->_pLeft;
else
_pRoot = cur->_pLeft;
delete cur;
return true;
}
//左右孩子都存在
//找到当前结点与中序遍历下的第一个结点ptr,
//与删除结点进行交换,之后删除ptr
else if (cur->_pLeft&&cur->_pRight)
{
Node* ptr = NULL;
if (parent->_pLeft == cur)
{
ptr = Inorder(cur->_pLeft);
cur->_key = ptr->_key;
cur->_value = ptr->_value;
delete ptr;
}
else if (parent->_pRight == cur)
{
ptr = Inorder(cur->_pRight);
cur->_key = ptr->_key;
cur->_value = ptr->_value;
delete ptr;
}
}
}
return false;
}
递归版本的删除代码
bool RemoveR(Node*& _pRoot, const K& key)//此处是引用
{
if (_pRoot == NULL)
return false;
if (_pRoot->_key < key)
return RemoveR(_pRoot->_pRight, key);
else if (_pRoot->_key > key)
return RemoveR(_pRoot->_pLeft, key);
else
{
if (_pRoot->_pLeft == NULL)
_pRoot = _pRoot->_pRight;
else if (_pRoot->_pRight == NULL)
_pRoot = _pRoot->_pLeft;
}
}
三、完整代码
#include <iostream>
using namespace std;
typedef int DataType;
template<class k,class v>
struct BSTreeNode
{
struct BSTreeNode* _pLeft;
struct BSTreeNode* _pRight;
k _key;
v _value;
BSTreeNode()
:_key(NULL)
, _value(NULL)
, _pLeft(NULL)
, _pRight(NULL)
{}
};
template<class k,class v>
class BSTree
{
typedef BSTreeNode<k, v> Node;
public:
BSTree()
:_pRoot(NULL)
{}
bool Insert(const k& key, const v& value)
{
if (_pRoot == NULL)
_pRoot = new Node(kev, value);
Node* pCur = _pRoot;
while (pCur)
{
if (pCur->_key < key)
pCur = pCur->_pRight;
else if (pCur->_key > key)
pCur = pCur->_pLeft;
else if (pCur->_key == key)
return false;
}
pCur = new Node(key, value);
return true;
}
//递归版本插入
bool InsertR(Node*& _pRoot, const K& key)
{
if (NULL == _pRoot)
{
_pRoot = new Node(key);
return true;
}
if (_pRoot->_key < key)
return InsertR(_pRoot->pRight,key);
if (_pRoot->_key > key)
return InsertR(_pRoot->_pLeft, key);
//右指针的别名
}
bool Remove(Node* _Root, const K& key)
{
Node* parent = NULL;
if (_pRoot == NULL)
return false;
if (_pRoot->_key == key)
delete(_pRoot);
Node* cur = _pRoot;
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_pRight;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_pLeft;
}
else
break;
}
//此处找到要删除结点,或者为空
if (cur)
{
//结点有右孩子没有左孩子
if (cur->_pRight&&cur->_pLeft == NULL)
{
//此处需要判断结点是双亲的左子树和结点是双亲的右子树
//同时判断根结点
if (parent->_pLeft==cur)
{
parent->_pLeft = cur->_pRight;
}
else if (parent->_pRight == cur)
{
parent->_pRight = cur->_pRight;
}
else//根结点
{
_pRoot = cur->_pRight;
}
delete cur;
return true;
}
//结点有左孩子没有右孩子
if (cur->_pLeft&&cur->_pRight == NULL)
{
if (parent->_pLeft == cur)
{
parent->_pLeft = cur->_pLeft;
}
else if (parent->_pRight == cur)
parent->_pRight = cur->_pLeft;
else
_pRoot = cur->_pLeft;
delete cur;
return true;
}
//左右孩子都存在
//找到当前结点与中序遍历下的第一个结点ptr,
//与删除结点进行交换,之后删除ptr
else if (cur->_pLeft&&cur->_pRight)
{
Node* ptr = NULL;
if (parent->_pLeft == cur)
{
ptr = Inorder(cur->_pLeft);
cur->_key = ptr->_key;
cur->_value = ptr->_value;
delete ptr;
}
else if (parent->_pRight == cur)
{
ptr = Inorder(cur->_pRight);
cur->_key = ptr->_key;
cur->_value = ptr->_value;
delete ptr;
}
}
}
return false;
}
Node* Inorder(Node* Root)
{
Node* cur = Root;
if (!cur)
{
return NULL;
}
if (cur->_pLeft)
cur = cur->_pLeft;
return cur;
}
//递归版本的删除
bool RemoveR(Node*& _pRoot, const K& key)//此处是引用
{
if (_pRoot == NULL)
return false;
if (_pRoot->_key < key)
return RemoveR(_pRoot->_pRight, key);
else if (_pRoot->_key > key)
return RemoveR(_pRoot->_pLeft, key);
else
{
if (_pRoot->_pLeft == NULL)
_pRoot = _pRoot->_pRight;
else if (_pRoot->_pRight == NULL)
_pRoot = _pRoot->_pLeft;
}
}
private:
Node* _pRoot;
};