BST树

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/weixin_43949535/article/details/100745879

2019年9月11日19:47:05
看本篇博客,可以先行回顾我的博客:二叉树的四种遍历方式(递归与非递归实现)

折半查找

通常情况下,在一个已然有序的数列中 进行每个元素的查找,那么常用的就是折半查找。根据减而治之的策略,很容易就证明其正确性和有穷性。其时间复杂度非常优秀的达到了O(logn),比在这组数列之中顺序查找要好得多。其做法是:

  1. 将要查找的值val 与 数列中间位置mid上的值进行比对,相等则直接返回mid。
  2. 若是小于Array【mid】,那么根据有序数列的特点,mid以及其右边的每个元素的值都是不可能比对成功的。所以问题的规模也就随之缩减至【0,mid-1】,然后接着在这个一半的区间上进行同样的查找。每次查找即使不成功,也会将问题的规模进行缩减。直至val==Array【mid】成功;或者low > high,查找不存在。
  3. 若是大于,则同上可得。
  4. 因此对于 这种已然有序的数列而言,折半的效率是相当高的。

如下:折半查找的源代码如下:

/**══════════════════════════════════╗
*作    者:songjinzhou                                                 ║
*CSND地址:https://blog.csdn.net/weixin_43949535                       ║
**GitHub:https://github.com/TsinghuaLucky912/My_own_C-_study_and_blog║
*═══════════════════════════════════╣
*创建时间:                                                            
*功能描述:                                                            
*                                                                      
*                                                                      
*═══════════════════════════════════╣
*结束时间:                                                             
*═══════════════════════════════════╝
//                .-~~~~~~~~~-._       _.-~~~~~~~~~-.
//            __.'              ~.   .~              `.__
//          .'//              西南\./联大               \\`.
//        .'//                     |                     \\`.
//      .'// .-~"""""""~~~~-._     |     _,-~~~~"""""""~-. \\`.
//    .'//.-"                 `-.  |  .-'                 "-.\\`.
//  .'//______.============-..   \ | /   ..-============.______\\`.
//.'______________________________\|/______________________________`.
*/
#include <iostream>
#include <vector>
using namespace std;

class Solution
{
public:
	//递归实现折半查找,返回相等元素下标
	static int binarySearch_Operator(vector<int>& src, int low, int high,int val)
	{
		if (low > high)
			return -1;
		int mid = low + (high - low) / 2;
		if (val == src[mid])
			return mid;
		
		if (val < src[mid])//去左边找
			return binarySearch_Operator(src, low, mid - 1, val);
		else
			return binarySearch_Operator(src, mid + 1, high, val);
		
		return -1;//没有找到
	}
	//递归实现折半查找,返回相等元素下标
	int binarySearch(vector<int>& src,int val)
	{
		int size = src.size();
		if (size == 0)
			return -1;
		int low = 0, high = size - 1;
		
		return binarySearch_Operator(src, low, high, val);
	}

	//非递归实现折半查找,返回相等元素下标
	int nonbinarySearch(vector<int>& src, int val)
	{
		int size = src.size();
		if (size == 0)
			return -1;
		int low = 0, high = size - 1;
		int mid = low + (high - low) / 2;

		while (low <= high)
		{
			if (src[mid] == val)
			{
				return mid;
			}
			if (val < src[mid])
			{
				high = mid - 1;
			}
			else
			{
				low = mid + 1;
			}
			mid = low + (high - low) / 2;
		}
		return -1;
	}
};

int main()
{
	Solution solution;
	//35,37,47,51,58,62,73,88,93,99
	vector<int>myvec({ 35,37,47,51,58,62,73,88,93,99 });

	cout << "递归实现,51的下标是:" << 
				solution.binarySearch(myvec, 51) << endl;
	cout << "递归实现,99的下标是:" << 
				solution.binarySearch(myvec, 99) << endl;
	cout << "非递归实现,51的下标是:" << 
				solution.nonbinarySearch(myvec, 51) << endl;
	cout << "非递归实现,99的下标是:" << 
				solution.nonbinarySearch(myvec, 99) << endl;
	return 0;
}
/**
*备用注释:
*
*
*
*/

测试如下:
在这里插入图片描述
折半的递归和非递归实现代码如上,其作用下面就知道了(可以暂时多思考一下 其数据搜索查找过程)。

BST树定义以及特性

BST树:即 二叉查找树(Binary Search Tree),又名二叉搜索树或二叉排序树。它可以是一颗空树,或者是具有下列性质的一棵二叉树:

  1. BST树上面不存在键值相等的节点
  2. 若它的左子树不空,则左子树上所有结点的值均 小于 它的根结点的值。
  3. 若它的右子树不空,则右子树上所有结点的值均 大于 它的根结点的值。
  4. 任意一个节点的左、右子树也分别为一棵二叉查找树。
  5. 对BST树进行 中序遍历,就可以得到一个有序的数列
  6. BST树 依旧是一棵二叉树,之前总结的二叉树的特性都是满足的。

