算法_二叉排序树(BST)总结及其删除操作实现细节剖析

二叉排序树的总结

定义

二叉排序树:或者是一棵空树,或者是一棵满足BST性质的二叉树。
BST性质:
1)如果根结点有左子树,则左子树的所有结点的关键字值都小于根结点的;
2)如果根结点有右子树,则右子树的所有结点的关键字值都大于根结点的;
3)左、右子树都是二叉排序树。


注意
  1. 可能有人会问,为什么通常称BST为二叉排序树而不是二叉搜索树?
    原因是:BST的创建过程的本质是对元素序列的排序。对一棵BST进行中序遍历所得序列一定是递增的。

  2. 值得注意的是:有人会把折半查找的判定树二叉排序树搞混。
    需要清楚两者之间的联系与区别:
    1)联系:容易混淆两者是因为它们的关键字的排序规则是一样的——左子树的关键字小于根,右子树的关键字大于根。而造成这种联系的原因又与他们的区别有关。
    2)区别:
    判定树由有序查找表构造:判定树是对折半查找过程的描述,而折半查找要求查找表是有序的,因此由于二分查找的本质以及查找表的有序性,就决定了判定树的左子树比根结点小,右子树比根结点大,因此其中序遍历序列是有序的;
    二叉排序树在构造过程中对查找表排序:二叉排序树的查找表不一定是有序的,但是因为其创建过程是要符合BST性质的,也就是对查找表进行排序,因此构造出来的一棵二叉排序树的中序遍历序列是有序的。


BST删除操作实现细节剖析

二叉排序树的删除操作要求执行删除操作后仍满足BST性质。
将删除操作分类:

  1. 删除叶子结点
  2. 删除无右子树的结点
  3. 删除无左子树的结点
  4. 删除有左右子树的结点

我们知道,链式存储结构的删除操作的核心就是找到删除结点的前驱,而对于二叉排序树的删除操作我们利用C++中的引用类型来实现这一目的。

代码实现:
BST结点声明:

typedef struct node
{
	KeyType key;
	InfoType info;
	struct node *lchild;
	struct node *rchild;
}BSTNode;

删除操作分2步:

  1. 查找
bool delete_bst(BSTNode * &bt, KeyType k)
{
	if (bt == NULL)
	{
		return false;
	}
	else if (k == bt->key)
	{
		delete1(bt);
		return true;
	}
	else if (k < bt->key)
		return delete_bst(bt->lchild, k);
	else
		return delete_bst(bt->rchild, k);
}
  1. 删除
void delete1(BSTNode * &p)
{
	BSTNode *q;
	if (p->rchild == NULL)
	{
		q = p;
		p = p->lchild;
		free(q);
	}
	else if (bt->lchild == NULL)
	{
		q = p;
		p = p->rchild;
		free(q);
	}
	else
		delete2(p, p->lchild);
}

void delete2(BSTNode *p, BSTNode * &r)
{
	BSTNode *q;
	if (r->rchild != NULL)
		delete2(p, r->rchild);
	else
	{
		q = r;
		p->key = r->key;
		p->data = r->data;
		r = r->lchild;
		free(q);
	}
}

剖析:
参数传递只有两种方式,一种是传引用、一种是传值(传指针也属于传值类型)。
引用只是一个别名,因此对引用的操作都是在对原对象操作。
举个例子:
如果我们执行查找函数:

delete_bst(bt->lchild, k);

bool delete_bst(BSTNode * &p, KeyType k)
函数的第一个形参是引用类型,这意味着我们传进去的是bt指针所指结点的lchild成员(lchild成员存放着其后继结点的地址),在函数内对形参p的值的修改本质上是对bt指针所指结点的lchild成员的值的修改。简单的说,当我们调用一个以引用类型作为形参的函数的时候,我们实质是让函数给我们传进去的实参创建了一个别名,在函数内对别名的操作都会作用在实参上
因此,如果形参p的key等于k,那么执行删除操作(注意此时形参p只是实参bt->child的别名),即执行删除函数:

delete1函数的声明:
void delete1(BSTNode * &p)

delete1(p);	//p是bt->lchild的别名而已

如果删除情况刚好是p->rchild为空的情况,那么函数内会执行语句块:

BSTNode *q;
if (p->rchild == NULL)
	{
		q = p;
		p = p->lchild;
		free(q);
	}

为了直观,我们直接用最开始的实参bt->lchild替换别名p,则代码是这样的:

BSTNode *q;
if (bt->lchild->rchild == NULL)
	{
		q = bt->lchild;
		bt->lchild = bt->lchild->lchild;
		free(q);
	}

内存图:
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值