【数据结构进阶】之搜索二叉树(C++实现)

这篇具有很好参考价值的文章主要介绍了【数据结构进阶】之搜索二叉树(C++实现)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言

📃博客主页: 小镇敲码人
💞热门专栏:数据结构与算法
🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌏 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月,独傲天下百坚强。 男儿应有龙腾志,盖世一意转洪荒。 莫使此生无痕度,终归人间一捧黄。🍎🍎🍎
❤️ 什么?你问我答案,少年你看,下一个十年又来了 💞 💞 💞

🍎 搜索二叉树的定义

二叉搜索树是一种树形结构之一,它是一种特殊的二叉树,它有着这样的性质:
1.根的左子树的值都比根的值要小。
2.根的右子树的值都要比根的值要大。
注意:搜索二叉树的左右子树也是搜索二叉树。

💎 搜索二叉树的性质

这里直接给出结论,那就是搜索二叉树的一个福利(性质),其中序遍历(先遍历左子树,再遍历根节点,最后遍历右子树)为一个排好序了的单调递增序列,这里我们等下根据程序验证这个性质。由于这个性质,搜索二叉树又可以叫做二叉排序树或二叉查找树,注意:空树也当作搜索二叉树

🍎 搜索二叉树的模拟实现

我们想手撕一个搜索二叉树,先要弄清楚这个树需要具备哪些功能?

  1. 插入(递归与非递归)
  2. 删除 (递归与非递归)
  3. 查找(递归与非递归)

然后就是设计这个二叉树的大体框架,我们首先需要定义一个结构体用来表示搜索二叉树的节点,然后我们需要定义一个搜索二叉树的类模板来放置这个搜索二叉树的方法和数据成员,这里我们为了简单,没有将类方法的声明和定义分离。

注意:我们的搜索二叉树为了简单,不支持存储重复元素。

template<class T>//定义模板参数,因为这个
//节点结构体,用来存放一个节点的元素
struct BSTreeNode{
	BSTreeNode(const T& val = T())
		:left_(nullptr),right_(nullptr),val_(val)
	{}
	BSTreeNode<T>* left_;
	BSTreeNode<T>* right_;
	T val_;
};

template<class T>
class BSTree//二叉搜素树的类模板
{
	typedef BSTreeNode<T> Node;
	typedef Node* PNode;
public:
	BSTree() :root_(nullptr);//普通构造函数
	BSTree(const BSTree<int>& copyed);//拷贝构造函数
	BSTree<int>& operator=(BSTree<int> tmp);//赋值运算符重载
	bool Insert(const T& val);//非递归插入
	bool InsertF(const T& val);//递归插入
	PNode Find(const T& val);//非递归查找
	PNode FindF(const T& val);//递归查找
	bool erase(const T& val);//非递归删除
	bool eraseF(const T& val);//递归删除
	~BSTree()//析构函数
private:
	PNode root_;
};

上面是大致的结构,下面我们具体来看一下每一个方法的具体实现

💎 搜索二叉树的插入

插入分为两种情况
1.树为空,这种情况直接创建一个新的节点,然后把它给根节点即可。
2.树不为空,循环这个树如果这个节点值大于根就往右边去找插入位置,反之往左边去找插入位置,如果这个位置为空,就代表可以插入了,注意我们应该保存父节点,否则将无法插入。如果有节点的值等于我们要插入的值,那么被插入的值已经存在,我们直接返回。

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言

🍁 非递归版本

bool Insert(const T& val) {
		//根节点为空
		if (root_ == nullptr)
		{
			root_ = new Node(val);
		}
		//根节点不为空
		else {
			PNode parent = nullptr;
			PNode child = root_;
			while (child != nullptr)
			{
				if (val > child->val_)
				{
					parent = child;
					child = child->right_;
				}
				else if (val < child->val_)//如果小于当前节点的值,我们就往
				{
					parent = child;
					child = child->left_;
				}
				else//等于说明这个元素已经存在,直接返回false
					return false;
			}
			PNode newnode = new Node(val);
			if (parent->val_ > val)
				parent->left_ = newnode;
			else
				parent->right_ = newnode;
			return true;
		}
	}

🍁 递归版本

