二叉排序树(Binary Search Tree)

二叉排序树的定义

二叉排序树,又称二叉查找树,我们这样定义:树非空,对于任意结点存在左子树或右子树,则左子树上所有结点的关键字均小于该结点的关键字,右子树上所有结点的关键字均大于该节点的关键字,这样的二叉树叫二叉排序树。即:

左子树结点值<根结点值<右子树结点值

还有一个默认规定,在二叉排序树中,不能有两个结点关键字相同。

二叉排序树的查找

定义一个二叉树的结构:

typedef struct BSTNode {
	int data;
	struct BSTNode* lchild;
	struct BSTNode* rchild;
}BSTNode,* BSTree;

查找算法的基本思路是,从根节点开始,目标值更小的往左找,目标值更达的往右找,知道目标值和结点值相同,返回该结点。通过递归的方式实现:

BSTNode* Search(BSTree T, int k) {
	if (T == NULL)
		return NULL;
	if (k == T->data)
		return T;
	else if (k < T->data)
		return Search(T->lchild, k);
	else 
		return Search(T->rchild, k);
}

二叉排序树的插入

插入算法的设计思路与查找类似,采用递归的方式,将待插入结点的关键字值与树中各个结点的关键字值进行比较,若小于则向左走,若大于则向右走,知道找到一个空结点,将新结点插入。从算法的实现方式上,我们可以知道,插入结点一定都是叶子结点。下面是算法实现:

Status BST_Insert(BSTree* T, int k) {
	if ((*T) == NULL) {
		(*T) = (BSTree)malloc(sizeof(BSTNode));
		if (!(*T))exit(OVERFLOW);
		(*T)->data = k;
		(*T)->lchild = (*T)->rchild = NULL;
		return OK;//插入成功
	}
	else if (k == (*T)->data)return ERROR;//插入失败
	else if (k < (*T)->data)return BST_Insert(&(*T)->lchild, k);
	else return BST_Insert(&(*T)->rchild, k);
}

二叉排序树的删除

删除算法的实现较为复杂,主要需要考虑结点的类型,来分门别类进行讨论。首先分析,一个结点会有三种情况:

1.该结点是叶子结点,那么可以直接删除;

2.结点只含有左子树或右子树,在删除该节点后,用其子树代替其在原先的位置;

3.结点既含有左子树,有含有右子树,这里有两种处理方法。在待删除结点的位置,可以用待删除结点的后继结点来替代,然后删除后继结点;或者是用待删除的结点的前驱结点来替代,然后删除前驱结点。关于前驱后继结点的位置问题,我们通过中序遍历二叉树的性质,可以知道,在这种情况下,待删除结点的后继是该结点的右子树的最左下结点,前驱是该结点的左子树的最右下结点。

具体代码如下:

Status Delete(BSTree* p)
{
	//从二叉排序树中删除节点p,并重接它的左或右子树
	BSTree q, s;
	if (!(*p)->lchild && !(*p)->rchild)	//p为叶子节点
		*p = NULL;
	else if (!(*p)->lchild)	//左子树为空,重接右子树
	{
		q = *p;
		*p = (*p)->rchild;
		free(q);
	}
	else if (!(*p)->rchild)	//右子树为空,重接左子树
	{
		q = *p;
		*p = (*p)->lchild;
		free(q);
	}
	else						//左右子树均不为空
	{
		q = *p;
		s = (*p)->lchild;
		while (s->rchild)		//转左,然后向右走到尽头
		{
			q = s;
			s = s->rchild;
		}
		(*p)->data = s->data;
		if (q != *p)				//判断是否执行上述while循环
			q->rchild = s->lchild;	//执行上述while循环,重接右子树
		else
			q->lchild = s->lchild;	//未执行上述while循环,重接左子树
		free(s);
	}
	return OK;
}

然后我们遍历用递归的方式实现最终的删除算法:

Status Delete_BST(BSTree* T, int key)
{
	/* 若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素节点 */
	/* 并返回TRUE;否则返回FALSE */
	if (!(*T))
		return FALSE;	//不存在关键字等于key的数据元素
	else
	{
		if (key == (*T)->data)
			Delete(T);
		else if (key < (*T)->data)
			return Delete_BST(&(*T)->lchild, key);
		else
			return Delete_BST(&(*T)->rchild, key);
	}
	return OK;
}

附加:完整代码:

#include<stdio.h>
#include<stdlib.h>
#define TRUE		1
#define OK			1
#define FALSE		0
#define ERROR		0
#define OVERFLOW	-1
typedef struct BSTNode {
	int data;
	struct BSTNode* lchild;
	struct BSTNode* rchild;
}BSTNode,* BSTree;
typedef int Status;

