【C++进阶】二叉搜索树

文章目录

  • 二叉搜索树概念
  • 二叉搜索树操作
    • 1.二叉树结点类
    • 2.整体框架
    • 3.构造函数
    • 4.析构函数
    • 5.拷贝构造
    • 6.赋值重载
    • 7.find查找接口
    • 版本一:循环实现
      • 版本二:递归版本
    • 8. insert插入接口
      • 版本一:循环版本
      • 版本二:递归版本
    • 9.erase删除接口
      • 版本一:循环版本
      • 版本二:递归版本

二叉搜索树概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树

二叉搜索树又具有排序的特性,当中序遍历之后,我们会发现是顺序排列的,并且二叉搜索树不允许重复,所以可以说二叉搜索树是天生去重的。
【C++进阶】二叉搜索树_第1张图片

二叉搜索树操作

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

我们将上边数组内容逐一插入到二叉搜索树中,就可以得到下边这幅图,通过这幅图来探究二叉搜索树的相关操作:
【C++进阶】二叉搜索树_第2张图片

1.二叉树结点类

要实现搜索二叉树,就必须实现二叉树的结点类,在前边数据结构中,我们学习过,一个结点类中有该结点的值,左子树的指针,右子树的指针。
并且必须实现拷贝构造函数,在后边使用new来开辟一个对象时用到,来开辟一个指定值的结点。

template
struct node
{
	k _key;
	node* _left;
	node* _right;

//使用初始化列表初始化
	node(const k& key)
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{}
};

2.整体框架

我们使用模版来实现二叉搜索树,就可以实现泛型,在BStree中创建一个node* 类型的头结点。

template
struct node
{
	k _key;
	node* _left;
	node* _right;


	node(const k& key)
		:_key(key)
		,_left(nullptr)
		,_right(nullptr)
	{


	}
};
template
class BStree
{
public:
	typedef node node;

    //在中间实现各类接口
    
	private:
	//给一个默认值,可以不用写构造函数
	node* _root = nullptr;
};

3.构造函数

构造函数比较简单,如果给了默认值,就可以不实现构造函数,如果没有给默认值,就直接构造一颗空树。

//构造函数
BSTree()
	:_root(nullptr)
{}

4.析构函数

//析构函数
	~BStree()
	{
		_destory(_root);
	}
//进行递归销毁
	void _destory(node*& root)
	{
		if (root == nullptr)
			return;
		//后序进行销毁
		_destory(root->_left);
		_destory(root->_right);
		delete root;
		root = nullptr;
	}

5.拷贝构造

注意拷贝构造函数的参数必须引用传参,否则就会造成递归拷贝。若不实现拷贝构造,默认生成的实现的是浅拷贝。

//拷贝构造函数
	BStree(const BStree& copy)
	{
		_root = Copy(copy._root);
	}
//递归拷贝一颗树
	node* Copy(node* root)
	{
		if (root == nullptr)
			return nullptr;
		node* copyRoot = new node(root->_key);
		copyRoot->_left = Copy(root->_left);
		copyRoot->_right = Copy(root->_right);
		return copyRoot;
	}

6.赋值重载

在实现了拷贝构造之后,就可以通过现代写法来直接进行赋值重载

	BStree operator=(BStree t)
	{
		swap(t._root, _root);
		return *this;
	}

此时的形参t就是实参的拷贝,将t的头结点指针与this的头结点指针交换,就可以实现赋值重载。

7.find查找接口

根据二叉搜索树的性质,当树为空或者没找到时返回false,若树不为空进入循环,分以下三种情况:

1.当前结点的值小于key值,二叉树在右子树继续查找。
2.当前结点的值大于key值,二叉树在左子树继续查找。
3.当前结点的值等于key值,已经找到,返回true。