public:
bool InsertF(const T& val)
	{
		return Insert_(val, root_);
	}
private:
bool Insert_(const T& val, PNode& root) 
	{
		if (root == nullptr)//找到了把新节点的地址直接传给父节点左节点或者右节点指针的引用,此时的root是它们的别名改变root的值,就相当于改变了左节点或者右节点的保存地址
		{
			root = new Node(val);
			return true;
		}
		else if (val > root->val_)//大于当前节点,继续遍历它的右子树
			return Insert_(val, root->right_);
		else if (val < root->val_)
			return Insert_(val, root->left_);
		else
			return false;
	}

这里引用起到很大的作用,它是节点指针的别名,改变它保存的地址值,就是改变了相应父亲节点的指向,如果你对引用不太熟悉,可以看一下博主这篇文章,对引用有着清晰的解释。

至于上面为什么要用函数套一层,因为递归要用到根节点,根节点又是private,受类访问限定符的限制,在外面是调不到的,所以只好在里面套一层,那个不直接调的函数Insert_我们也要放在private修饰。

💎 二叉搜索树的查找

查找应该如何实现?只要理清楚下面的几点,查找搜索二叉树中的某个值就变的异常简单了。

  • 找到那个值就返回true,找不到就返回false(当前节点为空指针)。
  • 如果要寻找的目标值大于当前节点的值就去继续遍历它的右子树,否则遍历它的左子树。

🍁 非递归版本

bool Find(const T& val)
	{
		PNode root = root_;
		while (root)
		{
			if (val > root->val_)
				root = root->right_;
			else if (val < root->val_)
				root = root->left_;
			else
				return true;//找到了返回true
	    }
		return false;//根节点为空,我们直接返回空指针
	}

🍁 递归版本

public:
bool FindF(const T& val) 
	{
		return Find_(root_,val);
	}
private:
bool Find_(PNode root, const T& val)
	{
		if (root == nullptr)
			return false;
		if (val > root->val_)
			return Find_(root->right_, val);
		else if (val < root->val_)
			return Find_(root->left_, val);
		else
			return true;
	}

这里套一层是为了解决同样的问题,递归需要根节点,但是根节点无法从外面传。这里小伙伴对递归可能有些疑问,在博主的二叉树其它系列文章里有详细的对递归展开图的展示,如果你有疑问,请自行翻阅,这里我们只给出粗略图:

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言
【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言

💎 二叉搜索树的删除

删除就比其它两个方法要复杂一点,但是只要我们弄清楚下面的情况,这个问题也就迎刃而解了:
1.我们要删除的那个节点只有一个节点,我们使用托孤法,将这个节点的子节点托给它的父亲。

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言
2.我们要删除的那个节点一个节点都没有,其实这种情况和情况1一致,都是使用托孤法来解决。

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言
3.我们要删除的节点有两个子节点(都不为空),此时使用托孤法明显行不通了(你的父亲最多帮你管一个孩子,能力有限(dog)),或者比较复杂,我们换一种方法,使用替换法。

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言

🍁 非递归版本

bool erase(const T& val)
	{
		PNode parent = root_;
		PNode target = root_;
		while (target != nullptr)
		{
			if (val > target->val_)
			{
				parent = target;
				target = target->right_;
			}
			else if (val < target->val_)
			{
				parent = target;
				target = target->left_;
			}
			else
				break;
		}
		if (target == nullptr)//没找到直接返回false
			return false;
		if (target->left_ == nullptr)//托孤
		{
			if (parent->val_ < val)
				parent->right_ = target->right_;
			else if (parent->val_ > val)
				parent->left_ = target->right_;
			else
				root_ = target->right_;
			delete target;
		}
		else if (target->right_ == nullptr)//托孤
		{
			if (parent->val_ < val)
				parent->right_ = target->left_;
			else if (parent->val_ > val)
				parent->left_ = target->left_;
			else
				root_ = target->left_;
			delete target;
		}
		else//替换法
		{
			PNode tmp = target->right_;
			PNode parent_ = target;
			while (tmp->left_)
			{
				parent_ = tmp;
				tmp = tmp->left_;
			}
			swap(tmp->val_, target->val_);//交换待删除和替代节点
			if (parent_ == target)//右子树没有左节点
				target->right_ = tmp->right_;
			else//右子树有左节点此时,我们把替换节点的右子树给父亲节点的左子树即可,不用关心它是否为空
			parent_->left_ = tmp->right_;
			delete tmp;
		}
		return true;
	}

