二叉搜索树又称为二叉排序树,它可能是一棵空树,亦或是一棵具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有结点的值都小于根结点的值。
- 若它的右子树不为空,则右子树上所有结点的值都大于根结点的值。
- 它的左右子树也分别为二叉搜索树。
二叉搜索树的操作
1.二叉搜索树的查找
实现思路:
若结点不为空:
如果结点的值key == 查找的key,返回该结点;
如果结点的值key > 查找的key,在其左子树去查找;
如果结点的值key < 查找的key,在其右子树去查找。
如果遍历了整棵树都没找到就返回nullptr。
2.二叉搜索树的插入
实现思路:
如果树为空,直接插入,然后返回true,
如果树不为空,按二叉搜索树性质查找要插入的位置,然后插入新结点。
3.二叉搜索树的删除(重、难点)
1)如果树为空,删除失败,返回false;
2)树不为空,查找要删除元素的位置,如果此元素不在树中,则返回false;否则要删除的结点分为下面三种情况:
A.要删除的结点只有左孩子
a.如果该结点是树的根,更新根结点(_root = _root->_left)
b.否则该结点是子树,如果该结点在parent->_left,则parent->_left = cur->_left;如果该结点在parent->_right,则parent->_right = cur->_left
B.要删除的结点只有右孩子
a.如果该结点是树的根,更新根结点(_root = _root->_right)
b.否则该结点是子树,如果该结点在parent->_right,则parent->_right = cur->right;如果该结点在parent->_right,则parent->_left = cur->_right
C.要删除的结点左右孩子都存在
此情况直接删除不好删,可以在其子树中找一个替代结点,比如找其左子树中的最大结点,即左子树中最右侧的结点,或者找其右子树中最小的结点,即右子树中最小的结点。替换结点找到后,将替代结点中的值交给待删除结点,转换成删除替代结点。具体代码见下面。
BinarySearchTree.h
#pragma once
//二叉搜索数的模拟实现
#include<iostream>
using std::cout;
using std::endl;
template<class T>
struct BSTNode
{
BSTNode(const T& key = T())
: _left(nullptr)
, _right(nullptr)
, _key(key)
{}
BSTNode<T>* _left;
BSTNode<T>* _right;
T _key;
};
template<class T>
class BSTree
{
typedef BSTNode<T> Node;
public:
BSTree()
: _root(nullptr)
{}
~BSTree()
{
Destroy(_root);
}
//拷贝构造
BSTree(const BSTree<T>& tree)
{
_root = Copy(tree._root);
}
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* tmp = new Node;
tmp->_key = root->_key;
tmp->_left = Copy(root->_left);
tmp->_right = Copy(root->_right);
return tmp;
}
BSTree& operator=(const BSTree& tree)
{
if (this != &tree)
{
Destroy(this->_root);
this->_root = Copy(tree._root);
}
return *this;
}
bool Insert(const T& key)
{
//如果树为空,直接创建一个新结点进行插入
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
//查找要插入的位置
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
parent = cur;
if (key < cur->_key)
{
cur = cur->_left;
}
else if (key > cur->_key)
{
cur = cur->_right;
}
else
{
return false;//元素在树中已经存在,不用重新插入
}
}
//插入元素
cur = new Node(key);
if (key < parent->_key)
{
parent->_left = cur;
}
else
{
parent->_right = cur;
}
return true;
}
Node* Find(const T& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key == key)
{
return cur;
}
else if (key < cur->_key)
{
cur = cur->_left;
}
else
{
cur = cur->_right;
}
}
return nullptr;
}
bool Erase(const T& key)
{
//如果树为空,删除失败
if (_root == nullptr)
{
return false;
}
//查找key在树中的位置
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (key == cur->_key)
{
break;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
else
{
parent = cur;
cur = cur->_right;
}
}
//遍历了整棵树,如果key不在树中,无法删除
if (cur == nullptr)
{
return false;
}
//如果在树中找到了key,进行删除结点,要分三种情况:
//1.该结点只有右孩子
//2.该结点只有左孩子
//3.该结点左右子树都存在
if (cur->_left == nullptr)
{
//情况1:
//只有根结点和根的右孩子,此时要删除的结点正好是树的根
if (cur == _root)
{
_root = cur->_right;
}
else
{
//或该结点不是树的根
if (cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
}
//情况2:
else if (cur->_right == nullptr)
{
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (cur == parent->_right)
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
}
}
else
{
//当前结点左右孩子都存在,直接删除不好删,可以在其子树中找一个替代结点,比如找其左子树中的最大结点,即左子树中最右侧的结点,或者找其右子树中最小的结点,即右子树中最小的结点。替换结点找到后,将替代结点中的值交给待删除结点,转换成删除替代结点。
if (cur->_left != nullptr || cur->_right != nullptr)
{
//找右子树中最小的结点替换待删除的结点
Node* repalce = cur->_right;
parent = cur;
while (repalce->_left)
{
parent = repalce;
repalce = repalce->_left;
}
cur->_key = repalce->_key;
if (repalce == parent->_left)
{
parent->_left = repalce->_right;
}
else
{
parent->_right = repalce->_right;
}
delete repalce;
repalce = nullptr;
}
return true;
}
return false;
}
void Inorder()
{
_Inorder(_root);
}
private:
void _Inorder(Node* root)
{
if (root)
{
_Inorder(root->_left);
cout << root->_key << " ";
_Inorder(root->_right);
}
}
void Destroy(Node*& root)
{
if (root)
{
Destroy(root->_left);
Destroy(root->_right);
root = nullptr;
}
}
private:
Node* _root;
};
void TestBSTree()
{
BSTree<int> BST1;
BST1.Insert(5);
BST1.Insert(3);
BST1.Insert(4);
BST1.Insert(1);
BST1.Insert(7);
BST1.Insert(8);
BST1.Insert(2);
BST1.Insert(6);
BST1.Insert(0);
BST1.Insert(9);
BST1.Inorder();
cout << endl;
BST1.Find(10);
BST1.Erase(7);
BST1.Inorder();
cout << endl;
BSTree<int> copy(BST1);
BSTree<int> BST2;
BST2 = BST1;
copy.Inorder();
cout << endl;
BST2.Inorder();
}
test.cpp
#include"BinarySearchTree.h"
int main()
{
TestBSTree();
return 0;
}
二叉搜索树的性能分析
二叉搜索树的插入和删除都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,如每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,比较次数越多
在最优的情况下,二叉搜索树为完全二叉树,其平均比较次数为logN,在最差情况下,二叉搜索树退化为单支树,其平均比较次数为N。
如果退化成单支树,二叉搜索树的性能就失去了,那能否改进,不论按照什么次序插入关键码,都可以使二叉搜索树的性能最佳呢?,这里就引入了AVL树。AVL树的详细描述请看下一节博客。