数据结构(22)--动态查找之二叉排序树(二叉查找树)

参考书籍:数据结构(C语言版)严蔚敏吴伟民编著清华大学出版社

本文中的代码可从这里下载:https://github.com/qingyujean/data-structure

1.动态查找表

特点:表结构在查找过程中动态生成
要求:对于给定值 key, 若表中存在其关键字等于 key的记录,则查找成功返回,并且对查找成功的关键字可做删除操作;查找失败则可以做插入关键字等于 key的记录的操作。
动态查找表主要有二叉树结构和树结构两种类型。二叉树结构有二叉排序树、平衡二叉树等。树结构有B-树、B+树等。

2.二叉排序树

二叉排序树,又叫二叉查找树二叉搜索树。它或是一棵空树;或是具有下列性质的二叉树:
   (1)若它的左子树不空,则左子树上所有结 的值均小于它的根结点的值;
   (2)若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
   (3)它的左、右子树也分别为二叉排序树。

2.1二叉排序树的查找

    将给定值与根节点的值进行比较,然后递归进行。

    代码实现:

#include<stdio.h>
#include<stdlib.h>
#define NULL 0
#define TRUE 1
#define FALSE 0
typedef int status; 
typedef int TElemType;
typedef int KeyType;
//动态二叉链表
typedef struct BiTNode{
	TElemType data;
	struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

bool isFind = false;//标识是否查找到
//在根指针T所指的二叉排序树中递归的查找其关键字等于key的数据元素,若查找成功,则指针p指向该数据元素的结点,并返回True,,否则返回False
//指针f总指向p的双亲,其初始值为NULL
status searchBST(BiTree T, KeyType key, BiTree &f, BiTree &p){
	if(!T){
		p = T;//使P指向NULL
		isFind = false;//标识没有查找到
		return FALSE;
	}
	if(T->data == key){
		p = T;
		isFind = true;//标识已经查找到
		return TRUE;
	}else if(T->data > key){
		f = T; 
		return searchBST(T->lchild, key, f, p);
	}else{
		f = T; 
		return searchBST(T->rchild, key, f, p);
	}
}

 

//中序遍历打印二叉树的递归算法(左、根、右)
void inOrderPrint(BiTree T){
	if(T){
		inOrderPrint(T->lchild);
		printf("%d ", T->data);		
		inOrderPrint(T->rchild);
	}
	
}

 

2.2二叉排序树的插入

    若二叉排序树为空,则作为根结点插入,否则,若待插入的值小于根结点值,则作为左子树插入,否则作为右子树插入。将二叉排序树的查找算法改写,以便能在查找不成功时返回插入位置。

    二叉排序树的建立:反复调用二叉排序树的插入算法即可。

    示例:结定关键字序列79,62,68,90,88,89,17,5,100,120,生成二叉排序树

代码实现:

status insertBST(BiTree &T, KeyType key){
	BiTNode *p;
	BiTNode *f = NULL;
	if(searchBST(T, key, f, p)){//找到了
		return TRUE;
	}else{//没找到,即不存在,则要插入该值
		//printf("查找&d失败,则在原二叉排序树中插入该值\n");
		p = (BiTNode *)malloc(sizeof(BiTNode));
		p->data = key;
		p->lchild = p->rchild = NULL;
		if(!f){//f为NULL,说明二叉排序树还是一棵空树
			T = p;
		}else if(key > f->data){
			f->rchild = p;
		}else{
			f->lchild = p;
		}
		return TRUE;
	}
}

演示:

int main(){
	BiTree T;
	T = NULL;//对树初始化:重要!!!

	//测试,按结定关键字序列79,62,68,90,88,89,17,5,100,120生成二叉排序树
	KeyType keyArray[] = {79, 62, 68, 90, 88, 89, 17, 5, 100, 120};
	for(int i = 0; i < 10; i++){
		insertBST(T, keyArray[i]);
	}
	printf("中序遍历该二叉树为:");
	inOrderPrint(T);
	printf("\n");

	
	//查找失败则插入
	int test = 1;
	while(test <= 3){//做2次测试
		printf("\n请输入要查找的关键字key=");
		KeyType key;
		scanf("%d", &key);
		insertBST(T, key);
		if(isFind){
			printf("查找%d成功\n", key);
		}else{
			printf("查找%d失败,则在原二叉排序树中插入该值\n", key);		
			printf("插入关键字%d后,中序遍历该二叉树为:", key);
			inOrderPrint(T);
			printf("\n");
		}
		test++;
	}

    return 0;
}

 

注意:1.二叉排序树与关键字排列顺序有关,排列顺序不一样,得到的二叉排序树也不一样。

            2. 中序遍历二叉排序树可得到一个关键字的有序序列;
            3. 进行插入操作时,不必移动其它结点,仅需改动某个结点的指针。表明,二叉排序树既拥有类似于折半查找的特性,又采用了链表作存储结构,因此是动态查找表的一种适宜表示。

2.3二叉排序树的删除

    对于二叉排序树,删去树上一个结点相当于删去有序序列中的一个记录,在删除某个结点之后依旧要保持二叉排序树的特性。

 如何删除? 

  设在二叉排序树上被删结点为*p(指向结点的指针为p),其双亲结点为*f,设*p是*f的左孩子。 如图:

分三种情况进行讨论(注意:下面的讨论假设p不是根节点,如果要删除的结点p是根节点,还需另外讨论,但也非常简单,代码里有实现,这里就不赘述了): 
(1)若*p结点为叶子结点,即PL和PR均为空树。
  由于删去叶子结点不破坏整棵树的结构,则只需修改其双亲结点的指针。 
(2)若*p结点只有左子树PL或者只有右子树PR。
  只需令PL或PR直接成为其双亲结点*f的左子树即可。
(3)若*p结点的左子树和右子树均不空。下面2种方法均可以,后面我的实现中使用第一种方法: 
  ①令*p左子树为*f的左子树,而*p右子树为*s的右子树
  ②是令*p的直接前驱(或直接后继)替代*p,然后再从二叉排序树中删去它的直接前驱(或直接后继)。

代码实现:

status deleteBST(BiTree &T, KeyType key){
	BiTNode *p, *q;//q将代替p在双亲f下的位置
	BiTNode *f = NULL;
	if(!searchBST(T, key, f, p)){//没找到
		return FALSE;
	}else{//找到了,则要执行删除操作
		if(!f){//f为NULL,说明要删除的是二叉排序树的根节点,示例:79
			if(p->lchild == NULL && p->rchild == NULL){//p是叶子结点,示例:5,68, 89,120
				T = NULL;
			}else if(p->lchild == NULL){//p只有右子树,示例:100,88
				T = p->rchild;
			}else if(p->rchild == NULL){//p只有左子树,示例:17
				T = p->lchild;				
			}else{//P的两棵子树均不为空
				//让p的左子树的根节点为新的根节点,右子树的根节点链接到左子树的最右下端
				T = p->lchild;
				q = T;
				while(q && q->rchild)
					q = q->rchild;
				//q指向p的左子树的最右下端
				if(q)
					q->rchild = p->rchild;
			}

		}else{
			if(p->lchild == NULL && p->rchild == NULL){//p是叶子结点,示例:5,68, 89,120
				q = NULL;
			}else if(p->lchild == NULL){//p只有右子树,示例:100,88
				q = p->rchild;
			}else if(p->rchild == NULL){//p只有左子树,示例:17
				q = p->lchild;				
			}else{//P的两棵子树均不为空,示例:62,90,
				//将p的左子树的根节点代替p,p的右子树到p的左子树的最右下端
				q = p->lchild;
				while(q->rchild){
					q = q->rchild;
				}
				q->rchild = p->rchild;//p的右子树移到左子树的最右下端
			}
			//重新指派p的双亲的孩子,并删除p结点
			if(f->lchild == p)
				f->lchild = q;
			else if(f->rchild == p)
				f->rchild = q;
			free(p);
		}//end else
	}//end else
	return TRUE;
}

演示:

int main(){
	BiTree T;
	T = NULL;//对树初始化:重要!!!

	//测试,按结定关键字序列79,62,68,90,88,89,17,5,100,120生成二叉排序树
	KeyType keyArray[] = {79, 62, 68, 90, 88, 89, 17, 5, 100, 120};
	for(int i = 0; i < 10; i++){
		insertBST(T, keyArray[i]);
	}
	printf("中序遍历该二叉树为:");
	inOrderPrint(T);
	printf("\n");	
	
	//查找成功则删除
	int test = 1;
	while(test <= 3){//做3次测试
	    printf("\n请输入要查找的关键字key=");
		KeyType key;
		scanf("%d", &key);
		deleteBST(T, key);
		if(isFind){
			printf("查找%d成功,则删除该关键字\n", key);
			printf("删除关键字%d后,中序遍历该二叉树为:", key);
			inOrderPrint(T);
			printf("\n");
		}else{
			printf("查找%d失败\n", key);		
		}
		test++;
	}

    return 0;
}

 

2.4二叉排序树的查找分析

    含有n个结点的二叉排序树不唯一,ASL也不相同。最差情况是(n+1)/2(退化成顺序查找,例如:单支树的情形);最好情况是和 log2n成正比(类似折半查找的判定树)
    为了保证树型查找有较高的查找速度,我们希望二叉树的每一个结点的左、右子树高度尽量接近平衡,即使按任意次序不断地插入结点,也不要使此树成为退化单支树。

 

本文中的代码可从这里下载:https://github.com/qingyujean/data-structure

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值