【树表的相关查找算法】-----二叉排序树

1.树表的查找与二叉排序树的相关概念

1.1为什么要用到动态查找表(即几种特殊的树)

在这里插入图片描述

1.2二叉排序树的相关概念

1.2.1二叉排序树的递归定义

在这里插入图片描述
注意,二叉排序树的严格定义应是
左子树小于根结点,右子树大于根结点

这里的定义是:右子树大于等于根结点,实际上不是最经典的二叉排序树的定义,可根据实际情况灵活选择

1.2.2几个具体实例如下

在这里插入图片描述

  • 如图左边这棵二叉排序树是按照数字大小进行排序的。

(严格按照左子树小于根结点,右子树大于(等于)根结点)

  • 同理右边这棵二叉排序树是按照字母的ASCII表大小进行排序的。

两个非二叉排序树的示例如下:
在这里插入图片描述
如图,明显我们可以得知结点48大于整棵树的根结点45,根据二叉排序树的定义,结点48应该存储在右子树中,故这是一颗非二叉排序树

在这里插入图片描述
同理,bang中b大于cao中的c,而bang结点应该存储在右子树中,故这是一颗非二叉排序树

1.2.3二叉排序树的性质

在这里插入图片描述
比如中序遍历下图这颗二叉排序树:
在这里插入图片描述

我们就可以获得一个递增有序序列:
在这里插入图片描述

1.2.4二叉排序树的存储结构:

//二叉排序树的结构定义(二叉链表):

#define KeyType int
//二叉排序树中结点中  关键字 的数据类型

//1.定义结点结构体
typedef struct
{
	KeyType key;//定义关键字域

	//同样可以定义其他数据域
}ElemType;
//ElemType:二叉排序树中每个结点的数据类型


//2.定义二叉链表(与二叉树完全一致)
typedef struct BSTNode
{
	ElemType data;//数据域
	struct BSTNode* lchild, * rchild;//左,右孩子指针
}BSTNode ,*BSTree;
//BSTNode:用来定义二叉排序树的结点类型
//BSTree:用来定义二叉排序树

1.3二叉排序树的基础算法

1.3.1二叉排序树的查找算法(递归定义)

1.3.1.1思路与代码分析

返回值:查找成功返回指向该结点的指针,否则返回NULL
参数:T是二叉排序树的指向根结点的指针,key是要查找的关键字

算法思路:
  • (1)递归终止条件:T为空(可能是空树的情况,也可能是没找到待查找结点的情况),或者找到关键字为key的结点,返回该结点指针
    为空时直接返回了NULL,找到相应关键字时返回了对应的地址
  • (2)若key小于当前T结点关键字,则递归查找左子树
  • (3)若key大于当前T结点关键字,则递归查找右子树
代码实现:
//1.二叉排序树的查找算法:
//返回值:查找成功返回指向该结点的指针,否则返回NULL
//参数:T是二叉排序树的指向根结点的指针,key是要查找的关键字
BSTree  SearchBSTree (BSTree T,KeyType key)
{
	//[1]递归终止条件:(*T)为空(可能是空树的情况,也可能是没找到待查找结点的情况),或者找到关键字为key的结点,返回该结点指针
	if (!T || T->data.key == key)
	{
		return T;
	}
	//[2]若key小于当前T结点关键字,则递归查找左子树
	// (二叉查找树的性质:左子树中所有结点小于根结点,右子树中所有结点大于根结点)
	else if (key < T->data.key)
	{
		return SearchBSTree(T->lchild, key);
	}
	//[3]若key大于当前T结点关键字,则递归查找右子树
	// (二叉查找树的性质:左子树中所有结点小于根结点,右子树中所有结点大于根结点)
	else
	{
		return SearchBSTree(T->rchild, key);
	}
}

实例分析如下:
在这里插入图片描述

1.3.1.2算法分析
查找过程分析:
  • 回忆:
    在这里插入图片描述
  • 二叉排序树上查找某关键字等于给定值的结点过程,其实就是走了一条从根到该结点的路径。

于是我们得到以下两个性质:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
比如查找这个35就需要比较4次

