目录
1.知识回顾
2.递归查找函数find_r
递归将大问题转化为子问题,如果根节点的值不是想要的,就到左子树或者右子树中查找
递归需要写子函数_find_r,find_r会调用_find_r,显然子函数应该私有
查找节点有两种情况,找到或找不到
找不到即root为空指针,返回false
找到即root->key == val,返回true
public:
bool find_r(const K& val)
{
return _find_r(root, val);
}
private:
bool _find_r(Node<K>* root, const K& val)
{
if (root == nullptr)
return false;
if (root->key < val)
_find_r(root->right, val);
else if (root->key > val)
_find_r(root->left, val);
else//root->key == val
return true;
}
测试代码:
#include "BinarySearchTree.h"
int main()
{
Binary_Search_Tree<int> bst;
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
for (auto e : a)
{
bst.insert(e);
}
cout << bst.find_r(7) << endl;
return 0;
}
运行结果:
3.递归插入函数insert_r
和find_r函数一样,也需要私有的_insert_r子函数,如果传入Node<K>* root需要找父节点,但如果用Node<K>*& root不需要找父节点,因为是引用
递归插入,变成一个一个的子问题(向左子树插入或者右子树插入),终止条件写在最前面
完整代码
public:
bool insert_r(const K& val)
{
return _insert_r(root, val);
}
private:
bool _insert_r(Node<K>*& root,const K& val)
{
if (root == nullptr)
{
root = new Node<K>(val);
return true;
}
if (root->key < val)
{
_insert_r(root->right, val)
}
else if (root->key > val)
{
_insert_r(root->left, val);
}
else
{
return false;
}
}
测试代码:
#include "BinarySearchTree.h"
int main()
{
Binary_Search_Tree<int> bst;
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
for (auto e : a)
{
bst.insert_r(e);
}
bst.print_inoder();
return 0;
}
运行结果:
★为什么不需要找父节点
如果是空树
满足root == nullptr,构造一个节点就返回true了
如果是非空树
比如要插入3,则val==3
再次强调引用的好处:
4.递归删除函数erase_r
和find_r函数一样,也需要私有的_erase_r子函数,如果传入Node<K>* root需要找父节点,但如果用Node<K>*& root不需要找父节点,因为是引用
和insert_r一样,递归到左子树或右子树去查找要删除的节点,但注意分类讨论:1.能找到-->讨论其有几棵子树 2.不能找到
先找节点
if (root->key < val)
{
_erase_r(root->right, val);
}
else if (root->key > val)
{
_erase_r(root->left, val);
}
else //root->key == val
{
//删除
}
再删除
需要删除的节点的左树为空
由于root是父指针的别名,因此可以直接修改
if (root->left == nullptr)
{
root = root->right;
}
注:如果该树只有一个节点,也是能删除的
需要删除的节点的右树为空
由于root是父指针的别名,因此可以直接修改
else if (root->right == nullptr)
{
root = root->left;
}
需要删除的节点的左树和右树都不为空
例如删除下图的5:
首先递归找到5,找到了,root为3的右指针的引用:
发现5有左右两棵子树,查到5左树的最大节点为4(注:left_max是root->left的拷贝)
交换left_max和root的key:
然后递归删除left_max指向的节点,即返回return _erase_r(root->left, val);
完整代码
bool _erase_r(Node<K>*& root, const K& val)
{
if (root == nullptr)
return false;
if (root->key < val)
{
_erase_r(root->right, val);
}
else if (root->key > val)
{
_erase_r(root->left, val);
}
else //root->key == val
{
Node<K>* del = root;
if (root->left == nullptr)//需要删除的节点的左树为空
{
root = root->right;
}
else if (root->right == nullptr)//需要删除的节点的右树为空
{
root = root->left;
}
else//需要删除的节点的左树和右树都不为空
{
Node<K>* left_max = root->left;
while (left_max->right)
{
left_max = left_max->right;
}
swap(root->key, left_max->key);
return _erase_r(root->left, val);
}
delete del;
return true;
}
}
注意不能写成return _erase_r(left_max,val);
见下面这个特殊情况:
要求删除8:
那么有:return _erase_r(left_max, val);
注意到left_max是局部变量,不是root->left的别名
当执行return _erase_r(left_max, val)时,设局部变量ptr为局部变量left_max的别名
ptr的右树为空,执行ptr = ptr -> left
会发现ptr改变了指向但并没有改掉二叉树,本质原因: 在erase ptr时,ptr的改变本质上改变的是上层递归的left_max,而left_max是局部变量,改变的是这个局部变量left_max,并没影响树的结构
测试代码:
#include "BinarySearchTree.h"
int main()
{
Binary_Search_Tree<int> bst;
int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
for (auto e : a)
{
bst.insert_r(e);
}
bst.erase_r(7);
bst.print_inoder();
bst.erase_r(3);
bst.print_inoder();
return 0;
}
运行结果:
4.析构函数
析构函数需要调用函数,因为需要传指向根节点的指针
~Binary_Search_Tree()
{
destory(root);
}
在destory函数中递归删除所有节点即可:
void destory(Node<K>* root)
{
if (root == nullptr)
return;
destory(root->left);
destory(root->right);
delete root;
root = nullptr;
}
5.拷贝构造函数
1.不能使用默认提供的拷贝构造函数,因为是浅拷贝
2.也不能调用插入函数,因为相同二叉搜索树的节点的插入顺序必须相同
拷贝构造函数也数需要调用函数,因为需要传指向根节点的指针
Binary_Search_Tree(const Binary_Search_Tree<K>& bst)
{
root = copy(bst.root);
}
在copy函数中递归拷贝所有节点,例如按前序遍历:
//一定防止x->left和x->right为nullptr的情况
//否则下次调用copy会导致new Node<K>(x->key)为new Node<K>(nullptr->key),这是非法的
Node<K>* copy(const Node<K>* x)
{
Node<K>* copy_root = new Node<K>(x->key);
if (x->left)
copy_root->left = copy(x->left);
if (x->right)
copy_root->right = copy(x->right);
return copy_root;
}
测试代码:
#include "BinarySearchTree.h"
int main()
{
Binary_Search_Tree<int> bst;
int a[] = {8,3,1,10,14};
for (auto e : a)
{
bst.insert_r(e);
}
bst.print_inoder();
Binary_Search_Tree<int> copy_bst(bst);
copy_bst.print_inoder();
return 0;
}
运行结果:
6.重载operator=
使用现代写法,调用拷贝构造函数
Binary_Search_Tree<K>& operator=(Binary_Search_Tree<K> tmp)
{
swap(root, tmp.root);
return *this;
}
7.二叉搜索树的应用
1.key的搜索模型:可快速判断key是否存在,例如检查单词是否拼写正确:先将所有正确的单词存储到二叉搜索树中,然后再遍历查找是否有某个单词
2.key和value的匹配模型:通过key匹配value
key-value匹配的第一种写法:
template<class K,class V>
class Node
{
public:
Node(const K& key,const V& val)
:left(nullptr)
, right(nullptr)
,_key(key)
,_val(val)
{
}
Node<K,V>* left;
Node<K,V>* right;
K _key;
V _val;
};
key-value匹配的第二种写法:
#include <utility>
template<class K, class V>
class Node
{
public:
Node(const K& key, const V& val)
:left(nullptr)
, right(nullptr)
, pack({ key,val })
{
}
Node < K, V > * left;
Node<K, V>* right;
std::pair<K, V> pack;
};