目录
1. find 的递归实现
find的递归实现较为简单,思路是:根据当前节点的 key 与传入的 key 作比较:
- 如果前者大于后者,那么当前节点往左子树走;
- 如果前者小于后者,那么当前节点往右子树走;
- 如果两者相等,返回true;
- 走到空,返回false。
代码实现:
// 对外提供的
bool find_recursion(const T& key)
{
return _find_recursion(_root, key);
}
// 类中私有的
bool _find_recursion(Node* root, const T& key)
{
if (root == nullptr)
return false;
else
{
if (root->_key < key)
return _find_recursion(root->_right, key);
else if (root->_key > key)
return _find_recursion(root->_left, key);
else
return true;
}
}
2. insert 的递归实现
insert分为两个过程
- 第一个过程:找到合适位置;
- 第二个过程:构建新节点,并完成连接关系。
假如现在我们已经得到了合适的插入位置,那么如何连接呢?
例如,如下图所示:我们要插入13这个数据,现在的关键问题是,如何将15和13这两个节点连接起来呢? 具体如下:
第一种方法:调用函数时,将父亲节点即这里的15也传进来。找到合适位置,创建节点并连接。
但是我们在这里提出一个较好玩的玩法,利用引用传参,如下所示:
// 对外提供的
bool insert_recursion(const T& key)
{
return _insert_recursion(_root, key);
}
// 类中私有的
bool _insert_recursion(Node*& root, const T& key)
{
if (root == nullptr)
{
// 走到空, 说明找到了目标位置, 需要构建新节点, 并完成连接关系
// 在这里, 我们用上图解释:
// root就是15这个节点的左孩子的引用,即root就是15的左孩子
// 给root new了一个node(key),等价于插入了这个节点,并连接了起来.
root = new Node(key);
return true;
}
else
{
if (root->_key < key)
return _insert_recursion(root->_right, key);
else if (root->_key > key)
return _insert_recursion(root->_left, key);
else
return false;
}
}
3. erase 的递归实现
对于erase的递归实现,其实也可以分为两个过程:
- 第一个过程:找到这个要删除的特殊节点;
- 第二个过程:可以分为三种情况(左孩子为空、右孩子为空、左右孩子都不为空),根据不同情况进行删除。
假设我们现在已经得到了要删除节点的位置,该如何删除呢?
3.1. 被删除的节点右孩子为空
如图所示:我们要删除6号节点(其右孩子为空),该如何删除:
由于 root 是4的右孩子的引用,且 root 的右孩子为空,那么root = root->_left,就可以将4的右孩子由6变更为5,我们在删除6即可,因此我们需要提前保存6节点,当指向变更之后,delete 6。
3.2. 被删除的节点左孩子为空
如图所示:我们要删除15号节点(其左孩子为空),该如何删除:
由于 root 是8的右孩子的引用,且 root 没有左孩子,那么我们此时只需要更改 root 即可,让 root 到它的右孩子 (root = root->_right),等价于将8连接了19,当然我们也需要提前将 root 节点进行保存,更改指向后,在释放 root 节点即可。
3.3. 被删除的节点左右孩子都不为空
较为复杂的就是第三种情况了,由于此时被删除节点有两个孩子,因此无法像上面两种情况进行处理。此时我们还是要利用循环实现的思路:
- (1):从被删除的节点开始,先找到左子树的最大节点or右子树的最小节点(我在这里称之为"合适节点");
- (2):交换这个"合适结点"和被删除节点的key;
- (3):将删除原节点转化为删除我们后找的这个"合适节点"。
在这里我们用实例说明,如下图所示:如果我要删除下图中的4,该如何删除?
我在这里实现的"合适节点"是: 左子树的最大(右)节点
相信前两个过程是没有困难的,最后一步可能不好实现,但是当我们经过了前两个过程,我们发现被删除节点变成了我们找到的"合适节点",而且这个"合适节点"很有特征,如果它是左子树的最大值,那么它一定不会有右子树,反之,如果他是右子树的最小节点,那么它一定不会有左子树。因此我们可以在递归一次,如果"合适节点"是左子树的最大节点,那么我们递归树的左子树即可,反之如果是右子树的最小节点,那么我们递归树的右子树即可。
代码如下:
// 对外提供的
bool erase_recursion(const T& key)
{
return _erase_recursion(_root, key);
}
// 类中私有的
bool _erase_recursion(Node*& root, const T& key)
{
if (!root)
return false;
else
{
// 如果当前节点的key > 目标key,那么递归它的左子树即可
if (root->_key > key)
return _erase_recursion(root->_left, key);
// 如果当前节点的key < 目标key,那么递归它的右子树即可
else if (root->_key < key)
return _erase_recursion(root->_right, key);
// 如果找到了,进行删除
else
{
// 此时的root就是要删除的节点
Node* del = root;
// a. 左子树为空
if (root->_left == nullptr)
root = root->_right;
// b. 右子树为空
else if (root->_right == nullptr)
root = root->_left;
// c. 左右子树都不为空
else
{
// 左子树的最右节点
Node* left_max = root->_left;
while (left_max->_right)
left_max = left_max->_right;
// 交换"合适节点"和"被删除节点"的key
std::swap(left_max->_key, root->_key);
// 在这里递归左子树即可
return _erase_recursion(root->_left, key);
}
delete del;
del = nullptr;
return true;
}
}
}
4. 析构函数的实现
析构函数的实现我们依据的是后序的思想(LRN),先析构左子树、然后是右子树、最后才是根。这种实现的原因是是少了许多的记录信息,例如在这里我们就不用记录下一个节点。因为我们释放的就是当前的叶子节点。
具体实现如下:
~BinarySearchTree()
{
_BSTDestroy(_root);
}
// 注意我们这里传递的是根的引用
void _BSTDestroy(Node*& root)
{
if (root == nullptr)
return;
else
{
// 依据后序的思想
_BSTDestroy(root->_left);
_BSTDestroy(root->_right);
delete root;
root = nullptr;
}
}
5. copy constructor的实现
老生常谈的问题,如果我们没有显示实现拷贝构造函数,那么编译器默认生成的拷贝构造会对内置类型按照字节序的方式进行拷贝,对自定义类型成员属性会去调用它的拷贝构造函数。而字节序的方式进行拷贝会带来两个问题:
- 其一,其中一个对象的修改会影响另一个对象;
- 其二,同一空间会被析构两次,进程crash。
因此,我们在这里必须要实现深拷贝,那如何实现呢?我们可以借助前序的思想(NLR)。从根节点开始进行构造节点,然后递归构造它的左子树和右子树。注意构造的时候需要它们的连接关系。
代码如下:
BinarySearchTree(const BinarySearchTree<T>& copy)
{
_root = _creat_new_root(copy._root);
}
Node* _creat_new_root(Node* root)
{
// 如果遇到空了,就不用构造了
if (root == nullptr)
return nullptr;
else
{
// 根据前序的思想(NLR),依次构造它的根、左子树、右子树
// 同时将它们连接起来
Node* new_root = new Node(root->_key);
new_root->_left = _creat_new_root(root->_left);
new_root->_right = _creat_new_root(root->_right);
return new_root;
}
}
6. 赋值运算符重载
赋值运算符重载就比较简单了,因为我们已经实现了copy constructor,在这里利用传值传参会进行拷贝构造的特性实现我们的赋值
代码如下:
// 传值传参会进行拷贝构造
BinarySearchTree<T>& operator=(BinarySearchTree<T> copy)
{
std::swap(_root, copy._root);
return *this;
}
7. 搜索二叉树的完整实现
代码如下:
#ifndef _BINARY_SEARCH_TREE_HPP_
#define _BINARY_SEARCH_TREE_HPP_
#include <iostream>
namespace Xq
{
template<class T>
struct BinarySearchTreeNode
{
BinarySearchTreeNode<T>* _left;
BinarySearchTreeNode<T>* _right;
T _key;
BinarySearchTreeNode(const T& key) :_key(key), _left(nullptr), _right(nullptr) {}
};
template<class T>
class BinarySearchTree
{
private:
typedef BinarySearchTreeNode<T> Node;
public:
BinarySearchTree(Node* root = nullptr) :_root(root) {}
bool insert(const T& key)
{
// 1. 如果是空树,直接对_root赋值即可,插入成功并返回true
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
else
{
// step 1: 先找目标位置
Node* cur = _root;
// 为了更好的连接新节点, 因此记录父节点
Node* parent = nullptr;
while (cur)
{
// 如果当前节点的Key大于目标Key
// 当前节点应该向左子树走
if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
// 如果当前节点的Key小于目标Key
// 当前节点应该向右子树走
else if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else
{
// 找到了相同的 key, 在这里不插入
return false;
}
}
// cur 走到了空, 即 cur 就是合适的位置
cur = new Node(key);
// 我们需要判断cur是parent的左节点还是右节点
// 如果key小于parent的key,那么插入左节点
if (key < parent->_key)
parent->_left = cur;
// 反之连接到右节点
else
parent->_right = cur;
return true;
}
}
bool find(const T& key)
{
// 1. 从根节点开始
Node* cur = _root;
while (cur)
{
// 2. 如果当前关键字大于目标关键字,那么向左子树走
if (cur->_key > key)
cur = cur->_left;
// 3. 如果小于目标关键字,那么向右子树走
else if (cur->_key < key)
cur = cur->_right;
// 4. 相等,就返回true
else
return true;
}
// 5. 循环结束,说明没找到, 返回false
return false;
}
bool erase(const T& key)
{
// 先找要删除的节点
Node* del = _root;
Node* del_parent = nullptr;
while (del)
{
if (del->_key < key)
{
del_parent = del;
del = del->_right;
}
else if (del->_key > key)
{
del_parent = del;
del = del->_left;
}
else
{
// 锁定了要删除的节点
// 分三种情况:
// case 1: 左子树为空
if (del->_left == nullptr)
{
// 如果要删除的节点是根
if (del == _root)
{
Node* newroot = del->_right;
delete _root;
_root = newroot;
}
else
{
// 托孤法删除
if (del_parent->_left == del)
del_parent->_left = del->_right;
else
del_parent->_right = del->_right;
delete del;
del = nullptr;
}
}
// case 2: 右子树为空
else if (del->_right == nullptr)
{
if (_root == del)
{
Node* newroot = del->_left;
delete _root;
_root = newroot;
}
else
{
if (del_parent->_left == del)
del_parent->_left = del->_left;
else
del_parent->_right = del->_left;
delete del;
del = nullptr;
}
}
// case 3: 左右子树都不为空
else
{
// 从被删除节点开始, 找右子树的最小(左)节点 || 找左子树的最大(右)节点
if (del->_right)
_erase_right_min_node(del);
else
_erase_left_max_node(del);
}
return true;
}
}
return false;
}
bool find_recursion(const T& key)
{
return _find_recursion(_root, key);
}
bool insert_recursion(const T& key)
{
return _insert_recursion(_root, key);
}
bool erase_recursion(const T& key)
{
return _erase_recursion(_root, key);
}
~BinarySearchTree()
{
_BSTDestroy(_root);
}
BinarySearchTree(const BinarySearchTree<T>& copy)
{
_root = _creat_new_root(copy._root);
}
// 传值传参会进行拷贝构造
BinarySearchTree<T>& operator=(BinarySearchTree<T> copy)
{
std::swap(_root, copy._root);
return *this;
}
void InOrder()
{
_InOrder(_root);
std::cout << std::endl;
}
private:
void _InOrder(Node* root)
{
if (root)
{
_InOrder(root->_left);
std::cout << root->_key << " ";
_InOrder(root->_right);
}
}
bool _find_recursion(Node* root, const T& key)
{
if (root == nullptr)
return false;
else
{
if (root->_key < key)
return _find_recursion(root->_right, key);
else if (root->_key > key)
_find_recursion(root->_left, key);
else
return true;
}
}
bool _insert_recursion(Node*& root, const T& key)
{
if (root == nullptr)
{
root = new Node(key);
return true;
}
else
{
if (root->_key < key)
return _insert_recursion(root->_right, key);
else if (root->_key > key)
return _insert_recursion(root->_left, key);
else
return false;
}
}
void _erase_right_min_node(Node* del)
{
// 从被删除结点开始, 找右子树的最小(左)节点
Node* right_min = del->_right;
// 并记录这个节点的父亲节点, 让其从del开始
Node* right_min_parent = del;
while (right_min->_left)
{
right_min_parent = right_min;
right_min = right_min->_left;
}
// 交换这个节点和要删除节点的 key
std::swap(del->_key, right_min->_key);
// 将删除 del 转化为删除 right_min (托孤法删除)
if (right_min_parent->_left == right_min)
right_min_parent->_left = right_min->_right;
else
right_min_parent->_right = right_min->_right;
delete right_min;
right_min = nullptr;
}
void _erase_left_max_node(Node* del)
{
// 从被删除节点开始, 找左子树的最大(右)节点
Node* left_max = del->_left;
// 并记录这个节点的父亲节点, 让其从del开始
Node* left_max_parent = del;
while (left_max->_right)
{
left_max_parent = left_max;
left_max = left_max->_right;
}
// 交换这个节点和要删除节点的 key
std::swap(del->_key, left_max->_key);
// 将删除 del 转化为删除 left_max (托孤法删除)
if (left_max_parent->_left == left_max)
left_max_parent->_left = left_max->_left;
else
left_max_parent->_right = left_max->_left;
delete left_max;
left_max = nullptr;
}
bool _erase_recursion(Node*& root, const T& key)
{
if (!root)
return false;
else
{
// 如果当前节点的key > 目标key,那么递归它的左子树即可
if (root->_key > key)
return _erase_recursion(root->_left, key);
// 如果当前节点的key < 目标key,那么递归它的右子树即可
else if (root->_key < key)
return _erase_recursion(root->_right, key);
// 如果找到了,进行删除
else
{
// 此时的root就是要删除的节点
Node* del = root;
// a. 左子树为空
if (root->_left == nullptr)
root = root->_right;
// b. 右子树为空
else if (root->_right == nullptr)
root = root->_left;
// c. 左右子树都不为空
else
{
// 左子树的最右节点
Node* left_max = root->_left;
while (left_max->_right)
left_max = left_max->_right;
// 交换"合适节点"和"被删除节点"的key
std::swap(left_max->_key, root->_key);
// 在这里递归左子树即可
return _erase_recursion(root->_left, key);
}
delete del;
del = nullptr;
return true;
}
}
}
Node* _creat_new_root(Node* root)
{
// 如果遇到空了,就不用构造了
if (root == nullptr)
return nullptr;
else
{
// 根据前序的思想(NLR),依次构造它的根、左子树、右子树
// 同时将它们连接起来
Node* new_root = new Node(root->_key);
new_root->_left = _creat_new_root(root->_left);
new_root->_right = _creat_new_root(root->_right);
return new_root;
}
}
// 注意我们这里传递的是根的引用
void _BSTDestroy(Node*& root)
{
if (root == nullptr)
return;
else
{
// 依据后序的思想
_BSTDestroy(root->_left);
_BSTDestroy(root->_right);
delete root;
root = nullptr;
}
}
private:
Node* _root;
};
}
#endif
二叉树进阶 --- 中,结束。