于是根据上面的特性,如果为了找出BST树的最大元素和最小元素,那么就是从根节点一直往左走,直至无左路可走,即得最小元素;从根节点一直往右走,直至无右路可走,即得最大元素。如下图所示:minval=8,maxval=25 。对其进行中序遍历,结果为:8 10 11 12 13 15 16 17 18 19 22 25
在这里插入图片描述
既然中序遍历BST树,可以得到一个有序数列,那么对于一组无序的数据来说: 将他们构建成为一个BST树即可。其中,构建树的过程:就是在对无序序列进行排序的过程。 由于BST的物理结构是建立在二叉链表之上,每一个新加入的元素都是作为BST树上的一个叶子节点进行插入的。所以在此过程中 没有数据元素的移动(不会影响到其他的节点),都是指针操作(仅仅将插入节点的某个孩子域指针 修改即可)。

BST树的结构定义

BST树上的每一个节点的度最大为2,且该节点的值满足: left < this < right 。 BST树的结构定义如下:

//BST 树的结构定义
template<typename T>
class BSTree
{
public:
	BSTree():root(nullptr){}

private:
	class BSTNode//BST树的 节点类型定义
	{
	public:
		BSTNode(T data=T()):val(data),left(nullptr),right(nullptr){}

		T val;
		BSTNode* left;
		BSTNode* right;
	};
	BSTNode* root;//指向BST树(指向根节点)的指针
};

BST树的插入

在这里插入图片描述

BST插入的非递归版本

/* *******************************************************
 * 非递归实现BST树的插入操作
 * 对于插入操作的实现,首先分析有两种情况:第一种是插入前BST树中
 * 并没有结点,这种情况下我们直接把该值作为根节点即可返回;第二种情况
 * 是树中已经存在结点,因此我们就需要根据BST树的性质进行搜索应该
 * 插入的结点,注意我们应该保存遍历到当前结点的父节点所以这里使用了pre
 * 进行保存的。而使用p作为工作指针,去遍历位置。因为当前结点有可
 * 能是我们要插入的结点,所以最后还是需要要判断应该插入父节点pre的左孩子
 * 还是右孩子函数的实现容易理解:大于当前结点值向右走,小于该当前结点值向左走,
 * 直到找到为空的结点。找到该结点位置后,我们还需要判断待插入结点与父结点的大小,
 * 大于父结点插入到右孩子,小于父结点插入到左孩子即可
 *********************************************************/
 void noninsert(const T& val)//BST的插入 非递归实现
	{
		if (root == nullptr)
		{
			root = new BSTNode(val);//新节点作为根节点
			return;
		}
		BSTNode* p = root;//工作指针 去寻访合适的插入位置
		BSTNode* pre = nullptr;//pre是p的父节点
		while (p != nullptr)
		{
			if (val < p->val)
			{
				pre = p;
				p = p->left;
			}	
			else
			{
				pre = p;
				p = p->right;
			}
		}
		//此时 pre指向的就是这个插入位置的父节点
		if (val < pre->val)
		{
			pre->left = new BSTNode(val);//插在左边
		}
		else pre->right = new BSTNode(val);//插在右边
	}

BST插入的递归版本

//插入 递归实现操作
BSTNode *insert_Operator(BSTNode* cur_node, const T& val)
	{
		//若是root指向的树 则是个空树
		//这里进行了 指针的链接操作,把这个叶子节点 连接上了
		//于是就把这个叶子节点 连接在这个分支上了
		if (cur_node == nullptr)
			return new BSTNode(val);//作为一个叶子节点 插在父节点(一个叶子)
		if (val < cur_node->val)//的左右孩子
		{
			cur_node->left = insert_Operator(cur_node->left, val);
		}
		else
		{
			cur_node->right = insert_Operator(cur_node->right, val);
		}
		return cur_node;
	}
	void insert(const T& val)//BST树的插入 递归实现
	{
		root = insert_Operator(root, val);
	}

测试如下:
在这里插入图片描述

BST树的查询

BST查询的非递归版本

/* 
 * 非递归实现BST树的查询操作
 * BST树的查询操作的实现首先需要判断根节点是否为空
 * 之后便可以遍历BST树查找元素,这里我们返回布尔值,
 * 有时候我们可能需要根据具体的情况,要让查询
 * 返回查询到的结点,只需修改函数返回值类型和函数
 * 的返回语句即可。
*/

bool nonquery(const T& val)//非递归实现BST的查询
	{
		if (root == nullptr)
			return false;

		BSTNode* p = root;
		while (p != nullptr)
		{
			if (val == p->val)
			{
				return true;
			}
			else if (p->val > val)
			{
				p = p->left;
			}
			else
			{
				p = p->right;
			}
		}
		return false;
	}

BST查询的递归版本

//递归实现BST的查询
	static bool query_Operator(BSTNode* this_node, const T& val)
	{
		if (this_node == nullptr)
			return false;
		if (val == this_node->val)
			return true;
		if (val < this_node->val)//去左边找
		{
			return query_Operator(this_node->left, val);
		}
		else
		{
			return query_Operator(this_node->right, val);
		}
	}
	bool query(const T& val)//递归实现BST的查询
	{
		return query_Operator(root, val);
	}