void visit(BSTree T) {
	printf("%d ", T->data);
}
void InOrder(BSTree T) {
	if (T != NULL) {
		InOrder(T->lchild);
		visit(T);
		InOrder(T->rchild);
	}
}
void Create_BSTree(BSTree* T,int arr[],int n) {
	(*T) = NULL;
	int i = 0;
	while (i < n) {
		BST_Insert(T, arr[i]);
		i++;
	}
}
BSTNode* Search(BSTree T, int k) {
	if (T == NULL)
		return NULL;
	if (k == T->data)
		return T;
	else if (k < T->data)
		return Search(T->lchild, k);
	else 
		return Search(T->rchild, k);
}
Status BST_Insert(BSTree* T, int k) {
	if ((*T) == NULL) {
		(*T) = (BSTree)malloc(sizeof(BSTNode));
		if (!(*T))exit(OVERFLOW);
		(*T)->data = k;
		(*T)->lchild = (*T)->rchild = NULL;
		return OK;//插入成功
	}
	else if (k == (*T)->data)return ERROR;//插入失败
	else if (k < (*T)->data)return BST_Insert(&(*T)->lchild, k);
	else return BST_Insert(&(*T)->rchild, k);
}
Status Delete_BST(BSTree* T, int key)
{
	/* 若二叉排序树T中存在关键字等于key的数据元素时,则删除该数据元素节点 */
	/* 并返回TRUE;否则返回FALSE */
	if (!(*T))
		return FALSE;	//不存在关键字等于key的数据元素
	else
	{
		if (key == (*T)->data)
			Delete(T);
		else if (key < (*T)->data)
			return Delete_BST(&(*T)->lchild, key);
		else
			return Delete_BST(&(*T)->rchild, key);
	}
	return OK;
}
Status Delete(BSTree* p)
{
	//从二叉排序树中删除节点p,并重接它的左或右子树
	BSTree q, s;
	if (!(*p)->lchild && !(*p)->rchild)	//p为叶子节点
		*p = NULL;
	else if (!(*p)->lchild)	//左子树为空,重接右子树
	{
		q = *p;
		*p = (*p)->rchild;
		free(q);
	}
	else if (!(*p)->rchild)	//右子树为空,重接左子树
	{
		q = *p;
		*p = (*p)->lchild;
		free(q);
	}
	else						//左右子树均不为空
	{
		q = *p;
		s = (*p)->lchild;
		while (s->rchild)		//转左,然后向右走到尽头
		{
			q = s;
			s = s->rchild;
		}
		(*p)->data = s->data;
		if (q != *p)				//判断是否执行上述while循环
			q->rchild = s->lchild;	//执行上述while循环,重接右子树
		else
			q->lchild = s->lchild;	//未执行上述while循环,重接左子树
		free(s);
	}
	return OK;
}
void main() {
	BSTree T;
	int array[8] = { 50,66,60,26,21,30,70,68 };
	Create_BSTree(&T, array, 8);
	printf("\n删除结点之前的二叉排序树:");
	InOrder(T);
	printf("\n");
	Delete_BST(&T, 70);
	printf("\n删除70之后的二叉排序树:");
	InOrder(T);
	printf("\n");
}

查找效率的分析

由于二叉排序树的性质,我们中序遍历二叉排序树后,得到的是一个递增序列。二叉排序树的目的不仅仅是为了排序,二叉排序树的主要作用是提高了元素的查找、插入、删除的速度,这也与其链式的存储结构有关。

我们重点关注二叉排序树的查找效率。查找操作的原理是通过比对结点的数值大小,来判定是向左走还是向右走,最后在相等情况下停下来,查找成功。那么,其比较的次数是待查找元素所在的深度,最好的情况是1次,即要找的元素就是根结点,最坏不会超过树的深度。所以,查找性能取决于这颗树的形状。通过平均查找长度(ASL)计算,我们知道,一个二叉排序树应该尽量“平衡”,其深度与完全二叉树相同,这样的树拥有最佳的查找性能。现在的问题是,二叉排序树的形状不是固定的,不一定每一个二叉排序树都具有最佳的查找性能,如何将一个二叉排序树“平衡”,并且仍然保持二叉排序树的性质,是一个很有意义的问题,这就牵扯到平衡二叉树的知识了。

上一篇:C++ 二叉树的先序,中序,后序遍历-递归与非递归方式


下一篇:树2 List Leaves