二叉排序树的平均查找长度:

在这里插入图片描述
根据上述实例,我们发现 平均查找长度和树的高度有关
在这里插入图片描述

1.3.2二叉排序树的插入算法 与 生成(递归定义)

1.3.2.1思路与代码分析

参数:参数:T 是指向二叉排序树的根结点指针的 二级指针
注意递归调用时,使用(*T)的地址 即&(*T)

算法思路:
  • (1)递归终止条件:若当前根结点为空,则直接建立新结点,作为根结点,并插入数据,初始化左右孩子(指针域置空)
  • (2)若当前待插入结点的值大于根结点,递归查找右子树并进行插入
  • (3)若当前待插入结点的值小于根结点,递归查找左子树并进行插入
  • (4) 若当前待插入结点的值等于根结点,则表示该关键字已经存在,不插入
代码实现:
//2.二叉排序树的插入算法:
//参数:T 是指向二叉排序树的根结点指针的 二级指针
bool InsertBST(BSTree *T, KeyType key)
{
	//[1]若当前根结点为空,则直接建立新结点,作为根结点,并插入数据,初始化左右孩子(指针域置空)
	if (!(*T))
	{
		*T = (BSTNode*)malloc(sizeof(BSTNode));

		if (!(*T))
		{
			printf("内存分配失败!");
			exit(-1);
		}

		(*T)->data.key = key;
		//易错1:注意置空左右孩子的指针域
		(*T)->lchild = NULL;
		(*T)->rchild = NULL;

		//易错2:注意返回条件不能忘记
		return true;
	}
	//[2]若当前待插入结点的值大于根结点,递归查找右子树并进行插入
	else if(key > (*T)->data.key)
	{
		return InsertBST(&(*T)->rchild, key);
	}
	//[3]若当前待插入结点的值小于根结点,递归查找左子树并进行插入
	else if(key < (*T)->data.key)
	{
		return InsertBST(&(*T)->lchild, key);
		//易错:注意传入二级指针:即(*T)的地址
	}
	//[4]若当前待插入结点的值等于根结点,则表示该关键字已经存在,不插入
	else
	{
		return false;
	}
}
1.3.2.2算法分析
生成二叉树的方式------通过插入函数进行操作
  • 从空树出发,经过一系列的查找、插入操作之后,可生成一栋二叉排序树。
  • 插入的元素一定在叶子结点上
    在这里插入图片描述
生成二叉排序方式的分析
  • 一个无序序列可通过构造二叉排序树而变成一个有序序列。
    构造树的过程就是对无序序列进行排序的过程。
  • 插入的结点均为叶子结点,故无需移动其他结点。相当于在有序序列上插入记录而无需移动其他记录。

但是要着重注意:
在这里插入图片描述
在这里插入图片描述
如上图:虽然是同一批关键字序列,但由于插入次序的不同,最终建立的二叉排序树就不同

但是中序遍历两颗树,获得的关键字序列是完全一样的递增有序序列

这里使用了两种不同的插入序列,将同一批关键字序列插入生成两颗二叉排序树,显然这两棵树的形态不同,但其中序遍历的结果是相同的(二叉排序树的性质)

1.4二叉排序树的删除算法

1.4.1删除过程中的注意事项

  • (1)从二叉排序树中删除一个结点,不能把以该结点为根的子树都删去,只能删去该结点
  • (2)并且还应保证删除后所得的二又树仍然满足二叉排序树的性质不变

由于中序遍历二叉排序树可以得到一个递增有序的序列。那么,在二又排序树中删去一个结点相当于删去有序序列中的一个结点。

删除之后应进行的相关处理

在这里插入图片描述

1.4.2删除操作的具体思路如下:

1.4.2.1 被删除的结点是叶子结点:直接删除该结点,注意其双亲的相应指针域应指向空(NULL)

比如对于下列这颗二叉树进行删除结点20和88的操作:

在这里插入图片描述
在这里插入图片描述

