删除BST的节点

BST是什么?

二叉搜索树BST(Binary Search Tree),又称为二叉排序树。

该树要么是一个空树,要么具有以下性质

序号BST的性质
1左树不空,则左树中所有节点中的值都小于根节点的值
2右树不空,则右树中所有节点中的值都大于根节点的值
3左右子树也是BST

下图中的树便是一棵BST:
在这里插入图片描述

BST的节点插入

创建二叉搜索树比较简单,首先我们定义出二叉搜索树的节点类模板BSTNode,该类应具有三个成员,分别是左孩子指针leftchild,右孩子指针rightchild,节点值val。如下:

template<Type>
class BSTNode {
	friend class BSTree<Type>;
public:
	BSTNode() :val(Type()), leftchild(nullptr), rightchild(nullptr) {}
	BSTNode(Type _val, BSTNode* _left = nullptr, BSTNode* _right = nullptr) :
		val(_val),leftchild(_left),rightchild(_right){}
	~BSTNode(){}
private:
	BSTNode* leftchild;
	BSTNode* rightchild;
	Type val;
};

这仅仅是一个二叉搜索树的节点类,我们的目的是创建二叉搜索树。

二叉搜索树的类模板BSTree应该要能访问BSTNodeprivate成员,所以要将BSTree添加为BSTNode友元类

不想添加友元类也行,有两种做法:
1.用struct构造BSTNode
2.将BSTNodeprivate成员改为public

接下来就要创建BSTree类,我们继续用模板写:

template<class Type>
class BSTree {
public:
	BSTree() :root(nullptr){}
	BSTree(vector<Type>& v):root(nullptr) {
		for (const auto& e : v)
			Insert(e);
	}
public:
	//此处只暴露对外调用接口,真正的方法被保护
	bool Insert(const Type& data){ return Insert(root,data); }//新增节点
	bool Remove(const Type& key){ return Remove(root, key); }//删除节点
protected:
	bool Insert(BSTNode<Type>*& root, const Type& data);
	bool Remove(BSTNode<Type>*& root, const Type& key);
	}
private:
	BSTNode<Type>* root;
};

用一个栗子,简单解释一下二叉搜索树的插入操作。

现需要用{50,30,40,10,70,80,2,60,90}创建二叉搜索树,过程如下:
在这里插入图片描述

按照上面的思路,可以轻易写出真正的插入接口(递归):

bool Insert(BSTNode<Type>*& root, const Type& data) {
		if (!root) {//节点为空
			root = new BSTNode<Type>(data);
			return true;
		}
		if (data > root->val)//插入值大于本节点值,说明应插入右树
			Insert(root->rightchild, data);
		else if (data < root->val)//插入值小于本节点值,说明应插入右树
			Insert(root->leftchild, data);
		return false;//能运行到此处,说明插入值与本节点值相同,不接受插入重复值,返回插入失败
	}

BST的节点删除

单纯删除二叉搜索树的节点,而不进行后续调整,有可能会破坏树的结构。

如下图,
直接删除50,会使下面的树散架,直接丢失整棵树;
直接删除10,会丢失2;
直接删除80,会丢失90;
直接删除60,谁也不会丢,符合预期。
在这里插入图片描述

因此要是直接删除,不做调整,仅仅适合删除叶子节点。

我们编写代码,要尽量高内聚,低耦合,也就是说,要编写删除节点的函数,你就把删除的各种情况都写在这一个函数里,让一个函数可以完成各类节点删除的工作。

所以删除节点就有以下四类情况:
1. 删除目标左右均有
2. 删除目标有左无右
3. 删除目标有右无左
4. 删除目标无左无右

删除工作完成后,树依然还要是二叉搜索树

删除目标无左无右

设我们现在删除目标是90,那我们直接删除即可。
在这里插入图片描述
在这里插入图片描述
二叉搜索树的结构没被破坏,符合预期