🍁 递归版本

递归遍历主要用到了引用,代码相对于非递归版本会少不少,逻辑上大体一致,只不过实现上有一点细微区别,引用的使用让我们的递归代码看起来更加的简洁。

bool eraseF(const T& val)
    {
		return erase_(root_, val);
	}
bool erase_(PNode& root, const T& val)
	{
		if (root == nullptr)//root等于空说明二叉树搜索树为空或者没找到这个节点,我们直接返回空
			return false;
		if (val > root->val_)//被删除值比当前节点值大去它的右树找
			return erase_(root->right_, val);
		else if (val < root->val_)//被删除值比当前节点值小去它的左树找
			return erase_(root->left_, val);
		else//找到了我们开始执行删除操作
		{
			
			if (root->left_ == nullptr)//被删除节点只有一个子树,托孤法
			{
				PNode tmp = root;//提前保存,防止内存泄漏
				root = root->right_;
				delete tmp;
			}
			else if (root->right_ == nullptr)//被删除节点只有一个子树,托孤法
			{
				PNode tmp = root;//提前保存,防止内存泄漏
				root = root->left_;
				delete tmp;
			}
			else//被删除节点有两个子节点,替换法
			{
				//先找右子树的最左节点,并把当前根节点值和它的值进行交换
				PNode tmp = root->right_;
				PNode parent = root;
				while (tmp->left_)
				{
					parent = tmp;
					tmp = tmp->left_;
				}
				swap(tmp->val_, root->val_);//交换
				//此时我们要删除的目值所在节点至多只有一个子节点了,我们使用托孤法完成删除
				if (parent == root)//目标节点的右子树只存在一个左节点
					parent->right_ = tmp->right_;
				else
				{
					parent->left_ = tmp->right_;
				}
				delete tmp;
			}
			return true;//删除成功,返回true
		}
	}

大体思路一致,注意细节:如引用的理解,这里和插入的非递归引用的使用是一样的,因为引用本质是PNode指针的别名,改变了root,就相当于改变了某个父节点的右节点或者左节点的指针值,注意:一个引用变量不能作为多个值的引用,所以如果这里如果是情况3,目标节点有两个子节点,引用就不能发挥作用了,因为我们只能使用临时变量保存,用二级指针可能可行。

💎 拷贝构造函数

有时候,我们可能要用一个已经存在搜索二叉树类对象来初始化另外一个新创建的搜索二叉树对象,这个时候就需要用到拷贝构造函数,正常情况下系统会默认生成一个拷贝构造函数。

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言
上图是我们没有写拷贝构造函数,编译器默认生成的拷贝构造函数,这里有小伙伴就会有疑问了,既然编译器默认生成的可以正常运行,那么重新写一个不是多次一举吗,然而并不是这样,这里是因为我们还没有写析构函数(释放空间),如果写了析构函数这里就会出现二次析构的问题(释放不属于你的空间),因为系统默认生成的拷贝构造是值拷贝,这里需要深拷贝,这个问题我们放到下面的析构函数去将,下面我们来对拷贝构造函数进行一下模拟实现:

public:
BSTree(const BSTree<int>& copyed)
	{
		preorder_(root_,copyed.root_);//这里我们走一个前序来构建一下搜索二叉树
	}
private:
void preorder_(PNode& root,const PNode& copyroot)
	{
		if (copyroot == nullptr)
			return;
		root = new Node(copyroot->val_);
		preorder_(root->left_,copyroot->left_);
		preorder_(root->right_,copyroot->right_);
	}

这里我们使用二叉树的前序遍历递归构建了一个新的二叉搜索树,并且空间都是新开的,是深拷贝。

💎 赋值运算符重载

在类和对象中我们已经介绍了,关于为什么要赋值运算符重载的一些知识,同样的如果我们不显式的去写,编译器也会默认生成一个赋值运算符重载的函数,但是同样的会存在深拷贝的问题:

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言