1.4.2.2 被删除的结点只有左子树或者只有右子树,用其左子树或者右子树替换它(结点替换)------其双亲结点相应的指针域应指向被 删除结点的左子树 或者右子树

比如对于下列这颗二叉树进行删除结点40和80的操作:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.4.2.3 被删除的结点既有左子树,也有右子树

方法有两种:
在这里插入图片描述
注意这里的删除前驱或者删除后继的操作是递归进行的

比如对于下列这颗二叉树进行删除结点50的操作:
这里使用第一种方法:以中序前驱值替换之

实际上,对于两种方法的使用都有可能会使删除后的二叉排序树高度增高,这可能会造成后续查找时的复杂度变大
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

1.4.3相关代码与算法思路总结

1.4.3.1中序后继法:
1.4.3.1.1思路汇总:
  • (1)递归终止条件:

如果当前树节点为空 (*T 为 NULL),则返回 false,表示没有找到待删除的关键字或树本身为空。

  • (2)递归寻找待删除节点:

  • (2.1)如果待删除的关键字 key 大于当前节点的关键字,则递归到当前节点的右子树进行删除。

  • (2.2)如果待删除的关键字 key 小于当前节点的关键字,则递归到当前节点的左子树进行删除。

  • (3)处理找到的待删除节点:

如果当前节点的关键字等于待删除的关键字,进入处理删除的逻辑

  • (3.1)情况 1:无左右孩子(叶子节点):

释放该节点的内存,并将该节点指针置为空 (NULL)。

  • (3.2)情况 2:只有左孩子:

使用左孩子替代待删除节点,释放待删除节点的内存。

  • (3.3)情况 3:只有右孩子:

使用右孩子替代待删除节点,释放待删除节点的内存。

  • (3.4)核心情况 4:有左右孩子:

使用中序后继节点(即右子树中的最小节点)替代待删除节点。 具体步骤:
【1】从当前节点的右子树开始,寻找最小的节点(即中序后继节点),该节点位于其左子树的最右下角。
【2】将当前节点的数据替换为中序后继节点的数据。
【3】递归删除中序后继节点,注意传入的是右孩子指针的地址,以避免删除已经替换的当前节点。

参数分别为 指向当前二叉排序树的地址的二级指针待删除关键字

//4.1二叉排序树的删除算法:
//(这里使用中序后继法)

bool DeleteBST(BSTree* T, KeyType key)
{
	//[1]递归终止条件:(*T)为空(空树的情况 或者 没找到关键字),返回false
	if (!(*T))
	{
		return false;
	}

	//[2]递归寻找待删除的结点

	//<2.1>若key大于当前结点的关键字,则递归到右子树删除
	else if (key > (*T)->data.key)
	{
		return DeleteBST(&(*T)->rchild, key);
	}

	//<2.2>若key小于当前结点的关键字,则递归到左子树删除
	else if (key < (*T)->data.key)
	{
		return DeleteBST(&(*T)->lchild, key);
	}

	//[3]找到了待删除结点,处理删除的三种情况
	else
	{
		//<3.1> 若待删除结点无左右孩子(叶子结点),直接删除
		// 具体操作:(释放该结点的内存 并 将该结点置空)
		if ((*T)->lchild==NULL&& (*T)->rchild == NULL)
		{
			free(*T);//释放该结点的内存
			(*T) = NULL;// 将该结点置空
			return true;
		}

		//<3.2>若待删除结点只有左孩子,左孩子代替该结点
		//具体操作:定义临时指针指向待删除结点,替换待删除结点,释放临时指针指向的待删除结点的内存
		else if ((*T)->lchild != NULL && (*T)->rchild == NULL)
		{

			BSTNode* DeleteNode = (*T);//定义临时指针指向待删除结点

			(*T) = (*T)->lchild;//用左孩子代替该结点

			free(DeleteNode);//释放该结点内存

			return true;
		}

		//<3.3>若待删除结点只有右孩子,右孩子代替该结点
		//具体操作:定义临时指针指向待删除结点,替换待删除结点,释放临时指针指向的待删除结点的内存
		else if ((*T)->lchild == NULL && (*T)->rchild != NULL)
		{

			BSTNode * DeleteNode = (*T); //定义临时指针指向待删除结点

			(*T) = (*T)->rchild;//用右孩子代替该结点

			free(DeleteNode);//释放该结点内存

			return true;
		}
		
		//<3.4>若待删除结点有左右孩子,选择中序后继(右子树的最小结点)替代
		else if ((*T)->lchild != NULL && (*T)->rchild != NULL)
		{
			//<3.4.1>寻找中序后继结点(右子树的最小结点),用新的临时指针指向这个结点
			//注意:这里必须从(*T)->rchild开始查找,因为中序后继首先要比当前结点要大
			BSTNode* MinNode = (*T)->rchild;


			//这里使用了二叉排序树的性质,右子树的最小结点在 其左子树的最后一个左孩子的位置
			while (MinNode->lchild != NULL)
			{
				MinNode = MinNode->lchild;
			}


			//<3.4.2>用中序后继结点(右子树的最小结点)替代当前结点(只交换内容)
			(*T)->data = MinNode->data;


			//<3.4.3>递归删除中序后继结点
			//注意1:传入的是右孩子指针的地址;
			//因为删除的是右子树最小结点,同时避免了将已经替换了的当前结点误删
			return DeleteBST(&((*T)->rchild), MinNode->data.key);
		}
	}
}
1.4.3.1.2注意事项:

