概念
二叉搜索树(BST - Binary Search Tree)是一种特殊的二叉树,每个顶点最多可以有两个子节点。其遵顼以下规则:
若它的左子树不为空,则左子树上所有节点的至都
小于
根节点的值
若它的右子树不为空,则右子树上所有节点的至都大于
根节点的值
它的左右子树也分别为二叉搜索树
其有两个特性:
- 查找数据非常快,每次查找数据,只需要将数据与当前节点比较,然后决定去左子树还是右子树找,如果最后找到了
nullptr
,那就是不存在。 - 二叉搜索树的中序遍历,得到值是有序的。正是因为二叉搜索树左子树的值都小于根,右子树的值都大于根。中序遍历是 左子树 - 根 - 右子树,所以最后得到的数据就是有序的
模拟实现
节点类
:
template<class K>
struct BSTreeNode
{typedef BSTreeNode<K> Node;Node* _left;Node* _right;K _key;BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){}
};
二叉搜索树类
:
template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
private:Node* _root = nullptr;
};
插入
想要对二叉搜索树进行节点插入,有两种情况:
- 节点值存在:此时不再进行插入
- 节点值不存在:进行插入
值得注意的是:如果某一个值不存在于二叉搜索树中,那么插入这个值一定是在nullptr
插入。
bool Insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(key);if (key < parent->_key)parent->_left = cur;elseparent->_right = cur;return true;
}
中序遍历
中序遍历,对于二叉树而言是一个比较简单的操作,我们看到以下代码:
void InOrder(Node* root)
{if (root == nullptr)return;InOrder(root->_left);cout << root->_key << " - ";InOrder(root->_right);
}
以上代码,先遍历左子树InOrder(root->_left);
,然后输出当前节点的值cout << root->_key << " - ";
,再遍历右子树InOrder(root->_right);
。是一个很常规的中序遍历,但是存在一个问题:这个函数外部没法传参。
比如说我要遍历某一个二叉搜索树bst
:
bst.InOrder(bst._root);
这个调用是存在问题的,那就是_root
是私有成员,外部不能把_root
作为参数传入中序遍历的接口。此时我们就需要再在外面套一层接口,像这样:
void InOrder()
{_InOrder(_root);
}void _InOrder(Node* root)
{if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " - ";_InOrder(root->_right);
}
我们给函数InOrder
创建了一个子函数_InOrder
,外部无需传参就可以调用InOrder
。我们将中序遍历的代码写在了子函数中,而在InOrder
内部,再去调用这个子函数 _InOrder(_root);
,就可以正常传参了。
查找
想要查找,基本逻辑就是:
当前节点的值小于
key
,到左子树查找
当前节点的值大于key
,到右子树查找
当前节点的值等于key
,返回true
如果查找到nullptr
,说明不存在,返回false
bool Find(const K& key)
{Node* cur = _root;while (cur){if (key < cur->_key)cur = cur->_left;else if (key > cur->_key)cur = cur->_right;elsereturn true;}return false;
}
删除
首先,既然要删除特定的节点,那么我们就要先查找到该节点。既然要修改该节点,那么也要查找到其父节点,这个思路与前面的插入接口非常相似。
只要被删除的节点,有一个子树为nullptr
,那么就可以将另外一个子树直接链接到其父节点下。
这里还要额外考虑根节点的情况
if (cur->_left == nullptr)
{if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;
}
else if (cur->_right == nullptr)
{if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;
}
else
{//其他情况
}
当待删除节点的左右子树都不为空
有两种解决方案:
- 取出左子树最大的值替换删除节点
- 取出右子树最小的值替换删除节点
取出节点后,我们把右子树最小节点rightMin
的值交给待删除节点的cur
,但是rightMin
还有可能有右子树,那么就要把右子树移交给rightMin
的父节点。
Node* rightMinParent = cur;
Node* rightMin = cur->_right;while (rightMin->_left)
{rightMinParent = rightMin;rightMin = rightMin->_left;
}cur->_key = rightMin->_key;if (rightMinParent->_left == rightMin)rightMinParent->_left == rightMin->_right;
elserightMinParent->_right == rightMin->_right;delete rightMin;
删除总代码:
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{if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (cur == parent->_left)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;}else{Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMin == rightMinParent->_left)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;delete rightMin;}return true;}}return false;
}
析构函数
想要删除整棵树,那就需要递归式地删除每一个节点,为了保证树的节点不会错乱,我们最好通过后序遍历删除,代码如下:
~BSTree()
{Destroy(_root);
}void Destroy(Node* root)
{if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;
}
拷贝构造
想要拷贝一棵树出来,我们也需要进行递归式的深拷贝,不过由于要先有父节点,再有子节点,所以要用前序遍历。代码如下:
BSTree(const BSTree<K>& t)
{_root = Copy(t._root);
}Node* Copy(Node* root)
{if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;
}
赋值重载
代码如下:
BSTree<K>& operator=(BSTree<K> t)
{swap(_root, t._root);return *this;
}
这是一个比较现代的赋值重载,注意我们在传参时BSTree<K> t不是一个引用,而是一个不同的参数,此时参数t是实参的一份拷贝,是通过拷贝构造创建的,然后我们把这个形参拷贝构造出来的树直接交换给自己: swap(_root, t._root);。这样就相当于通过拷贝构造,完成了一个赋值重载。
递归查找
bool FindR(const K& key)
{return _FindR(_root, key);
}bool _FindR(Node* root, const K& key)
{if (root == nullptr)return false;if (key < root->_key)return _FindR(root->_left, key);else if (key > root->_key)return _FindR(root->_right, key);elsereturn true;
}
递归插入
bool InsertR(const K& key)
{return _InsertR(_root, key);
}bool _InsertR(Node*& root, const K& key)
{if (root == nullptr){root = new Node(key);return true;}if (key < root->_key)return _InsertR(root->_left, key);else if (key > root->_key)return _InsertR(root->_right, key);elsereturn false;
}
递归删除
bool EraseR(const K& key)
{return _EraseR(_root, key);
}bool _EraseR(Node*& root, const K& key)
{if (root == nullptr)return false;if (key < root->_key){return _EraseR(root->_left, key);}else if (key > root->_key){return _EraseR(root->_right, key);}else{//删除节点}
}
通过递归找到删除节点后,要考虑两种情况:
- 待删除节点有一个子树为
nullptr
- 待删除节点拥有两个子树
先看到第一种:
Node* del = root;if (root->_left == nullptr)
{root = root->_right;
}
else if (root->_right == nullptr)
{root = root->_left;
}
else
{
}
delete del;
return true;
接下来我们再看到左右子树都不为nullptr
的情况:
else
{Node* rightMin = root->_right;while (rightMin->_left){rightMin = rightMin->_left;}swap(root->_key, rightMin->_key);return _EraseR(root->_right, key);
}
总代码展示
BinarySearchTree.h
:
#pragma once
#include <iostream>
using namespace std;template<class K>
struct BSTreeNode
{typedef BSTreeNode<K> Node;Node* _root;Node* _left;Node* _right;K _key;BSTreeNode(const K& key):_root(nullptr),_left(nullptr),_right(nullptr),_key(key){}
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:BSTree() = default;BSTree(const BSTree<K>& t){_root = Copy(t._root);}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}~BSTree(){Destroy(_root);}void Destroy(Node* root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(key);if (key < parent->_key)parent->_left = cur;elseparent->_right = cur;return true;}bool Erase(const K& key){Node* cur = _root;Node* parent = nullptr;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;}else{Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMinParent->_left == rightMin)rightMinParent->_left == rightMin->_right;elserightMinParent->_right == rightMin->_right;delete rightMin;}return true;}}return false;}bool Find(const K& key){Node* cur = _root;while (cur){if (key < cur->_key)cur = cur->_left;else if (key > cur->_key)cur = cur->_right;elsereturn true;}return false;}void InOrder(){_InOrder(_root);cout << "end" << endl;}bool FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseR(const K& key){return _EraseR(_root, key);}private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " - ";_InOrder(root->_right);}bool _FindR(Node* root, const K& key){if (root == nullptr)return false;if (key < root->_key)return _FindR(root->_left, key);else if (key > root->_key)return _FindR(root->_right, key);elsereturn true;}bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (key < root->_key)return _InsertR(root->_left, key);else if (key > root->_key)return _InsertR(root->_right, key);elsereturn false;}bool _EraseR(Node*& root, const K& key){if (root == nullptr)return false;if (key < root->_key){return _EraseR(root->_left, key);}else if (key > root->_key){return _EraseR(root->_right, key);}else{Node* del = root;if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{Node* rightMin = root->_right;while (rightMin->_left){rightMin = rightMin->_left;}swap(root->_key, rightMin->_key);return _EraseR(root->_right, key);}delete del;return true;}}Node* _root = nullptr;
};