这里程序没有崩溃,同样是因为我们还没有写析构函数,等会我们会来验证,这里我们先模拟实现一下赋值运算符重载函数:

BSTree<int>& operator=(BSTree<int> tmp)
	{
		swap(tmp.root_,root_);
		return *this;
	}

这里我们使用的是现代的写法(ps:比较高级的意思hhhh),首先看参数不是引用(使用引用会导致外面那个值改变,这是不符合我们的需求的),而是外面对象的一个拷贝(这里是深拷贝,因为我们已经实现了深拷贝的拷贝构造函数),直接交换它们的根节点地址,然后返回this指针的解引用就完成了赋值,外面调用还是这样:

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言

💎 析构函数

终于到我们的二叉搜索树的析构函数了,这个函数不用多说,自然是用来释放我们在堆上面的申请的节点的空间,直接开始模拟实现,这里我们走一个二叉树的后序遍历来释放空间:

public:
~BSTree()//析构函数
	{
		cout << "root_:" << root_ << endl;
		Postorder_(root_);
	}
private:
void preorder_(PNode& root,const PNode& copyroot)
	{
		if (copyroot == nullptr)
			return;
		root = new Node(copyroot->val_);
		preorder_(root->left_,copyroot->left_);
		preorder_(root->right_,copyroot->right_);
	}

这里析构函数在递归走后序遍历之前,我们打印了一下根节点的地址,这里就是为了验证是否我们对同一片空间进行了两次内存的释放。

我们先来验证赋值运算重载函数我们我们不进行深拷贝的实现,会出现什么问题,我们先将我们写好的注释,调用编译器默认生成的。

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言
这是没注释时:

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言
程序正常运行。

再注释掉写好的拷贝构造函数,使用编译器自己默认生成的拷贝构造函数:

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言

可以看到出现了一样的问题,由于没有进行深拷贝,而导致我们对已经释放的空间进行了二次释放,这里我们不对深拷贝进行更详细的探讨,在C++类和对象(二)中会有更全面的解释。

这是没注释时:

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言
程序正常运行。

🍎 搜索二叉树的应用

💎 K模型

搜索二叉树的K模型和我们刚刚实现的搜索二叉树没有什么区别,它的应用有很多,比如我们的门禁,就是将你的信息插入到门禁系统里,再查找你这个人存不存在。

💎 K-V模型

K-V模型也叫做Key-value模型,它会存两个值,一个就是Key(关键字),另外一个是Value,常见的应用像字典的实现,商场的车库(将你车辆的信息作为key,此时的时间记作value,出来时根据现在的时间和value来收取费用)。

这两个模型都是用搜索二叉树实现的,我们这里不做过多阐述,下面来分析一下搜索二叉树的性能。

🍎 搜索二叉树的性能分析

我们可以看到搜索二叉树的三个关键方法,插入、删除和查找,其中插入和删除都是用到了查找的,所以查找的时间复杂度就决定了整个搜索二叉树的性能如何,这里查找的最坏情况是O(N),具体请看下面这幅图。

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言
此时查找2和8都程序都需要执行N/2次,是O(N)量级的,更极端的情况如下:

【数据结构进阶】之搜索二叉树(C++实现),数据结构与算法,数据结构,c++,开发语言

O(N)的时间复杂度意味着20亿的数据我们可能就要查找20亿(总之很大),有什么好的方法可以解决这个问题,让查找的效率达到O(logN)呢,敬请期待我们下期的平衡搜索二叉树(AVL树)。文章来源地址https://www.toymoban.com/news/detail-844562.html

