当前位置: 首页 > news >正文

【数据结构:树】——搜索二叉树-K模型(非递归和递归)

我们在数据结构初期认识到了基础的二叉树,大概对二叉树有了初步的了解。但是二叉树对于我来说却是一个不晓得难点;下面分享一下在学习二叉树的变形二插搜索树的一些经验;

二叉搜索树

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

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

相关的操作

(1)二插搜索树的查找
它既然能够起名叫做二插搜索树那么必然在搜索这方面具有优势,下图给出一个二叉搜索树尝试查找一下进行分析一下;
在这里插入图片描述
从图片的分析中我们就可以很明了的看出来,这种用递归和非递归可以完成相对来说我觉得在二叉树的操作中递归反而更好写的而非递归想多有挑战性。

//声明一下,这里只是将最后封装的类中的函数拿出来体现一下思想,完整代码在结尾处展示
//1.非递归版本
Node* 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 cur;
	}
	return NULL;
}

//2.递归版本
Node* _FindR(Node* root,const K& key)
{
	if(root == NULL)
		return NULL;
	if(root->_key = key)
		return root;	
	else if(root->_key < key)
		return _FindR(root->_right,key);
	else if(root->_key > key)
		return _FindR(root->_left,key)		
}

(2)二插搜索树的插入

  • 如果本来就是空树那就直接插入
    在这里插入图片描述
  • 如果不是空树按照其规则进行插入
    在这里插入图片描述
    相比较于空树的直接插入,而向非空的二叉树中插入式就要进行比较,这里我们可以考虑有两个指针一个指针Cur指向当前查找节点,另外一个指针Parent指向当前节点的前一个(为了插入新节点时能够同树连接起来)。
//声明一下,这里只是将最后封装的类中的函数拿出来体现一下思想,完整代码在结尾处展示
//1.非递归版本
bool Insert(const K& key)
{
	if(_root == nullptr)//空树的情况
	{
		_root = new Node(key);
	}
		
	Node* perent = 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//注意有一个注意那就是在搜索二叉树中每一个节点数都是“惟一的”
		{
			return false;
		}
	}
	
	if(parent->_key > key)
	{
		parent->_left = new Node(key);
	}
	else//这里已经排除等于的情况不大于那就小于
	{
		parent->_right = new Node(key);
	}
	return true;
}
//2.递归版本
bool _Insert(Node* root,const K& key)
{
	if(root->_key == nullptr)//空树
		root = new Node(key);
	if(root->_key > key)
		return _Insert(root->_left,key);
	else if(root->_key < key)
		return _Insert(root->_right,key);
	else
		return false;
}

(3)二插搜索树的删除
二叉树的删除可以分为四个情况:

  • 要删除的结点没左右孩子
  • 要删除的节点只有左孩子
  • 要删除的节点只有右孩子
  • 要删除的节点有左右孩子
    在这里插入图片描述
    从上面的图中我们呢可以看出来在搜索二叉树中,要删除一个节点总共就是有四种情况;但是在用代码实现时,我们还可以将上面的情况进行综合简化,总的来说就可以规划为下面的三种情况:
  • 情况一:删除该节点并且使该节点的双亲指向被删除节点的左孩子
  • 情况二: 删除该节点并且使该节点的双亲指向被删除节点的右孩子
  • 情况三:要删除的节点具有双亲,在它的左子树找到左子树最大节点其余交换,然后是删除交换后的节点(这样做是为了保证删除后不会破坏二叉树的结构)或者是找到该节点右子树中最小节点与其交换然后然后删除就OK了。
