二叉排序树的查找、插入和删除

1.      二叉排序树

二叉排序树(Binary Sort Tree)或者是一棵空树,或者是具有下列性质的二叉树:

(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;

(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;

(3)左、右子树也分别为二叉排序树;

(4)没有结点值相同的结点。

二叉排序树又称二叉查找树(Binary Search Tree),亦称二叉搜索树。通常采用二叉链表作为二叉排序树的存储结构。中序遍历二叉排序树可以得到关键字有序的序列,即一个无序序列可以通过构造二叉排序树成为有序序列,构造树的过程即为对无序序列排序的过程。

2.      查找

当二叉排序树不为空时,首先将给定值和根结点的关键字比较,若相等则查找成功;若给定值小于根结点的关键字,在左子树上递归查找;若给定值大于根结点的关键字,在右子树上递归查找。

代码:

/*
 * 在以T为根结点的树中,查找与给定值key相同的结点。
 * 如果存在返回指向该结点的指针,否则返回空指针。
 */
BiTree SearchBST1(BiTree T, TElemType key)
{
	if (!T)						//空树,查找失败
		return NULL;
	else if (key == T->data)	//查找成功
		return T;
	else if (key < T->data)		//在左子树中继续查找
		return SearchBST1(T->lchild, key);
	else                        //在右子树中继续查找
		return SearchBST1(T->rchild, key);
}

3.      插入

二叉排序树是一种动态树表。树的结构不是一次生成的,而是在查找过程中,当树中不存在关键字等于给定值的结点时再进行插入。新插入的结点一定是叶子结点,而且一定是查找不成功时查找路径上最后一个结点的左孩子或者右孩子。重写上述查找算法,以便查找不成功时,函数可以返回新结点的插入位置。

代码:

/*
 * 在以T为根结点的树中,查找与key相同的结点。
 * 形参中,结点F为结点T的父节点,初始调用值为NULL。
 * 如果存在此结点,返回TRUE,并使*p指向该结点;
 * 如果不存在此结点,返回FALSE,并使*p指向查找路径上的最后一个结点。
 */
bool SearchBST2(BiTree T, BiTree F, TElemType key, BiTree *p)
{
	//函数中,只能对*p进行修改,而不能试图修改p的值。
	//因为p为形参,形参的改变并不会影响实参。
	if (!T)
	{
		//T为空树,此时父结点F或者为NULL,或者为查找路径上的最后一个结点。
		//查找失败
		*p = F;
		return false;
	}
	else if (key == T->data)	//查找成功
	{
		*p = T;
		return true;
	}
	else if (key < T->data)
	{
		return SearchBST2(T->lchild, T, key, p);
	}
	else
	{
		return SearchBST2(T->rchild, T, key, p);
	}
}

/*
 * 在树*T中查找与key相同的结点。
 * 如果存在返回FALSE,否则将key值插入到树中。
 */
bool InsertBST(BiTree *T, TElemType key)
{
	BiTree p, n;
	if (SearchBST2(*T, NULL, key, &p))
		return false;
	else
	{
		n = (BiTree)malloc(sizeof(BiNode));
		n->data = key;
		n->lchild = NULL;
		n->rchild = NULL;

		//注意不要漏掉p为NULL的情况,
		//此时表明*T为空树,将新结点直接作为根结点。
		if (!p)
			*T = n;
		//key比查找路径上的最后一个结点小,则将n作为p的左子树。
		else if (key < p->data)
		{
			p->lchild = n;
		}
		//key比查找路径上的最后一个结点大,则将n作为p的右子树。
		else
		{
			p->rchild = n;
		}
		return true;
	}
}

4.      删除

对于二叉排序树,删除一个结点相当于删除有序序列中的一个记录,只要在删除某结点之后调整树中某些结点,使之继续保持二叉排序树的特性即可。

假如要删除的结点为*p(p为指向结点的指针),*p的父节点为*f,不失一般性,假设*p为*f的左孩子结点:

1)      若*p为叶子结点,由于删除叶子结点不破坏树的结构,因此只需修改f->lchild为空指针即可。

2)      若*p只有左子树PL或者只有右子树PR,此时只要令PL或PR直接成为*f的左子树即可。

3)      若*p既有左子树又有右子树,此时有两种处理方法:一是先直接令PL为*f的左子树,再令PR为PL子树中最右孩子的右子树。PL子树中最右孩子即为PL子树中最大的结点。二是令*p的直接前驱(或直接后继)代替*p,然后从二叉排序树中删除它的直接前驱(或直接后继)。*p的直接前驱为PL子树中最右孩子,大小仅次于*p;*p的直接后继为PR子树中最左孩子,大小仅大于*p。

代码:(使用第一种处理方法)

/*
 * 在以*T为根结点的树中,删除与key相同的结点。
 * 如果没有此结点返回FALSE。
 */