对于待删除结点有左右孩子这种情况

  • (1) 不能从 *T(当前待删除结点)开始查找中序后继的原因是 :
    在这里插入图片描述
  • (2)递归删除中序后继结点时,注意传入的两个参数
    分别是:右孩子指针的地址中序后继结点的关键字
1.4.3.2中序前驱法:
1.4.3.2.1思路汇总:

与中序后继法非常相似,只有待删除结点有左右孩子这种情况的处理与其不同,其余操作与中序后继法完全一致

  • (1)递归终止条件:

如果当前树节点为空 (*T 为 NULL),则返回 false,表示没有找到待删除的关键字或树本身为空。

  • (2)递归寻找待删除节点:

  • (2.1)如果待删除的关键字 key 大于当前节点的关键字,则递归到当前节点的右子树进行删除。

  • (2.2)如果待删除的关键字 key 小于当前节点的关键字,则递归到当前节点的左子树进行删除。

  • (3)处理找到的待删除节点:

如果当前节点的关键字等于待删除的关键字,进入处理删除的逻辑

  • (3.1)情况 1:无左右孩子(叶子节点):

释放该节点的内存,并将该节点指针置为空 (NULL)。

  • (3.2)情况 2:只有左孩子:

使用左孩子替代待删除节点,释放待删除节点的内存。

  • (3.3)情况 3:只有右孩子:

使用右孩子替代待删除节点,释放待删除节点的内存。

  • (3.4)核心情况 4:有左右孩子:

使用中序前驱节点(即左子树中的最大节点)替代待删除节点。 具体步骤:
【1】从当前节点的左子树开始,寻找最大的节点(即中序前驱节点),该节点位于其左子树的最右下角。
【2】将当前节点的数据替换为中序前驱节点的数据。
【3】递归删除中序前驱节点,注意传入的是左孩子指针的地址,以避免删除已经替换的当前节点。