测试如下:
在这里插入图片描述

BST树的遍历

请见我的博客:
二叉树的四种遍历方式(递归与非递归实现)

BST树的删除

二叉搜索树的删除操作分三种情况讨论:

  1. 如果待删除的节点是叶子节点,那么可以立即被删除,如下图所示:
    例:删除数据为16的节点,是叶子节点,可以直接删除
    在这里插入图片描述
  2. 如果有一个子节点,要将下一个子节点上移到当前节点,即替换之
    例:删除数据为25的节点,它下面有唯一一个子节点35, 上移到替换之
    在这里插入图片描述
  3. 如果有两个子节点,则将其右子树的最小数据代替此节点的数据,并将其右子树的最小数据删除,如下图所示
    例:删除节点数据为5的节点,找到被删除节点右子树的最小节点。需要一个临时变量successor,将11节点下面的子节点进行查询,找到右子树最小节点7,并把右子树最小节点7替换被删除节点,维持二叉树结构。如下图所示:
    在这里插入图片描述
    对于当前有序序列的BST树,如下所示:
    在这里插入图片描述

BST删除的非递归版本

//非递归实现BST的删除
	void nondelete_val(const T& val)
	{
		if (root == nullptr)
			return;
		BSTNode* pre = nullptr;
		BSTNode* p = root;
		while (p != nullptr)
		{
			if (p->val == val)
				break;
			if (val < p->val)//去左边找
			{
				pre = p;
				p = p->left;
			}
			else
			{
				pre = p;
				p = p->right;
			}
		}
		if (p == nullptr)
		{
			return;//没有找到
		}
		//如果 p不为空,那么他现在指向这个被删除的节点,pre是他爸
		if (p->left != nullptr && p->right != nullptr)//两个孩子
		{
			//这里使用前驱节点来 代替当前节点
			BSTNode* old_p = p;
			pre = p;
			p = p->left;//去其左孩子上 找到前驱节点
			while (p->right != nullptr)
			{
				pre = p;//pre一直是p爸
				p = p->right;
			}
			//此时p就指向了这个前驱节点,肯定没有右子树,可能有左孩子
			//也有可能这个p是一个叶子节点
			old_p->val = p->val;
			//值交换了,于是此时的前驱节点成了被删除的对象
			//这就集中地把 第三种情况统一到 第1 2种上面了
		}
		//于是接下来处理的就是 前两种情况:叶子节点或者一个孩子
		BSTNode* child = p->left;//这里设置左孩子,是防止第3种情况
		if (child == nullptr)
		{
			//如果是第三种下来的话,说明前驱节点是一个叶子节点,当做情况1处理
			child = p->right;
			//设置成右孩子,对于1 3都是空的;对于2来说 child确实是不空的那个孩子
		}
		//在第三种中,即使是根节点 也做了替换值,当然pre就不为空了
		if (pre == nullptr)
		{
			//这是对于只有一个孩子的情况
			root = child;//把不空的孩子 连上去
		}
		else
		{
			//此时就是 删除的不是根节点的情况
			if (pre->left == p)
			{
				pre->left = child;
			}
			else
			{
				pre->right = child;
			}
		}
		//接下来就可以把p指向的现在的这个节点给删除了
		delete p;
	}

BST删除的递归版本

	BSTNode* delete_val_Operator(BSTNode* this_node, const T& val)
	{
		if (this_node == nullptr)
			return nullptr;
		if (val < this_node->val)//在左边
		{
			this_node->left = delete_val_Operator(this_node->left, val);
		}
		else if (this_node->val < val)
		{
			this_node->right = delete_val_Operator(this_node->right, val);
		}
		else//找到了
		{
			if (this_node->left != nullptr && this_node->right != nullptr)
			{
				//两个孩子的情况
				BSTNode* p = this_node;
				p = p->left;//去左子树找前驱节点
				while (p->right != nullptr)
				{
					p = p->right;
				}
				//此时p就指向了这个前驱节点,肯定没有右子树,可能有左孩子
				//也有可能这个p是一个叶子节点
				this_node->val = p->val;//交换值了

				//于是下面就可以把第3种情况 合并到第1 2 上面了
				//单纯的就变成这个节点 左子树上的前驱节点的删除
				this_node->left = delete_val_Operator(this_node->left, p->val);

			}	//下面就是处理 一个孩子节点 和 叶子节点的情况
			else if (this_node->left != nullptr)
			{
				//说明 若是有孩子节点 也是左孩子
				return this_node->left;
			}
			else if (this_node->right != nullptr)
			{
				//把右孩子直接给其父节点 
				return this_node->right;
			}
			else
			{
				//这是叶子节点
				return nullptr;
			}
		}
		return this_node;
	}
	void delete_val(const T& val)//递归实现BST的删除
	{
		root = delete_val_Operator(root, val);
	}

测试如下:
在这里插入图片描述
2019年9月12日02:03:30

猜你喜欢

转载自blog.csdn.net/weixin_43949535/article/details/100745879
BST