CD66.【C++ Dev】模拟实现二叉搜索树的递归版本

目录

1.知识回顾

2.递归查找函数find_r

3.递归插入函数insert_r

完整代码

★为什么不需要找父节点

如果是空树

如果是非空树

4.递归删除函数erase_r

先找节点

再删除

需要删除的节点的左树为空

需要删除的节点的右树为空

需要删除的节点的左树和右树都不为空

完整代码

4.析构函数

5.拷贝构造函数

6.重载operator=

7.二叉搜索树的应用


1.知识回顾

CC40.【C++ Cont】二叉搜索树和平衡二叉树

CD66.【C++ Dev】模拟实现二叉搜索树的非递归版本

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;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhangcoder

赠人玫瑰手有余香,感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值