//4.2二叉排序树的删除算法:
//(这里使用中序前驱法)
//这里与中序后继法的唯一区别就是 对 待删除结点为有左右孩子结点的情况,其他部分与中序后继法完全一致
bool DeleteBST1(BSTree* T, KeyType key)
{
	//[1]递归终止条件:(*T)为空(空树的情况 或者 没找到关键字),返回false
	if (!(*T))
	{
		return false;
	}

	//[2]递归寻找待删除的结点

	//<2.1>若key大于当前结点的关键字,则递归到右子树删除
	else if (key > (*T)->data.key)
	{
		return DeleteBST(&(*T)->rchild, key);
	}

	//<2.2>若key小于当前结点的关键字,则递归到左子树删除
	else if (key < (*T)->data.key)
	{
		return DeleteBST(&(*T)->lchild, key);
	}

	//[3]找到了待删除结点,处理删除的三种情况
	else
	{
		//<3.1> 若待删除结点无左右孩子(叶子结点),直接删除
		// 具体操作:(释放该结点的内存 并 将该结点置空)
		if ((*T)->lchild == NULL && (*T)->rchild == NULL)
		{
			free(*T);//释放该结点的内存
			(*T) = NULL;// 将该结点置空
			return true;
		}

		//<3.2>若待删除结点只有左孩子,左孩子代替该结点
		//具体操作:定义临时指针指向待删除结点,替换待删除结点,释放临时指针指向的待删除结点的内存
		else if ((*T)->lchild != NULL && (*T)->rchild == NULL)
		{

			BSTNode* DeleteNode = (*T);//定义临时指针指向待删除结点

			(*T) = (*T)->lchild;//用左孩子代替该结点

			free(DeleteNode);//释放该结点内存

			return true;
		}

		//<3.3>若待删除结点只有右孩子,右孩子代替该结点
		//具体操作:定义临时指针指向待删除结点,替换待删除结点,释放临时指针指向的待删除结点的内存
		else if ((*T)->lchild == NULL && (*T)->rchild != NULL)
		{

			BSTNode* DeleteNode = (*T); //定义临时指针指向待删除结点

			(*T) = (*T)->rchild;//用右孩子代替该结点

			free(DeleteNode);//释放该结点内存

			return true;
		}


		//<3.4>若待删除结点有左右孩子,选择中序前驱(左子树的最大结点)替代
		else if ((*T)->lchild != NULL && (*T)->rchild != NULL)
		{
			//<3.4.1>寻找中序前驱结点(右子树的最小结点),用新的临时指针指向这个结点
			//注意:这里必须从(*T)->lchild开始查找,因为中序后前驱首先要比当前结点要小
			BSTNode* MaxNode = (*T)->lchild;


			//这里使用了二叉排序树的性质,左子树的最大结点在 其左子树的最后一个右孩子的位置
			while (MaxNode->rchild != NULL)
			{
				MaxNode = MaxNode->rchild;
			}


			//<3.4.2>用中序前驱结点(左子树的最大结点)替代当前结点(只交换内容)
			(*T)->data = MaxNode->data;


			//<3.4.3>递归删除中序前驱结点
			//注意1:传入的是左孩子指针的地址;
			//因为删除的是左子树最小结点,同时避免了将已经替换了的当前结点误删
			return DeleteBST(&((*T)->lchild), MaxNode->data.key);
		}
	}
}

1.4.4删除操作情况汇总:

在这里插入图片描述

1.5整体操作代码一览:

#define _CRT_SECURE_NO_WARNINGS 1
//二叉排序树的相关操作
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

//二叉排序树的结构定义(二叉链表):

#define KeyType int
//二叉排序树中结点中  关键字 的数据类型

//1.定义结点结构体(实际上就是数据域)
typedef struct
{
	KeyType key;//定义关键字域

	//同样可以定义其他数据域
}ElemType;
//ElemType:二叉排序树中每个结点的数据类型


//2.定义二叉链表(与二叉树完全一致)
typedef struct BSTNode
{
	ElemType data;//数据域
	struct BSTNode* lchild, * rchild;//左,右孩子指针
}BSTNode ,*BSTree;
//BSTNode:用来定义二叉排序树的结点类型
//BSTree:用来定义二叉排序树



//1.二叉排序树的查找算法:
//返回值:查找成功返回指向该结点的指针,否则返回NULL
//参数:T是二叉排序树的指向根结点的指针,key是要查找的关键字
BSTree  SearchBSTree (BSTree T,KeyType key)
{
	//[1]递归终止条件:(*T)为空(可能是空树的情况,也可能是没找到待查找结点的情况),或者找到关键字为key的结点,返回该结点指针
	if (!T || T->data.key == key)
	{
		return T;
	}
	//[2]若key小于当前T结点关键字,则递归查找左子树
	// (二叉查找树的性质:左子树中所有结点小于根结点,右子树中所有结点大于根结点)
	else if (key < T->data.key)
	{
		return SearchBSTree(T->lchild, key);
	}
	//[3]若key大于当前T结点关键字,则递归查找右子树
	// (二叉查找树的性质:左子树中所有结点小于根结点,右子树中所有结点大于根结点)
	else
	{
		return SearchBSTree(T->rchild, key);
	}
}


