二叉搜索树的概念
二叉搜索树也称为二叉排序树,可以是空树,不是空树时有以下特性。
- 当它的左子树不为空时,左子树所有节点的值都小于根节点的值。
- 当它的右子树不为空时,右子树所有节点的值都大于根节点的值。
- 所有子树也都是二叉排序树。
概念比较简单下面详细讲它的实现。
二叉搜索树的实现
首先它的节点结构跟普通的二叉树没什么区别,下面定义节点
template<class K>
struct BtreeNode
{
BtreeNode(const K&val) //构造函数用来在二叉排序树种插入新节点
:left(nullptr)
,right(nullptr)
,_val(val)
{}
BtreeNode<K>*left;
BtreeNode<K>*right;
K _val;
}
接着先写出二叉排序树的整体框架
template<class K>
class BSTree
{
typedef BtreeNode<K> Node //重命名一下节点类型
public: //这里写一下公共接口,来使用排序树,有插入操作、查找操作、删除操作、再写一个中序遍历,二叉排序树就基本成形了
BSTree() //默认构造函数 初始状态是一个空树
:_root(nullptr)
{}
bool insert(const K& val);//二叉排序树的插入
void midprint() //中序遍历 需要用到根节点指针 所以再封装一个函数
{
_midprint(_root); //外面不需要调用到这个函数,所以直接定义成私有的
cout << endl;
}
bool find(const K& val);//二叉排序树的查找,找到了返回true 没找到返回false
bool Erase(const K& val); //二叉排序树的删除,先找到要删除节点然后删除,成功了返回true,没找到要删除节点就返回false
//下面再写一下递归版的插入、递归版的查找、递归版的删除。
bool insertR(const K& val) //递归版插入
{
return _insertR(_root, val);
}
Node* findR(const K& val) //递归版查找
{
if (_root == nullptr)
return nullptr;
return _findR(_root, val);
}
bool EraseR(const K& val) //递归版删除
{
return _EraseR(_root, val);
}
private:
Node* _root //类中需要的成员变量就只有根节点指针,就可以控制二叉排序树
//把递归插入、查找和删除都声明在私有作用域下
bool _insertR(Node*& root, const K& val);
Node* _findR(Node* cur, const K& val);
bool _EraseR(Node* &cur, const K& val);
void _midprint(Node*cur) //中序遍历的具体实现
{
if (cur == nullptr)
return;
_midprint(cur->left);
cout << cur->_val << " ";
_midprint(cur->right);
}
}
首先看插入节点的思路,要插入一个值,先定义一个父节点和一个当前节点,父节点置空,把根节点地址赋值给当前节点,即从根节点开始找我们要插入的值要插到树的那个位置,所有对树的操作都要保持树依旧是二叉排序树,比较要插入的值和当前节点值的大小,大于当前节点值,就去右子树继续找要插入的位置,小于当前节点值就去左子树继续找插入的位置。
BSTree::bool insert(const K& val)
{
if (_root == nullptr) //先判断是否是空树,是的话就直接让根节点指针指向插入的节点
{
_root = new Node(val);
return true;
}
else
{
Node * parent=nullptr, *cur = _root;
while (cur) //这里就是开始找要插入的位置
{
if (val < cur->_val)
{
parent = cur;
cur = cur->left;
}
else if (val > cur->_val)
{
parent = cur;
cur = cur->right;
}
else //如果找到一个节点的值和我们要插入的值一样,就不要插入了 返回false
return false;
}
cur = new Node(val); //这里已经找到了要插入的位置也把上一个节点地址记录再parent中
if (val < parent->_val) //这里就判断插入节点是连在上一个节点的左边还是右边
parent->left = cur;
else
parent->right = cur;
return true;
}
}
二叉树的查找也比较简单,从跟节点开始比较节点值和要查找的值,相等就找到了,大于当前节点值,就去它的右子树上继续找,小于就去左子树找。
BSTree::bool find(const K& val)
{
Node* cur = _root;
while (cur) //从根节点到叶子节点还没找到,就是没有这个值了
{
if (val < cur->_val)
cur = cur->left;
else if (val > cur->val)
cur = cur->right;
else
return true;
}
return false;
}
要删除一个值,首先要找到它在树中的位置,它的位置有几种情况,删除操作就有所不同
- 删除节点是叶子节点,就直接将它父节点连接它的那根指针置空,释放这个节点
- 删除节点只有左孩子,在释放这个节点前要将它的左孩子连接到它父节点连接它的那个位置上去。
- 删除节点只有右孩子,在释放这个节点前要将它的右孩子连接到它父节点连接它的那个位置上去。
- 删除节点左右孩子都存在,要用到替换法删除,从要删除节点出发,去左子树找到最右端的节点,或去右子树找到最左端节点,把找到的这个节点的值赋给要删除节点,再去释放这个找到的最右端或最左端的节点,这就变相的等同于删除了要删除节点,最右端或最左端的节点都是符合前面三种情况的节点,按前面的删除方式删除。
对于删除节点是叶子节点的情况可以和第二种或第三种情况等同处理,就是把空指针赋值给了父节点的左右指针,效果一样。
BSTree:: bool Erase(const K& val)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (val < cur->_val)
{
parent = cur;
cur = cur->left;
}
else if (val > cur->_val)
{
parent = cur;
cur = cur->right;
}
else
{//val==cur->_val 找到了要删除的值 准备删除
if (cur->left == nullptr)
{
if (parent == nullptr) //cur 就是头节点的情况
{
_root = cur->right;
}
else
{
if (parent->_val < cur->_val)
parent->right = cur->right;
else
parent->left = cur->right;
}
delete cur;
}
else if (cur->right == nullptr)
{
if (parent == nullptr)
_root = cur->left;
else
{
if (parent->_val < cur->_val)
parent->right = cur->left;
else
parent->left = cur->left;
}
delete cur;
}
else
{ //当所在节点左节点和右节点都不为空时,往下找左子树中的最右节点和它换位置,替换删除,或者往下找右子树的最左节点,替换删除这样都可以维持二叉搜索树的结构
Node* tail = cur->left;
parent = cur;
while (tail->right)
{
parent = tail;
tail = tail->right;
}
cur->_val = tail->_val;
if (parent->left == tail)
parent->left = tail->left;
else
parent->right = tail->left;
delete tail;
}
return true;
}
}
return false;
}
递归插入参数设置为Node* & ,用引用的方式就记录了父节点
bool _insertR(Node*& root, const K& val)
{
if (root == nullptr)
{
root = new Node(val); //这里创建了一个节点同时也把它赋值给了父节点的左指针或右指针,因为是用引用传参,root又是上一层root->left或root->right 或是根节点指针的引用,就直接连接上了
return true;
}
if (val < root->_val)
_insertR(root->left, val);
else if (val > root->_val)
_insertR(root->right, val);
else
return false;
}
递归版查找
//前面已经判断了根节点为空的情况
BSTree:: Node* _findR(Node* cur, const K& val)
{
if (cur->_val == val)
return cur;
else if (val < cur->_val && cur->left)
return _findR(cur->left, val);
else if (val > cur->_val && cur->right)
return _findR(cur->right, val);
else
return nullptr;
}
递归版删除
bool _EraseR(Node* &cur, const K& val)
{
if (cur == nullptr)
return false;
if (val < cur->_val)
return _EraseR(cur->left, val);
else if (val > cur->_val)
return _EraseR(cur->right, val);
else //找到要删除的值了
{
//1 下面有点偏常规处理了
/*if (cur->left == nullptr)
{
Node* tem = cur;
cur = cur->right;
delete tem;
}
else if (cur->right == nullptr)
{
Node* tem = cur;
cur = cur->left;
delete tem;
}
else
{
Node* tail = cur->left;
Node* parent = cur;
while (tail->right)
{
parent = tail;
tail = tail->right;
}
cur->_val = tail->_val;
if (parent->left == tail)
parent->left = tail->left;
else
parent->right = tail->left;
delete tail;
}*/
//2 充分利用递归
Node* del = cur;
if (cur->left == nullptr)
cur = cur->right; //也是因为引用传参的原因,这里直接将删除节点的右子树连接上了删除节点父节点本来连接删除节点的位置上去
else if (cur->right == nullptr)
cur = cur->left; // 同理
else
{ //处理左右孩子都有的情况
Node* tail = cur->left;
while (tail->right)
{
tail = tail->right;
}
swap(cur->_val, tail->_val); //这里替换删除
return _EraseR(cur->left, val); //去删除已经交换了值的节点达到删除的效果,删除完会直接return出去
}
delete del; //走到这里就是处理前面三种情况的删除节点
return true;
}
}