版本一:循环实现

	bool find(const k& key)
	{
		node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

版本二:递归版本

//主函数
	bool FindR(const k& key)
	{
		return _FindR(_root, key);
	}
//递归子函数
	bool _FindR(node* root, const k& key)
	{
		if (root == nullptr)
		{
			return false;
		}
		if (root->_key > key)
		{
			return _FindR(root->_left, key);
		}
		else if(root->_key < key)
		{
			return _FindR(root->_right, key);
		}
		else
		{
			return true;
		}
	}

8. insert插入接口

在插入时,首先判断二叉树是否为空树,若为空树,直接领根结点为插入结点即可,若不为空树,分为以下几种情况:

1.当key大于当前结点的值,在二叉树的右子树继续插入。
2.当key小于当前结点的值,在二叉树的左子树继续插入。
3.当key等于当前结点的值,返回false,二叉搜索树不允许出现重复的值。

一直往下遍历,除非出现重复的值,否则一定可以在合适的地方插入。

版本一:循环版本

由于最后要进行插入,所以得定义一个parent变量来保存父结点的值.

//循环插入
	bool Insert(const k& key)
	{
		//如果为空树,就直接进行插入
		if (_root == nullptr)
		{
			_root = new node(key);
			return true;
		}
		//否则不为空树
		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
			{
				return false;
			}
		}
		//此处的cur是一个临时变量,所以要插入必须令cur等于parent的左右子树。
		cur = new node(key);
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}

		return true;
	}

版本二:递归版本

在子函数递归时,我们的参数是很有讲究的,此时参数要传引用,在传引用之后,在递归时,下一个递归函数中的root就是上一个递归函数中的root->_left的别名,所以直接修改是可以的,不需要使用循环版本的父结点来指向了。

	//递归插入
	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 (root->_key < key)
		{
			return _InsertR(root->_right, key);
		}
		else if (root->_key > key)
		{
			return _InsertR(root->_left, key);
		}
		//如果已经有了这个数,就返回false,因为搜索二叉树不允许重复
		else
		{
			return false;
		}
	}

9.erase删除接口

在删除某一结点时,首先和插入和查找类似,先找到要删除的结点,在找到要删除的结点之后,也应该分为以下三种情况:

1.要删除的结点的左孩子结点为空,那么直接让该结点的父结点指向该结点的左孩子结点。
2.要删除的结点的右孩子结点为空,直接让该结点的父结点指向该结点的右孩子结点。
3.要删除的结点左右孩子结点都不为空,我们使用替换法,将要删除的结点和该结点左子树中最大的结点,或者右子树中最小的结点互换,然后再去删除。

【C++进阶】二叉搜索树_第3张图片

版本一:循环版本

与查找类似,循环版本必须保存父结点的值,在最后进行插入。

bool erase(const k& key)
	{
		node* parent = nullptr;
		node* cur = _root;
		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			//找到了要删除的结点
			else
			{
				if (cur->_left == nullptr)
				{
					if (_root == cur)
					{
						_root = cur->_right;

					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
					}

					delete cur;
					cur = nullptr;
				}
				else if (cur->_right == nullptr)
				{
					if (_root == cur)
					{
						_root = cur->_left;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
					cur = nullptr;
				}
				else
				{
					node* min = cur->_right;
					node* prev = cur;
					while (min->_left)
					{
						prev = min;
						min = min->_left;
					}
					swap(min->_key, cur->_key);
					if (prev->_left == min)
					{
						prev->_left = min->_right;
					}
					else
					{
						prev->_right = min->_right;
					}

					delete min;
				}
				return true;
			}
		}
		return false;
	}

版本二:递归版本

和插入相同,子函数传参时使用引用传参,此时去root就是上次递归中root->_left的别名,所以修改root就是修改上个结点的左子树或右子树。

	bool EraseR(const k& key)
	{
		return _EraseR(_root, key);
	}
	
	bool _EraseR(node*& root,const k& key)
	{
		if (root->_key > key)
		{
			return _EraseR(root->_left, key);
		}
		else if (root->_key < 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* min = root->_right;
				while (min->_left)
				{
					min = min->_left;
				}
				swap(min->_key, root->_key);
				return _EraseR(root->_right, key);
			}

			delete del;
			del = nullptr;
		}
	}

你可能感兴趣的:(C++进阶,c++,数据结构,算法)