//2.二叉排序树的插入算法:
//参数:T 是指向二叉排序树的根结点指针的 二级指针
bool InsertBST(BSTree *T, KeyType key)
{
	//[1]若当前根结点为空,则直接建立新结点,作为根结点,并插入数据,初始化左右孩子(指针域置空)
	if (!(*T))
	{
		*T = (BSTNode*)malloc(sizeof(BSTNode));

		if (!(*T))
		{
			printf("内存分配失败!");
			exit(-1);
		}

		(*T)->data.key = key;
		//易错1:注意置空左右孩子的指针域
		(*T)->lchild = NULL;
		(*T)->rchild = NULL;

		//易错2:注意返回条件不能忘记
		return true;
	}
	//[2]若当前待插入结点的值大于根结点,递归查找右子树并进行插入
	else if(key > (*T)->data.key)
	{
		return InsertBST(&(*T)->rchild, key);
	}
	//[3]若当前待插入结点的值小于根结点,递归查找左子树并进行插入
	else if(key < (*T)->data.key)
	{
		return InsertBST(&(*T)->lchild, key);
		//易错:注意传入二级指针:即(*T)的地址
	}
	//[4]若当前待插入结点的值等于根结点,则表示该关键字已经存在,不插入
	else
	{
		return false;
	}
}



//3.二叉排序树的中序遍历:(复习)
//由于二叉排序树的性质:中序遍历二叉排序树,能够获得一个递增有序序列
bool PrintBSTree(BSTree T)
{
	//[1] 返回条件:子树为空,则返回true;
	if (!T)
	{
		return true;
	}

	//[2]递归遍历左子树
	PrintBSTree(T->lchild);

	//[3]访问根结点
	printf("%d ", T->data.key);

	//[4]递归遍历右子树
	PrintBSTree(T->rchild);

}

//4.1二叉排序树的删除算法:
//(这里使用中序后继法)

bool DeleteBST(BSTree* T, KeyType key)
{
	//[1]递归终止条件:(*T)为空(空树的情况 或者 没找到关键字),返回false
	if (!(*T))
	{
		return false;
	}

	//[2]递归寻找待删除的结点

	//<2.1>若key大于当前结点的关键字,则递归到右子树删除
	else if (key > (*T)->data.key)
	{
		return DeleteBST(&(*T)->rchild, key);
	}

	//<2.2>若key小于当前结点的关键字,则递归到左子树删除
	else if (key < (*T)->data.key)
	{
		return DeleteBST(&(*T)->lchild, key);
	}

	//[3]找到了待删除结点,处理删除的三种情况
	else
	{
		//<3.1> 若待删除结点无左右孩子(叶子结点),直接删除
		// 具体操作:(释放该结点的内存 并 将该结点置空)
		if ((*T)->lchild==NULL&& (*T)->rchild == NULL)
		{
			free(*T);//释放该结点的内存
			(*T) = NULL;// 将该结点置空
			return true;
		}

		//<3.2>若待删除结点只有左孩子,左孩子代替该结点
		//具体操作:定义临时指针指向待删除结点,替换待删除结点,释放临时指针指向的待删除结点的内存
		else if ((*T)->lchild != NULL && (*T)->rchild == NULL)
		{

			BSTNode* DeleteNode = (*T);//定义临时指针指向待删除结点

			(*T) = (*T)->lchild;//用左孩子代替该结点

			free(DeleteNode);//释放该结点内存

			return true;
		}

		//<3.3>若待删除结点只有右孩子,右孩子代替该结点
		//具体操作:定义临时指针指向待删除结点,替换待删除结点,释放临时指针指向的待删除结点的内存
		else if ((*T)->lchild == NULL && (*T)->rchild != NULL)
		{

			BSTNode * DeleteNode = (*T); //定义临时指针指向待删除结点

			(*T) = (*T)->rchild;//用右孩子代替该结点

			free(DeleteNode);//释放该结点内存

			return true;
		}
		
		//<3.4>若待删除结点有左右孩子,选择中序后继(右子树的最小结点)替代
		else if ((*T)->lchild != NULL && (*T)->rchild != NULL)
		{
			//<3.4.1>寻找中序后继结点(右子树的最小结点),用新的临时指针指向这个结点
			//注意:这里必须从(*T)->rchild开始查找,因为中序后继首先要比当前结点要大
			BSTNode* MinNode = (*T)->rchild;


			//这里使用了二叉排序树的性质,右子树的最小结点在 其左子树的最后一个左孩子的位置
			while (MinNode->lchild != NULL)
			{
				MinNode = MinNode->lchild;
			}


			//<3.4.2>用中序后继结点(右子树的最小结点)替代当前结点(只交换内容)
			(*T)->data = MinNode->data;


			//<3.4.3>递归删除中序后继结点
			//注意1:传入的是右孩子指针的地址;
			//因为删除的是右子树最小结点,同时避免了将已经替换了的当前结点误删
			return DeleteBST(&((*T)->rchild), MinNode->data.key);
		}
	}
}