bool DeleteBST(BiTree *T, TElemType key)
{
	if (!*T)		//空树。查无此结点。
		return false;
	else if (key == (*T)->data)
	{
		Delete(T);
		return true;
	}
	else if (key < (*T)->data)
	{
		return DeleteBST(&((*T)->lchild), key);
	}
	else
	{
		return DeleteBST(&((*T)->rchild), key);
	}
}

/*
 * 删除*T指向的结点
 */
bool Delete(BiTree *T)
{
	BiTree L;

	//*T既没有左孩子,又没有右孩子,为叶子结点
	if (!(*T)->lchild && !(*T)->rchild)
		*T = NULL;
	//*T只有右孩子
	else if (!(*T)->lchild)
		*T = (*T)->rchild;
	//*T只有左孩子
	else if (!(*T)->rchild)
		*T = (*T)->lchild;
	//*T既有左孩子,又有右孩子
	else
	{
		L = (*T)->lchild;//L指向被删除结点的左子树

		//寻找L的最右孩子
		while (L->rchild)
			L = L->rchild;

		//把*T的右子树接到左子树最右孩子的右子树上。
		L->rchild = (*T)->rchild;
		//*T的左子树直接作为*T父结点的子树
		*T = (*T)->lchild;
	}
	return true;
}

5.      时间复杂度

二叉排序树相对于其他数据结构的优势在于查找、插入、删除的时间复杂度较低。含有n个结点的二叉排序树的平均查找长度和树的形态有关。

最坏的情况:当先后插入的关键字有序时,二叉排序树退化为单枝树,树的深度为n,平均查找长度为(n+1)/2。

最好的情况:二叉排序树的形态和折半查找的判定树相同,平均查找长度和log2n成正比。

平均性能:随机情况下,平均查找长度和logn成正比。P(n)=O(logn)。

6.      测试代码(需额外添加上述代码)

/*
 * 二叉排序树的查找,插入和删除操作
 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define TOTAL 10

//二叉树的二叉链表存储表示
typedef int TElemType;
typedef struct BiNode
{
	TElemType data;
	struct BiNode *lchild;
	struct BiNode *rchild;
} BiNode,*BiTree;

//函数声明
BiTree SearchBST1(BiTree T, TElemType key);
bool SearchBST2(BiTree T, BiTree f, TElemType key, BiTree *p);
bool InsertBST(BiTree *T, TElemType key);
bool DeleteBST(BiTree *T, TElemType key);
bool Delete(BiTree *T);
bool InOrderTraverse_Recursive(BiTree T, bool(*Visit)(TElemType e));
bool PrintElement(TElemType e);

int main()
{
	BiTree T = NULL;//注意一定要先将T初始化为空树
	int i, num;

	//使用插入操作构建二叉排序树。(插入过程中已检验查找函数SearchBST2())
	printf("输入TOTAL个数构建二叉排序树:\n");
	for (i = 0; i < TOTAL; i++)
	{
		scanf("%d", &num);
		InsertBST(&T, num);
	}

	//检验插入效果。中序遍历该树,若结果为由小到大,表明插入成功。
	printf("中序遍历该树:\n");
	InOrderTraverse_Recursive(T, PrintElement);
	putchar(‘\n‘);

	//删除操作
	printf("输入待删除的数:");
	scanf("%d", &num);
	DeleteBST(&T, num);

	//检验删除操作
	printf("重新中序遍历该树:\n");
	InOrderTraverse_Recursive(T, PrintElement);
	putchar(‘\n‘);

	//检验查找操作SearchBST1()
	printf("输入待查找的数:");
	scanf("%d", &num);
	if (SearchBST1(T, num))
		printf("Yes\n");
	else
		printf("No\n");

	return 0;
}

/*
 * 中序递归遍历
 */
bool InOrderTraverse_Recursive(BiTree T, bool(*Visit)(TElemType e))
{
	if (T)
	{
		if (InOrderTraverse_Recursive(T->lchild, Visit))
			if (Visit(T->data))
				if (InOrderTraverse_Recursive(T->rchild, Visit))
					;
	}
	return true;
}

/*
 * 遍历函数
 */
bool PrintElement(TElemType e)
{
	printf("%-3d ",e);
	return true;
}

7.      测试结果

 

参考:

维基百科:二叉查找树

数据结构和算法系列12 五大查找之二叉排序树

二叉排序树的查找、插入和删除

时间: 2024-07-30 03:33:24

二叉排序树的查找、插入和删除的相关文章

BST(二叉排序树)的插入与删除