到了这里,关于【数据结构进阶】之搜索二叉树(C++实现)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构进阶篇 之 【二叉树顺序存储(堆)】的整体实现讲解(赋完整实现代码)

    做人要谦虚,多听听别人的意见,然后记录下来,看看谁对你有意见 3.1 向下调整算法 AdJustDown 3.2 向上调整算法 AdJustUP 3.3 堆的创建 3.3.1 向上建堆 3.3.2 向下建堆 3.3.3 堆的初始化与销毁 3.3.4 堆的插入(压栈) 3.3.5 取堆顶的数据 3.3.6 堆的删除 3.3.7 堆的数据个数 3.3.8 堆的判空

    2024年04月17日
    浏览(72)
  • [数据结构进阶 C++] 二叉搜索树(BinarySearchTree)的模拟实现

    二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树: 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值 它的左右子树也分别为二叉搜索树 我们先给出两个示例: 此

    2024年02月04日
    浏览(42)
  • 数据结构:搜索二叉树 | 平衡二叉树

    博客写的代码都放在这里:gitee仓库链接 1.二叉搜索树 1.1.基本概念 二叉搜索树又称二叉排序树, 可以为空,如果不为空具有以下性质的二叉树 : 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的

    2024年01月23日
    浏览(58)
  • 数据结构——常见二叉树的分类(完全二叉树、满二叉树、平衡二叉树、二叉搜索树、红黑树)

    专业术语 中文 描述 Root 根节点 一棵树的顶点 Child 孩子结点 一个结点含有的子树的根节点称为该结点的子节点 Leaf 叶子结点 没有孩子的节点 Degree 度 一个节点包含子树的数量 Edge 边 一个节点与另外一个节点的连接 Depth 深度 根节点到这个节点经过边的数量 Height 节点高度 从

    2024年02月03日
    浏览(44)
  • 数据结构第四天: Complete Binary Search Tree 【搜索二叉树,完全二叉树】

     这道题需要的操作时排序并且需要遍历,最重要的一点它是个完全二叉树,所以数组是最适合的 这道题我的思路来自于浙江大学课件7.0.2完全二叉树 这道题说白就是将输入的样例构造成一个完全搜索二叉树。因为完全线索二叉树的 根节点一定是是一个处于左右子树中间的那

    2024年02月06日
    浏览(55)
  • 数据结构进阶篇 之 【二叉树】详细概念讲解(带你认识何为二叉树及其性质)

    有朋自远方来,必先苦其心志,劳其筋骨,饿其体肤,空乏其身,鞭数十,驱之别院 1.1 二叉树中组分构成名词概念 1.2 二叉树的结构概念 1.3 特殊的二叉树 2.1 顺序存储 2.2 链式存储 前言: 在你的想象中如果有一个“二叉树”会是什么样子呢? 顾名思义,现实中的二叉树我们

    2024年04月13日
    浏览(45)
  • 〖数据结构〗一棵有点自律的树——搜索二叉树

    🌸作者简介: 花想云 ,在读本科生一枚,致力于 C/C++、Linux 学习。 🌸 本文收录于 C++系列 ,本专栏主要内容为 C++ 初阶、C++ 进阶、STL 详解等,专为大学生打造全套 C++ 学习教程,持续更新! 🌸 相关专栏推荐: C语言初阶系列 、 C语言进阶系列 、 数据结构与算法 、 Linu

    2024年02月08日
    浏览(42)
  • (浙大陈越版)数据结构 第三章 树(中) 二叉搜索树和平衡二叉树

    目录 4.1.1 二叉搜索树及查找 什么是二叉搜索树 定义 二叉搜索树特殊函数集: 查找操作:Find 算法思想 代码实现 补:查找最大和最小元素 4.1.2 二叉搜索树的插入 插入操作:Insert 算法思想 代码实现 例题 4.1.3 二叉搜索树的删除 删除操作:delete 算法思想 情况1:删除叶节点

    2024年02月08日
    浏览(55)
  • 数据结构与算法之二叉树: Leetcode 98. 验证二叉搜索树 (Typescript版)

    验证二叉搜索树 https://leetcode.cn/problems/validate-binary-search-tree/ 描述 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树 有效 二叉搜索树定义如下: 节点的左子树只包含 小于 当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身

    2024年02月16日
    浏览(40)
  • 数据结构与算法(三):树论(树形结构、二叉树、二叉搜索树、红黑树、Btree&B+Tree、赫夫曼树、堆树)

    树论(树形结构、二叉树、二叉搜索树、红黑树、Btree、B+Tree、赫夫曼树、堆树) 在树形结构里面重要的术语: 结点:树里面的元素。 父子关系:结点之间相连的边 子树:当结点大于1时,其余的结点分为的互不相交的集合称为子树 度:一个结点拥有的子树数量称为结点的

    2024年02月01日
    浏览(94)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包