本文主要讲解BST的insert和erase接口,语言使用C++
文章目录
二叉搜索树 Binary Search Tree (BST)
性质介绍
它或者是一棵空树,或者是具有以下性质的二叉树:
若左子树不为空,则左子树上所有节点的值都小于根节点的值
若右子树不为空,则右子树上所有节点的值都大于根节点的值
左右子树也分别为二叉搜索树
默认的搜索树是不允许数值冗余的,即键值不同
二叉树节点定义
template<class K>
struct TreeNode
{
private:
typedef TreeNode<K> self;
typedef self* NodePtr;
public:
K _key;
NodePtr _left = nullptr;
NodePtr _right = nullptr;
explicit TreeNode(const K& key = K()) //使用explict修饰,防止编译器隐式类型转换
: _key(key)
{};
};
template<class K>
class BinarySearchTree
{
typedef TreeNode<K> Node;
typedef Node* NodePtr;
typedef BinarySearchTree<K> self;
public:
bool insert(const K& key);
bool erase(const K& key);
bool find(const K& key);
//其余接口...
private:
NodePtr _root = nullptr;
};
find接口
二叉搜索树的查找规则
从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找
最多查找高度次,走到到空,还没找到,这个值不存在
bool find(const K& key) const
{
for (NodePtr cur = _root; cur != nullptr; )
{
if (key < cur->_key)
cur = cur->_left;
else if (key > cur->_key)
cur = cur->_right;
else
return cur;
}
return false;
}
insert接口
二叉搜索树的插入规则
树为空,则直接新增节点,赋值给root指针
树不空,按二叉搜索树性质查找插入位置,插入新节点
从根开始比较,比根大则往右走,比根小往左走,直至走到nullptr
若在树中找到了和插入值相同的值则返回false
//循环版本
bool insert(const K& key)
{
NodePtr* ptr = &_root; //使用指针进行指向,可以省去判断是双亲节点的哪个指针,以及_root为nullptr的情况
for (NodePtr cur = _root; cur != nullptr; cur = *ptr)
{
if (key < cur->_key) //比根小,往左走
ptr = &cur->_left;
else if (key > cur->_key) //比根大,往右走
ptr = &cur->_right;
else //和根相同,返回false
return false;
}
//*ptr始终是cur双亲节点中指向cur的指针,即双亲节点的左指针/右指针
*ptr = new Node(key);
return true;
}
//递归版本
bool Insert(const K& key)
{
return _Insert(_root, key);
}
//注意传入的是引用
bool _Insert(NodePtr& root, const K& key)
{
if (root == nullptr)
{
root = new Node(key); //可以直接new
return true;
}
if (root->_key == key)
return false;
else if (root->_key > key)
return _Insert(root->_left, key);
else
return _Insert(root->_right, key);
}
erase接口
二叉搜索树的删除规则
首先查找元素是否在二叉搜索树中,如果不存在,则返回false
该结点无孩子结点: 直接删除
只有左孩子结点: 删除该结点且使被删除节点的双亲0结点指向被删除节点的左孩子结点
只有右孩子结点: 删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点
有左,右孩子结点: 在它的右子树中寻找中序下的第一个结点(值最小),用它的值填补到被删除节点中,再来处理该结点的删除问题(替换法删除)
bool erase(const K& key) //放在public中
{
NodePtr* ptr = &_root;
for (NodePtr cur = _root; cur != nullptr; cur = *ptr)
{
if (key < cur->_key)
ptr = &cur->_left;
else if (key > cur->_key)
ptr = &cur->_right;
else
{
_erase(ptr); //先找到再删除
return true;
}
}
return false;
}
void _erase(NodePtr* ptr) //建议放在private中
{
NodePtr cur = *ptr; //同样使用指针法
if (cur->_left != nullptr && cur->_right != nullptr) //处理两个孩子都存在的情况
{
NodePtr rightMin = cur->_right;
//找到右孩子中最小的
for (ptr = &cur->_right; rightMin->_left != nullptr; ptr = &rightMin->_left, rightMin = *ptr);
cur->_key = rightMin->_key; //替换,被替换的节点最多有一个孩子
cur = *ptr;
}
//处理左右孩子有一个不为空或全为空
NodePtr noempty = cur->_left == nullptr ? cur->_right : cur->_left;
delete *ptr;
*ptr = noempty;
}
//递归版本无实现意义价值,递归查找,同样调用_erase接口
遍历
根据搜索二叉树的性质,我们可知,中序遍历这棵树可以得到一个升序的有序序列
因此可以,中序遍历这棵树,判断是否满足搜索二叉树的性质
//递归版本
void InOrder() const //放在public中
{
return _InOrder(_root);
}
void _InOrder(NodePtr root) const //建议放在private中
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << ' ';
_InOrder(root->_right);
}
//循环版本
vector<K> inorder() const
{
vector<K> ret;
stack<NodePtr> st;
NodePtr cur = _root;
while (cur || !st.empty())
{
while (cur)
{
st.push(cur);
cur = cur->left;
}
cur = st.top();
st.pop();
ret.push_back(cur->_key);
cur = cur->right;
}
return ret;
}
构造 & 拷贝构造 & 析构 & 运算符重载等接口
构造函数
BinarySearchTree()
{}
BinarySearchTree(const self&bst)
: _root(copy(bst._root))
{}
template<class InputIterator>
BinarySearchTree(InputIterator first, InputIterator last)
{
while (first != last)
{
insert(*first);
++first;
}
}
NodePtr copy(NodePtr root) //建议放在private中
{
if (root == nullptr)
return nullptr;
NodePtr ret = new Node(root->_key);
ret->_left = copy(root->_left);
ret->_right = copy(root->_right);
return ret;
}
析构函数
~BinarySearchTree()
{
//destory(); //性能很挫
Destory(_root);
}
bool empty() const
{
return _root == nullptr;
}
void destory() //建议放在private中
{
while (!empty())
erase(_root->_key);
}
void Destory(NodePtr& root) //建议放在private中
{
if (root == nullptr)
return;
Destory(root->_left);
Destory(root->_right);
delete root;
root = nullptr;
}
operator=重载
self& operator=(const self& bst)
{
if (&bst != this)
{
Destory(_root);
_root = copy(bst._root);
}
return *this;
}
self& operator=(self bst) //bst为调用拷贝构造形成的参数
{
std::swap(bst._root, _root);
return *this;
}
== and != 重载
bool operator==(const self& des) const
{
return compare(_root, des._root);
}
bool operator!=(const self& des) const
{
return !compare(_root, des._root);
}
bool compare(NodePtr des, NodePtr src) const //建议放在private中
{
if (des == nullptr && src == nullptr)
return true;
else if (des != nullptr && src != nullptr)
return des->_key == src->_key && compare(des->_left, src->_left) && compare(des->_right, src->_right);
else
return false;
}
size & hight & clear接口函数
size_t hight() const //放在public
{
return _hight(_root);
}
size_t size() const //放在public
{
return _size(_root);
}
void clear()
{
Destory(_root);
}
size_t _hight(NodePtr root) const //建议放在private
{
if (root == nullptr)
return 0;
int left = _hight(root->left);
int right = _hight(root->right);
return (size_t)1 + (left > right ? left : right);
}
size_t _size(NodePtr root) const //建议放在private
{
if (root == nullptr)
return 0;
return _size(root->_left) + _size(root->_right) + 1;
}
总结
二叉搜索树结构清晰明了,理解非常容易。只不过其insert,erase接口需要考虑和父节点以及子节点之间的关系,所以在操作上略有一些难度。但使用指针法,就无需考虑与双亲节点之间的关系了,整体难度降低了一个档次。
不过目前还没在网上看到有使用指针法来进行插入和删除操作的,大多是不断的判断,导致了学习的成本较高,看的让人眼花缭乱(那这也算是自创的算法了hhh)。