bool Earse(const K& key)
{
	//1.要删除该节点就先要找到该节点
	Node* parent = nullptr;
	Node* cur = _root;
	
	while(cur)
	{
		if(cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if(cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			if(cur->_left == nullptr)//情况二:要删除的节点只有右孩子
			{
				if(parent->_left == cur)//如果是父亲节点的左孩子
				{
					parent->_left = cur->_right;
				}
				else if(parent->_right == cur)//如果是父亲节点的右孩子
				{
					parent->_right = cur->_right;
				}
			}
			else if(cur->_right == nullptr)//情况一:要删除的节点只有孩子
			{
				if(parent->_left == cur)//如果是父亲节点的左孩子
				{
					parent->_left = cur->_left;
				}
				else if(parent->_right == cur)//如果是父亲节点的右孩子
				{
					parent->_right = cur->_left;
				}
			}
			else//情况三:要删除的节点有左右孩子
			{
				Node* del = cur;//要删除的节点
				//方式一:找到该节点的左子树中最大,然后和该节点进行交换然后删除节点
				//方式二:找到该节点的右子树中最小,然后和该节点进项交换然后删除节点
				//这里只是实现:其中一种情况同理,请自行实现;
				Node* lessParent = cur;
				Node* lessRight = cutr->_right;
				//去该节点的右子树要到最大节点,其实很简单就是先进行入右子树一直往左边找直到nullptr
				//为止,那么此时lessparent节点就是右子树的最大节点.
				while(lessRight->_left)
				{
					lessParent = lessRight;
					lessRight = lessRight->_left;
				}			
				//进行值得交换或者是覆盖
				cur->_key = lessRight->_key;
				del = lessRight;
				
				if (lessParent->_left == lessRight)
				{
					lessParent->_left = lessRight->_right;
				}
				else
				{
					lessParent->_right = lessRight->_right;
				}	
			}
			delete del;
			return true;
		}
	}
	return false;
}

//2.递归版本
bool _EarseR(Node** root, const k& key)
	{
		Node* cur = *root;
		Node* del = cur;
		Node* replace = nullptr;

		if (*root == nullptr)//是一个空树
			return false;

		if (cur->_key > key)
			return _EarseR(&(cur->_left), key);
		else if (cur->_key < key)
			return _EarseR(&(cur->_right), key);
		else
		{
			del = cur;
			if (cur->_left == nullptr)//要删除的节点只有右子树
			{
				*root = cur->_right;
			}
			else if (cur->_right == nullptr)//要删除的节点只有左子树
			{
				*root = cur->_left;
			}
			else//要删除的节点具有左右子树
			{
				replace = cur->_right;
				while (replace->_left)
				{
					replace = replace->_left;
				}
				del = replace;
				cur->_key = replace->_key;
				return _EarseR(&(cur->_right), replace->_key);
			}
			delete del;
			del = NULL;
			return true;
		}
	}

注意: 相对于二插搜索树的其他操作,二叉搜索树的删除就会具有一定的难度。一点就是要删除要考虑的情况比较多,还有就是在进行递归的转换时应当考虑同非递归的处理方法也有不同;在非递归中我们在删除的时候,我们都会有一个就是前面的有一个指针保存要删除节点的父节点,用来保证删除后二插搜索树的正确性但是在递归的啥时候相当于把删除操作进行了细化,当递归到是要删除的节点时,此时在递归内面在一个全新的栈帧内面,此时要删除的节点就是这个树的根节点,而他的父亲节点却不知道。这里该怎么办?我想到了一个处理的方法,就是使用二级指针,此时就是根节点的地址了,比如说上图要删除节点8,那次是函数传入的就是节点7的右孩子的二级指针,直接就可以*root = cur->_right;了,
这样就比较方便了,剩下的删除逻辑就是同非递归一样的;

完整代码

//搜索二叉树的实现

//K模型

template<class k>
struct BSTreeNode//实现一个数节点的结构体
{
	BSTreeNode(const k& data = K()) :_left(nullptr), _right(nullptr), _key(data)
	{}
	BSTreeNode<k>* _left;
	BSTreeNode<k>* _right;
	k _key;
};

template<class k>
class BSTree
{
	typedef BSTreeNode<k> Node;
public:
	BSTree() :_root(nullptr)
	{}

	~BSTree()
	{

	}

	//搜索二叉树插入
	bool Insert(const k& key)
	{
		if (_root == nullptr)//如果是空树
		{
			_root = new Node(key);
			return true;
		}

		Node* parent = _root;
		Node* cur = _root;

		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				return false;
			}
		}

		if (parent->_key > key)
		{
			parent->_left = new Node(key);
		}
		else if (parent->_key < key)
		{
			parent->_right = new Node(key);
		}
		return true;
	}

	//搜索二叉树中序遍历
	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}

	//搜索二叉树的查找
	Node* 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 cur;
			}
		}
		return NULL;
	}

	//二插搜索树删除---要删除的节点要么是只有一个孩子要么就是没有孩子
	bool Erase(const k& key)
	{
		Node* parent = NULL;
		Node* cur = _root;

		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key>key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				Node* del = cur;
				//1.是叶子;或者只有一个叶子
				if (cur->_left == NULL)
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else if (parent->_right == cur)
					{
						parent->_right = cur->_right;
					}
				}
				else if (cur->_right == NULL)
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else if (parent->_right == cur)
					{
						parent->_right = cur->_left;
					}
				}
				else//2.左右都不为空
				{
					Node* lessParent = cur;
					Node* lessRight = cur->_right;
					while (lessRight->_left)
					{
						lessParent = lessRight;
						lessRight = lessRight->_left;
					}
					
					cur->_key = lessRight->_key;
					del = lessRight;
					if (lessParent->_left == lessRight)
					{
						lessParent->_left = lessRight->_right;
					}
					else
					{
						lessParent->_right = lessRight->_right;
					}
				}
				delete del;
				return true;

			}	
		}
		return false;
	}

	//递归版本

	bool _InsertR(Node*& root, const k& key)//这里传入的是根节点的引用,因为进入递归后,再去传入的节点地址和上一次没有任何关系;相当于什么都没有插入进去
	{
		if (root == nullptr)
			root = new Node(key);	
		if (root->_key > key)
			return _InsertR(root->_left, key);
		else if (root->_key < key)
			return _InsertR(root->_right, key);
		else
			return false;		
	}

	bool InsertR(const k& key)
	{
		return _InsertR(_root, key);
	}

	Node* _FindR(Node* root, const k& key)
	{
		if (root == NULL)
			return NULL;
		if (root->_key == key)//底肥终止条件
			return root;
		else if (root->_key > key)
			return _FindR(root->_left, key);
		else if(root->_key < key)
			return _FindR(root->_right,key);
	}

	Node* FindR(const k& key)
	{
		return _FindR(_root, key);
	}

	bool _EarseR(Node** root, const k& key)
	{
		Node* cur = *root;
		Node* del = cur;
		Node* replace = nullptr;

		if (*root == nullptr)//是一个空树
			return false;

		if (cur->_key > key)
			return _EarseR(&(cur->_left), key);
		else if (cur->_key < key)
			return _EarseR(&(cur->_right), key);
		else
		{
			del = cur;
			if (cur->_left == nullptr)//要删除的节点只有右子树
			{
				*root = cur->_right;
			}
			else if (cur->_right == nullptr)//要删除的节点只有左子树
			{
				*root = cur->_left;
			}
			else//要删除的节点具有左右子树
			{
				replace = cur->_right;
				while (replace->_left)
				{
					replace = replace->_left;
				}
				del = replace;
				cur->_key = replace->_key;
				return _EarseR(&(cur->_right), replace->_key);
			}
			delete del;
			del = NULL;
			return true;
		}
	}

	bool EarseR(const k& key)
	{
		return _EarseR(&_root, key);
	}