//4.2二叉排序树的删除算法:
//(这里使用中序前驱法)
//这里与中序后继法的唯一区别就是 对 待删除结点为有左右孩子结点的情况,其他部分与中序后继法完全一致
bool DeleteBST1(BSTree* T, KeyType key)
{
	//[1]递归终止条件:(*T)为空(空树的情况 或者 没找到关键字),返回false
	if (!(*T))
	{
		return false;
	}

	//[2]递归寻找待删除的结点

	//<2.1>若key大于当前结点的关键字,则递归到右子树删除
	else if (key > (*T)->data.key)
	{
		return DeleteBST(&(*T)->rchild, key);
	}

	//<2.2>若key小于当前结点的关键字,则递归到左子树删除
	else if (key < (*T)->data.key)
	{
		return DeleteBST(&(*T)->lchild, key);
	}

	//[3]找到了待删除结点,处理删除的三种情况
	else
	{
		//<3.1> 若待删除结点无左右孩子(叶子结点),直接删除
		// 具体操作:(释放该结点的内存 并 将该结点置空)
		if ((*T)->lchild == NULL && (*T)->rchild == NULL)
		{
			free(*T);//释放该结点的内存
			(*T) = NULL;// 将该结点置空
			return true;
		}

		//<3.2>若待删除结点只有左孩子,左孩子代替该结点
		//具体操作:定义临时指针指向待删除结点,替换待删除结点,释放临时指针指向的待删除结点的内存
		else if ((*T)->lchild != NULL && (*T)->rchild == NULL)
		{

			BSTNode* DeleteNode = (*T);//定义临时指针指向待删除结点

			(*T) = (*T)->lchild;//用左孩子代替该结点

			free(DeleteNode);//释放该结点内存

			return true;
		}

		//<3.3>若待删除结点只有右孩子,右孩子代替该结点
		//具体操作:定义临时指针指向待删除结点,替换待删除结点,释放临时指针指向的待删除结点的内存
		else if ((*T)->lchild == NULL && (*T)->rchild != NULL)
		{

			BSTNode* DeleteNode = (*T); //定义临时指针指向待删除结点

			(*T) = (*T)->rchild;//用右孩子代替该结点

			free(DeleteNode);//释放该结点内存

			return true;
		}


		//<3.4>若待删除结点有左右孩子,选择中序前驱(左子树的最大结点)替代
		else if ((*T)->lchild != NULL && (*T)->rchild != NULL)
		{
			//<3.4.1>寻找中序前驱结点(右子树的最小结点),用新的临时指针指向这个结点
			//注意:这里必须从(*T)->lchild开始查找,因为中序后前驱首先要比当前结点要小
			BSTNode* MaxNode = (*T)->lchild;


			//这里使用了二叉排序树的性质,左子树的最大结点在 其左子树的最后一个右孩子的位置
			while (MaxNode->rchild != NULL)
			{
				MaxNode = MaxNode->rchild;
			}


			//<3.4.2>用中序前驱结点(左子树的最大结点)替代当前结点(只交换内容)
			(*T)->data = MaxNode->data;


			//<3.4.3>递归删除中序前驱结点
			//注意1:传入的是左孩子指针的地址;
			//因为删除的是左子树最小结点,同时避免了将已经替换了的当前结点误删
			return DeleteBST(&((*T)->lchild), MaxNode->data.key);
		}
	}
}




