1、什么是二叉搜索树?
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树。
2、二叉搜索树基本操作
二叉搜索树的查找:
//非递归查找
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->right;
}
else if (cur->_key > key)
{
cur = cur->left;
}
else
{
return cur;
}
}
return nullptr;
}
//递归查找
Node* _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return nullptr;
}
if (root->_key == key)
{
return root;
}
else if (root->_key < key)
{
return _FindR(root->right, key);
}
else if (root->_key > key)
{
return _FindR(root->left, key);
}
}
Node* FindR(const K& key)
{
return _FindR(_root, key);
}
二叉搜索树的插入:
- 树为空,则直接插入
- 树不为空时,按二叉搜索树性质查找插入位置,插入新节点
//递归插入
bool _InsertR(Node*& root, const K& key)//加上引用才可以插入进去
{
if (root == nullptr)
{
root = new Node(key);
}
if (root->_key < key)
{
return _InsertR(root->right, key);
}
else if (root->_key > key)
{
return _InsertR(root->left, key);
}
else
{
return false;
}
}
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
//非递归插入
bool Insert(const K& key)
{
if (_root == nullptr)//无结点时
{
_root = new Node(key);
return true;
}
Node* cur = _root;
Node* parent = nullptr;//注意要在这里定义一个parent保存前一个结点,最后在连接上才能实现插入
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->left;
}
else
{
return false;//不允许与树中相同的数据再次插入
}
}
cur = new Node(key);
if (parent->_key < key)
{
parent->right = cur;
}
else
{
parent->left = cur;
}
return true;
}
注意(可以看上述代码实现):
- 在用递归法实现搜索二叉树的 Insert 时,必须要传引用才可以实现搜索二叉树的插入。
- 在用非递归实现搜索二叉树的 Insert 时,必须要先定义一个 parent 保存前一个结点,最后在链接上才能实现插入。
二叉搜索树的删除:
首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
- a.要删除的结点没有孩子结点
- b.要删除的结点只有左孩子结点
- c.要删除的结点只有右孩子结点
- d.要删除的结点既有右孩子结点也有左孩子结点
其实上面四种情况总体上来算就只有三种情况,因为 a 情况可以算是 b 或者 c 情况。则删除方法只有三种分别为:
-
b.删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点 :
其中又有两种情况:
①:被删除节点的父亲结点的左孩子是该删除结点(比如下图要删除 1 )
②:被删除节点的父亲结点的右孩子是该删除结点(比如下图删除 7 )
此外,还应该注意,刚开始要判断删除的结点的父亲是否为空,防止根节点的右孩子为空且要删除根节点的时候,后面对 parent 操作会出错,比如 要删除 5, 5 是根节点,此时就应该直接 将 3 作为根节点(后附代码):
else if (cur->right == nullptr)
{
if (parent == nullptr)//这里要判断删除的结点的父亲是否为空,防止根节点的右孩子为空且要删除根节点的时候,后面对 parent 操作会出错
{
_root = cur->left;
}
else
{
if (parent->left == cur)
{
parent->left = cur->left;
}
else
{
parent->right = cur->left;
}
}
}
- **c.删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点 **
其中又有两种情况:
①:被删除节点的父亲结点的左孩子是该删除结点(比如下图要删除 1 )
②:被删除节点的父亲结点的右孩子是该删除结点(比如下图删除 7 )
此外,还应该注意,刚开始要判断删除的结点的父亲是否为空,防止根节点的左孩子为空且要删除根节点的时候,后面对 parent 操作会出错,比如 要删除 5, 5 是根节点,此时就应该直接 将 7 作为根节点(后附代码):
if (cur->left == nullptr)
{
if (parent == nullptr)//这里要判断删除的结点的父亲是否为空,防止根节点的左孩子为空且要删除根节点的时候,后面对 parent 操作会出错
{
_root = cur->right;
}
else
{
if (parent->left == cur)
{
parent->left = cur->right;
}
else
{
parent->right = cur->right;
}
}
}
-
d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中, 再来处理该结点的删除问题
①:用来替换应该删除结点的结点没有右孩子(比如下图要删除 5 ,现在用 6 来替换 5, 6 没有右孩子)
②:用来替换应该删除结点的结点有右孩子(比如下图要删除 5,现在用 6 来替换 6, 6 有右孩子)
此外,还应该注意,刚开始的 lessParent 不能初始化为空,如果循环不进去,后面再对 lessParent 的各项操作就会出错(后附代码),比如下图这种情况就会导致该情况发生,用 7 来替换 5 ,7 并无左孩子,所以循环不会进去,就会导致 lessParent 一直为 nullptr:
代码:
Node* lessParent = cur;//这里的 lessParent 不能为初始化为空,应该考虑循坏如果不进去的时候就会出错
Node* lessRight = cur->right;
while (lessRight->left)
{
lessParent = lessRight;
lessRight = lessRight->left;
}
cur->_key = lessRight->_key;
del = lessRight;
if (lessParent->left == lessRight)//用来替换应该删除结点的结点可能还有右孩子
{
lessParent->left = lessRight->right;
}
else
{
lessParent->right = lessRight->right;
}
整个实现完整源代码:
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* left;
BSTreeNode<K>* right;
K _key;
BSTreeNode(const K& key)
:_key(key)
, left(nullptr)
, right(nullptr)
{}
};
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
BSTree()
:_root(nullptr)
{}
//递归插入
bool _InsertR(Node*& root, const K& key)//加上引用才可以插入进去
{
if (root == nullptr)
{
root = new Node(key);
}
if (root->_key < key)
{
return _InsertR(root->right, key);
}
else if (root->_key > key)
{
return _InsertR(root->left, key);
}
else
{
return false;
}
}
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
//非递归插入
bool Insert(const K& key)
{
if (_root == nullptr)//无结点时
{
_root = new Node(key);
return true;
}
Node* cur = _root;
Node* parent = nullptr;//注意要在这里定义一个parent保存前一个结点,最后在连接上才能实现插入
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->left;
}
else
{
return false;//不允许与树中相同的数据再次插入
}
}
cur = new Node(key);
if (parent->_key < key)
{
parent->right = cur;
}
else
{
parent->left = cur;
}
return true;
}
//中序遍历
void InOrder()//调用递归时候,需要再次定义一个_InOrder(),因为对象在类外面无法传参
{
_InOrder(_root);
cout << endl;
}
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->left);
cout << root->_key << " ";
_InOrder(root->right);
}
//非递归查找
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->right;
}
else if (cur->_key > key)
{
cur = cur->left;
}
else
{
return cur;
}
}
return nullptr;
}
//递归查找
Node* _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return nullptr;
}
if (root->_key == key)
{
return root;
}
else if (root->_key < key)
{
return _FindR(root->right, key);
}
else if (root->_key > key)
{
return _FindR(root->left, key);
}
}
Node* FindR(const K& key)
{
return _FindR(_root, key);
}
//删除
bool erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->left;
}
else
{
Node* del = cur;
//1.左为空(里面也包含两种小情况)
if (cur->left == nullptr)
{
if (parent == nullptr)//这里要判断删除的结点的父亲是否为空,防止根节点的左孩子为空且要删除根节点的时候,后面对 parent 操作会出错
{
_root = cur->right;
}
else
{
if (parent->left == cur)
{
parent->left = cur->right;
}
else
{
parent->right = cur->right;
}
}
}
//2.右为空(里面也包含两种小情况)
else if (cur->right == nullptr)
{
if (parent == nullptr)//这里要判断删除的结点的父亲是否为空,防止根节点的右孩子为空且要删除根节点的时候,后面对 parent 操作会出错
{
_root = cur->left;
}
else
{
if (parent->left == cur)
{
parent->left = cur->left;
}
else
{
parent->right = cur->left;
}
}
}
//3.左右都不为空
else
{
Node* lessParent = cur;//这里的 lessParent 不能为初始化为空,应该考虑循坏如果不进去的时候就会出错
Node* lessRight = cur->right;
while (lessRight->left)
{
lessParent = lessRight;
lessRight = lessRight->left;
}
cur->_key = lessRight->_key;
del = lessRight;
if (lessParent->left == lessRight)//用来替换应该删除结点的结点可能还有右孩子
{
lessParent->left = lessRight->right;
}
else
{
lessParent->right = lessRight->right;
}
}
delete del;
return true;
}
}
return false;
}
private:
Node* _root;
};