private:
	void _Inorder(Node* root)
	{
		if (root == nullptr)
			return;
		_Inorder(root->_left);
		cout << root->_key << " ";
		_Inorder(root->_right);
		
	}
private:
	Node* _root;
};

void Test()
{
	int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
	BSTree<int> t;
	for (auto e : a)
	{
		t.Insert(e);
	}
	t.InsertR(11);
	t.InsertR(12);
	t.InsertR(13);
	//std::cout << t.FindR(6)->_key << std::endl;
	//t.Inorder();
	//t.Erase(8);
	t.EarseR(8);
	t.Inorder();
}

//代码正确,测试请自行测试,

以上就是二插搜索树的K模型的递归和非递归实现,,当然还有一个K-V模型我们在后面实现。虽然我们实现理解不是最优,你是希望能给大家一点帮助,那样也就有价值了!!!

相关文章:

  • 【C++】——STL关联式容器认识以及使用
  • TCP三次握手和四次挥手详解
  • 【Linux】进程控制
  • 【Linux】进程程序替换——exec函数簇
  • 【Linux】入门基础命令(2)
  • 【Linux】权限管理和粘滞位理解
  • linux下inode节点理解
  • C语言函数
  • C语言数组
  • C语言表达式
  • C语言初识指针
  • C语言结构体
  • C语言深度剖析数据在内存中的存储
  • 深入了解指针
  • 字符串函数(认识 + 实现)
  • [译] 怎样写一个基础的编译器
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • Django 博客开发教程 16 - 统计文章阅读量
  • Java超时控制的实现
  • js算法-归并排序(merge_sort)
  • SpiderData 2019年2月13日 DApp数据排行榜
  • 产品三维模型在线预览
  • 大主子表关联的性能优化方法
  • 让你成为前端,后端或全栈开发程序员的进阶指南,一门学到老的技术
  • 十年未变!安全,谁之责?(下)
  • 树莓派用上kodexplorer也能玩成私有网盘
  • ​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结
  • # Panda3d 碰撞检测系统介绍
  • # 飞书APP集成平台-数字化落地
  • #设计模式#4.6 Flyweight(享元) 对象结构型模式
  • (0)Nginx 功能特性
  • (06)Hive——正则表达式
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (草履虫都可以看懂的)PyQt子窗口向主窗口传递参数,主窗口接收子窗口信号、参数。
  • (附源码)springboot宠物管理系统 毕业设计 121654
  • (十二)springboot实战——SSE服务推送事件案例实现
  • (转) Face-Resources
  • (转)Linq学习笔记
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • .NET CF命令行调试器MDbg入门(二) 设备模拟器
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料
  • .NET与 java通用的3DES加密解密方法
  • .NET中统一的存储过程调用方法(收藏)
  • [ C++ ] STL---stack与queue
  • [ Linux ] git工具的基本使用(仓库的构建,提交)
  • [ 第一章] JavaScript 简史
  • []新浪博客如何插入代码(其他博客应该也可以)
  • [1204 寻找子串位置] 解题报告
  • [Android Pro] AndroidX重构和映射
  • [android学习笔记]学习jni编程
  • [autojs]逍遥模拟器和vscode对接
  • [Electron]ipcMain.on和ipcMain.handle的区别
  • [leetcode 双指针]
  • [Linux] PXE批量装机
  • [na]wireshark抓包排错-tcp.flags.reset