int main()
{
	//注意在主函数中创建二叉排序树时,可以使用插入算法进行创建
	//易错:但是要注意首先将T置空(NULL)以便插入算法进行时能正确判断空树
	BSTree T=NULL;

	//按照:3 12 24 37 45 53 61 78 90 100
	//建立一颗二叉排序树,应该只含有右子树,(因为创建序列为升序)
	InsertBST(&T, 3);
	InsertBST(&T, 12);
	InsertBST(&T, 24);
	InsertBST(&T, 37);
	InsertBST(&T, 45);
	InsertBST(&T, 53);
	InsertBST(&T, 61);
	InsertBST(&T, 78);
	InsertBST(&T, 90);
	InsertBST(&T, 100);
	

	printf("T:");
	PrintBSTree(T);



	printf("\n");

	BSTree T1 = NULL;

	//使用不同的插入序列,但相同的关键字序列,生成一颗二叉排序树并中序遍历
	InsertBST(&T1, 100);
	InsertBST(&T1, 3);
	InsertBST(&T1, 37);
	InsertBST(&T1, 24);
	InsertBST(&T1, 45);
	InsertBST(&T1, 53);
	InsertBST(&T1, 61);
	InsertBST(&T1, 78);
	InsertBST(&T1, 90);
	InsertBST(&T1, 12);

	printf("T1:");
	PrintBSTree(T1);

	printf("\n\n");


	//删除测试:

	//中序后继法删除结点测试
	BSTree T3 = NULL; // 初始化为空树

	// 插入多个结点
	InsertBST(&T3, 50);
	InsertBST(&T3, 30);
	InsertBST(&T3, 20);
	InsertBST(&T3, 40);
	InsertBST(&T3, 70);
	InsertBST(&T3, 60);
	InsertBST(&T3, 80);

	printf("T3:");
	PrintBSTree(T3);

	printf("\n");


	DeleteBST(&T3, 20);  // 删除叶子结点

	printf("T3:");
	PrintBSTree(T3);

	printf("\n");

	DeleteBST(&T3, 30);  // 删除只有一个孩子的结点

	printf("T3:");
	PrintBSTree(T3);

	printf("\n");


	DeleteBST(&T3, 50);  // 删除有两个孩子的结点

	printf("T3:");
	PrintBSTree(T3);

	printf("\n");

	printf("\n\n");
	//中序前驱法删除结点测试
	BSTree T4 = NULL; // 初始化为空树

	// 插入多个结点
	InsertBST(&T4, 50);
	InsertBST(&T4, 30);
	InsertBST(&T4, 20);
	InsertBST(&T4, 40);
	InsertBST(&T4, 70);
	InsertBST(&T4, 60);
	InsertBST(&T4, 80);

	printf("T4:");
	PrintBSTree(T4);

	printf("\n");


	DeleteBST(&T4, 20);  // 删除叶子结点

	printf("T4:");
	PrintBSTree(T4);

	printf("\n");

	DeleteBST(&T4, 30);  // 删除只有一个孩子的结点

	printf("T4:");
	PrintBSTree(T4);

	printf("\n");


	DeleteBST(&T4, 50);  // 删除有两个孩子的结点

	printf("T4:");
	PrintBSTree(T4);

	printf("\n");

	return 0;
}

在这里插入图片描述
注意这里两个删除算法可能最终留下的二叉排序树结构是不一样的,
只不过中序遍历的结果是完全相同的(二叉排序树的性质)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值