值得一说的是删除操作,删除操作我们分为三种情况: 1.要删的节点有两个孩子: 找到左子树中的最大值或者右子树中的最小值所对应的节点,记为node,并把node的值赋给要删除的节点del,然后删除node 实际上真正删除的是node,del只是发生了一次值的替换. 为了方便理解和操作,我们把两个孩子的情况放在最前面,这样经过以上处理后,该节点就会变成情况2或者情况3 ,接下爱这行这两种情况的代码. 2.要删的节点有一个孩子 r判断要删除的节点del是其父亲father的左孩子还是右孩子,以是fat

重温数据结构:二叉排序树的查找、插入、删除

读完本文你将了解到: 什么是二叉排序树 Binary Sort Tree BST 二叉排序树的关键操作 查找 插入 删除 运行代码测试 一道面试题 总结 Thanks 我们知道,二分查找可以缩短查找的时间,但是有个要求就是 查找的数据必须是有序的.每次查找.操作时都要维护一个有序的数据集,于是有了二叉排序树这个概念. 上篇文章 我们介绍了 二叉树 的概念,二叉树有左右子树之分,想必在区分左右子树时有一定的规则. 现在我们来介绍二叉树的一种特殊形式 - 二叉排序树,了解它的区分策略及常用操作. 什

二叉排序树(BST):创建、查找、插入与删除

删除结点的相关操作(左右子树均为非空的删除结点的方法): 算法分析: 下面以实例来说明二叉排序树的创建.查找.插入和删除等相关操作: 如输入关键字序列(45,24,37,12,54,93),然后对其进行相应的操作,程序如下: #include <iostream> #include <stdio.h> #include <stdlib.h> using namespace std; typedef struct BiTNode { int value; struct B

二叉排序树(概念,查找,插入,删除)

查找基本概念 查找表:由同一类型的数据元素构成的集合.对查找表的常用操作:查询元素是否存在.查询元素属性.插入一个数据元素.删除一个数据元素. 查找:也叫检索,是根据给定的某个值,在表中确定一个关键字等于给定值的数据元素. 关键字:可以标识一个数据元素的某个数据项. 主关键字:可以唯一地识别一个数据元素的关键字. 静态查找表:只进行查询某元素在表中与否或检索某元素的各种属性操作的表. 动态查找表:查找时同时进行插入表中无的元素或删除表中有的某元素的表. 二叉排序树 定义:二叉排序树(Binary

二叉查找树(二叉排序树)创建,插入,删除操作。

二叉排序树 二叉排序树是一个基础的树的数据结构.应用非常多. 它的特性就是,左孩子小于parent,右孩子大于parent. 寻找节点 寻找节点就是直接根据数值的大小,从root节点开始遍历,大于当前遍历节点就向它的右子树查找,相反则查找它的左子树.然后返回. 查找最大最小节点 直接根据root节点,遍历到最右就是最大节点,遍历到最左,就是最小节点. 插入节点 插入节点我这里插入的节点都会成为叶子节点.根据大小的关系向下遍历,遍历到最后的节点,然后插入就可以了. 删除节点 这里删除节点是相对麻烦

BST二叉排序树的查找和删除的完整C代码

二叉排序树的查找算法 假定二叉排序树的根节点指针为root,给定的关键字值为key,则查找算法可描述为: 置初值:p = root : 如果 key = p -> data ,则查找成功,算法结束: 否则,如果key < p->data ,而且 p 的左子树非空,则将 p 的左子树根送 p ,转步骤 2 :否则,查找失败,算法结束: 否则,如果 key > p->data ,而且 p 的右子树非空,则将 p 的右子树根送 p ,转步骤 2 :否则,查找失败,算法结束. //B

闭散列表的查找、插入和删除操作的完整C代码

/*闭散列表的建立.查找.插入.删除*/ #include <stdio.h> #define NIL -1 //假设关键字为非负整数 #define DEL -2 typedef int KeyType; KeyType HashTable[13]; //便于验证算法,关键字个数假定为不超过13,哈希表长定为13 //关键字插入函数 void InsertHashTable(KeyType k) { for(int i=0; i<13; i++) if( NIL == HashTabl

支持泛型AVL Tree的简单实现,并和STL map比较了插入,删除,查找的性能

1.问题描述: 1)AVL tree是一种自平衡树.它通过左右子树的高度差来控制树的平衡,当高度差是不大于1的时候,认为树是平衡的.树的平衡保证了树在极端情况下 (输入序列不够随机)的性能.很显然当左右子树高度平衡,保证了任何插入,删除,查找操作平均性能呢个,当不平衡时(有的子树很高),当 要操作的元素在这个子树时,性能会很差: 2)AVL tree 和Red black tree 都是一种平衡树,它的操作的时间复杂度是:O(lgN) ,N是树的节点的数目: 3)本文实现了AVL Tree, 并

字符串的插入、删除、查找并删除、重新赋值、替换

#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { //nsstring父类   NSMutableString可变字符串子类 NSMutableString *mustr=[[NSMutableString alloc]init]; NSMutableString *mustr1=[NSMutableString stringWithFormat:@&qu