删除目标无左有右

删除目标无左有右的情况下,我们只需要把删除目标的右孩子迁至删除目标处即可,如下:
在这里插入图片描述

在这里插入图片描述
注意!注意!注意!此处的说的“迁移”是迁移节点(也就是修改指针)。并不是把节点的值赋值进删除目标节点!!!

具体实现时就如下图,修改70的右指针rightchild指向90,再删除80即可。
在这里插入图片描述

如果采用赋值方式,倒也不是不可以,就是需要将80的右树所有节点进行迁移才行,比较麻烦。

二叉搜索树的结构没被破坏,符合预期

删除目标无右有左

情况与目标无左有右很类似,我们只需要把删除目标的左孩子迁至删除目标处即可,如下:
在这里插入图片描述在这里插入图片描述
二叉搜索树的结构没被破坏,符合预期

删除目标左右均有

这种情况下有两种解决方案:
1.找到删除目标左树中的最大值,进行值覆盖(绿色)
2.找到删除目标右树中的最小值,进行值覆盖(蓝色)

当值覆盖完毕后,这种类型的删除就转化成了删除叶子节点!!
一下简单了很多有没有?
在这里插入图片描述在这里插入图片描述

按这种思路,我们也就能写出真正的删除接口了(递归)

bool Remove(BSTNode<Type>*& root, const Type& key) {
		//先找到节点
		if (!root)//不存在
			return false;
		if (key > root->val)//进右树找
			return Remove(root->rightchild, key);
		else if (key < root->val)//进左树找
			return Remove(root->leftchild, key);
		else {//找到
			BSTNode<Type>* p = nullptr;
			if (root->leftchild && root->rightchild) {//存在左右子树
				p = root->leftchild;//进左树
				while (p->rightchild) //找最大值
					p = p->rightchild;
				root->val = p->val;//值覆盖
				Remove(root->leftchild, p->val);//删除左树的最大值
			}
			else {//有左无右,有右无左,无左无右
				p = root;//保存删除目标
				if (root->leftchild)//将左树迁移到本节点
					root = root->leftchild;
				else//将右树迁移到本节点
					root = root->rightchild;
				delete p;//删除目标
			}
			return true;
		}
	}

结语

最后,要是想要用迭代(非递归)方法写删除,会略微麻烦一些,我们需要找到删除目标的父节点pr去修改父节点的指针指向(leftchild,rightchild),实现如下:

bool Remove(BSTNode<Type>*& t, const Type& key) {
		if (!t)//空树
			return false;
		BSTNode<Type>* p = t, * pr = nullptr;//pr为p的父节点
		while (p)//寻找删除目标
		{
			if (key == p->val)//找到
				break;
			pr = p;//父节点更新
			if (key < p->val)//进左树寻找
				p = p->leftchild;
			else//进右树寻找
				p = p->rightchild;
		}

		if (p->leftchild && p->rightchild) {//左右均有
			BSTNode<Type>* q = p->leftchild;//进左树
			while (q->rightchild) {//找左树的最大值
				pr = q;
				q = q->rightchild;
			}
			p->val = q->val;//值覆盖
			p = q;//转化为删除叶子节点
		}

		if (!pr)//没有父节点,说明删除目标是整棵树的根节点
		{
			if (p->leftchild)//左树存在
				t = p->leftchild;//左树迁移
			else
				t = p->rightchild;//右树迁移
		}
		else {
			if (pr->leftchild == p) {//删除目标是父节点的左孩子
				if (p->leftchild)//有左无右
					pr->leftchild = p->leftchild;
				else//有右无左
					pr->leftchild = p->rightchild;
			}
			else {//删除节点是父节点的右孩子
				if (p->leftchild)//有左无右
					pr->rightchild = p->leftchild;
				else//有右无左
					pr->rightchild = p->rightchild;
			}
		}
		delete p;//删除目标
		return true;
	}
  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值