二叉排序树(BST:Binary Search Tree)

一、定义

在二叉树的基础上,增加以下约束/规则,构成BST

(1)如果树中一个节点的左子树非空,则左子树所有节点的值都小于该节点的值

(2)如果树中一个节点的右子树非空,则右子树所有节点的值都大于该节点的值

(3)左右子树也满足BST树的规则---->说明BST是递归定义的

BST-->二叉树+顺序性(左子树<根节点<右子树)

二、性质

(1)BST的中序遍历有序(递增)

原因:中序遍历为:(左子树) 根 (右子树)

           BST规则为:    左子树<根节点<右子树   

(2)减少查找次数

假设有九个数:8 3 10 1 6 14 4 7 13  在其中找数字x,时间复杂度为eq?O%5Cleft%20%28%20n%20%5Cright%20%29

将其转换为BST,可通过比较根节点的大小来查找,若x比根节点小,则比较x与左子树的根的大小,由此类推,最后找到x。

badcd59dc9b94361a6f0b6d29f50ba4e.png

由图易知最大查找次数为树的高度,将BST补全成完全二叉树,假设共有m个节点,则最大查找次数为 eq?%5Cleft%20%5Clceil%20%5Clog%20n&plus;1%20%5Cright%20%5Crceil ,时间复杂度为eq?O%5Cleft%20%28%20%5Clog%20n%20%5Cright%20%29

三、如何建立二叉排序树?----如何插入操作?

(1)在空树的基础上,先用第一个数据建立根节点

(2)执行n-1此插入操作

        插入操作:插入数据x=4

        (2.1)创建一个节点,把x放进去

        (2.2)查找4应该在的位置:同二(2)中的查找操作的步骤,查找到应该为NULL的位置时,即插入位置,如下图(写代码时,真正找的应该是NULL的父亲节点)

44923b7d60944a368c8fce7fb61479b6.png

代码:

#include <stdlib.h>
#include <stdio.h>
# include <string.h>
/*二叉排序树*/
//节点结构
typedef struct BSTNode{
	int data;//数据
	struct BSTNode* left;
	struct BSTNode* right; 
}BSTNode,*BSTree; 
BSTree initBST(int k)//初始化
{
	BSTNode* r=(BSTNode*)malloc(sizeof(BSTNode));
	if(r==NULL)
	{
		return NULL; 
	}
	r->data=k;
	r->left=r->right=NULL;
	return r; 
}
BSTree insert_BST(BSTree ro,int x)//插入操作
{
	BSTNode* s=(BSTNode*)malloc(sizeof(BSTNode));
	s->data=x;
	s->left=s->right=NULL;
	BSTNode* p=ro;
	BSTNode* pre=NULL;
	while(p!=NULL)
	{
		if(x<p->data)
		{
			pre=p;
			p=p->left;
		}
		else{//不存在等于的情况:默认BST无重复数字
			pre=p;
			p=p->right;
		}
		
	}
	if(x<pre->data )
	{
		pre->left=s;
	}
	else{
		pre->right=s;
	}
	return ro;//ro其实不变,但为了逻辑的严谨性还是return ro
	
}
//递归版插入
//往 以ro为根节点的树中插入一个数据x
// if(x<ro->data)  以ro->left为根节点的子树中插入数据x 
//if(x>ro->data)  以ro->right为根节点的子树中插入数据x
//递归出口:  if(ro==NULL)创建新结点s,return s; 

BSTree insert_BST1(BSTree ro,int x)
{
	if(ro==NULL)
	{
		BSTNode* s=(BSTNode*)malloc(sizeof(BSTNode));
	    s->data=x;
		s->left=s->right=NULL;
		return s;
		
	}
	if(x<ro->data)
	{
	
		ro->left=insert_BST1(ro->left,x); //最后一次调用ro->left会变,所以一定要返回ro->left
		return ro;//容易漏
	}
	else{
		
		ro->right=insert_BST1(ro->right ,x); //同理
		return  ro;
	}

 } 




void inOrder(BSTree ro) //中序遍历
{
	if(ro==NULL)
	{
		return;
	}
	
	if(ro->left!=NULL)
	{
		inOrder(ro->left);
	}
	printf("%d ",ro->data);
	
	if(ro->right !=NULL)
	{
		inOrder(ro->right);
	}
}
void Search(BSTree ro,int x)//查找
{
	BSTNode* p=ro;
	while(p!=NULL&&p->data !=x)
	{
		if(x<p->data)
		{
			p=p->left;
		}
		else{
			p=p->right;
		}
	}
	if(p==NULL)
	{
		printf("NO\n"); 
	}
	else{
		printf("YES\n"); 
	}
	
}
int main()
{
    int a[105],n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
    	scanf("%d",&a[i]);	
	}
	BSTree ro=initBST(a[1]);
	if(ro==NULL)
	{
		printf("建树失败\n");
		return 0;
	}
	for(int i=2;i<=n;i++)
	{
		ro=insert_BST1(ro,a[i]);
	 } 
	 inOrder(ro);
	 printf("\n");
	// Search(ro,116);
	
	return 0;
	 
}

三、BST的删除操作

假设要在BST中删除一个数据x,我们需要考虑:删除x所在节点之后让谁顶替x节点原来的位置?

为此要考虑三种情况:

1.x所在节点度为0

直接删除即可,该位置为NULL,但该方法效率不高,另一个方法见情况2.

2.x所在节点度为1

(1)找到该节点并删除,让其唯一的一个孩子顶替该节点的位置

情况1可以看成情况2,然后只写一份代码即可:设度为0的节点有一个为NULL的孩子节点

3.x所在节点度为2 

找到该节点并删除,用该节点的(右子树中最靠左的节点)或者(左子树中最右的节点)替代

代码思路:找到该节点,把其数据域改为(左子树中最右的节点的数据域的值),将其左子树进行递归,删除左子树最右的节点。

递归代码思路:在以root为根的树中删除数据k

把以root为根的树分成三部分:左子树 root 右子树

下面是伪代码:

if(k<root->data) 问题转化成 在以root->left为根的子树中删除数据k
if(k>root->data) 问题转化成 在以root->right为根的子树中删除数据k
if(k==root->data)
{
	root节点就是要删除的节点
	判断root的度:
	if(左右孩子都存在)//度为2
	{
		找到root的左子树中最靠右的节点p//写一个专门寻找的函数find
		root->data=p->data;
		问题转化成 在以root->left为根的子树中删除数据p->data;
	}
	else//把度为0和1都看成度为1的情况
	{
		ch//root唯一的孩子
		if(root->left!=NULL)
		{
			ch=root->left;
		}
		else
		{
			ch=root->right;
		}
		free(root);
		return ch;
	}
}

代码:

//找以ro为根的树中最靠右的节点p
BSTNode* find_p(BSTree ro)
{
	BSTNode* p=ro;
	while(p->right!=NULL)
	{
		p=p->right;
	}
	return p;
	
	
}
//BST删除操作:在以root为根的树中删除数据k
BSTree BST_de(BSTree ro,int k)
{
	if(k<ro->data )
	{//问题转化为:在以root->left为根的树中删除数据k
		ro->left=BST_de(ro->left,k);
	//	return ro;
	}
	else if(k>ro->data )
	{//问题转化为:在以root->right为根的树中删除数据k
		ro->right =BST_de(ro->right ,k);
	//	return ro;
	}
	else
	{//k==ro->data  ro这个节点就是要删除的节点
		if(ro->left!=NULL&&ro->right!=NULL)
		{//找到root的左子树中最靠右的节点p
			BSTNode* p=find_p(ro->left);
			ro->data=p->data;
		    ro->left=BST_de(ro->left,p->data);
		   // return ro;
		 } 
		else
		{//ֻ只有一个孩子ch(NULL)
			BSTNode* ch=NULL; 
			if(ro->left!=NULL)
			{
				ch=ro->left;
			}
			else
			{
				ch=ro->right;
			}
			free(ro);
			ro=NULL;
			return ch;
		}
	}
	return ro;	
} 

四、二叉树插入和删除操作的完整代码

#include <stdlib.h>
#include <stdio.h>
# include <string.h>

typedef struct BSTNode{
	int data;
	struct BSTNode* left;
	struct BSTNode* right; 
}BSTNode,*BSTree; 
BSTree initBST(int k)
{
	BSTNode* r=(BSTNode*)malloc(sizeof(BSTNode));
	if(r==NULL)
	{
		return NULL; 
	}
	r->data=k;
	r->left=r->right=NULL;
	return r; 
}
BSTree insert_BST(BSTree ro,int x)
{
	BSTNode* s=(BSTNode*)malloc(sizeof(BSTNode));
	s->data=x;
	s->left=s->right=NULL;
	BSTNode* p=ro;
	BSTNode* pre=NULL;
	while(p!=NULL)
	{
		if(x<p->data)
		{
			pre=p;
			p=p->left;
		}
		else{
			pre=p;
			p=p->right;
		}
		
	}
	if(x<pre->data )
	{
		pre->left=s;
	}
	else{
		pre->right=s;
	}
	return ro;
	
}

BSTree insert_BST1(BSTree ro,int x)
{
	if(ro==NULL)
	{
		BSTNode* s=(BSTNode*)malloc(sizeof(BSTNode));
	    s->data=x;
		s->left=s->right=NULL;
		return s;
		
	}
	if(x<ro->data)
	{
	
		ro->left=insert_BST1(ro->left,x); 
		return ro;
	}
	else{
		
		ro->right=insert_BST1(ro->right ,x); 
		return  ro;
	}

 } 

//找以ro为根的树中最靠右的节点p
BSTNode* find_p(BSTree ro)
{
	BSTNode* p=ro;
	while(p->right!=NULL)
	{
		p=p->right;
	}
	return p;
	
	
}
//BST删除操作:在以root为根的树中删除数据k
BSTree BST_de(BSTree ro,int k)
{
	if(k<ro->data )
	{//问题转化为:在以root->left为根的树中删除数据k
		ro->left=BST_de(ro->left,k);
	//	return ro;
	}
	else if(k>ro->data )
	{//问题转化为:在以root->right为根的树中删除数据k
		ro->right =BST_de(ro->right ,k);
	//	return ro;
	}
	else
	{//k==ro->data  ro这个节点就是要删除的节点
		if(ro->left!=NULL&&ro->right!=NULL)
		{//找到root的左子树中最靠右的节点p
			BSTNode* p=find_p(ro->left);
			ro->data=p->data;
		    ro->left=BST_de(ro->left,p->data);
		   // return ro;
		 } 
		else
		{//ֻ只有一个孩子ch(NULL)
			BSTNode* ch=NULL; 
			if(ro->left!=NULL)
			{
				ch=ro->left;
			}
			else
			{
				ch=ro->right;
			}
			free(ro);
			ro=NULL;
			return ch;
		}
	}
	return ro;	
} 
void inOrder(BSTree ro) 
{
	if(ro==NULL)
	{
		return;
	}
	
	if(ro->left!=NULL)
	{
		inOrder(ro->left);
	}
	printf("%d ",ro->data);
	
	if(ro->right !=NULL)
	{
		inOrder(ro->right);
	}
}
void Search(BSTree ro,int x)
{
	BSTNode* p=ro;
	while(p!=NULL&&p->data !=x)
	{
		if(x<p->data)
		{
			p=p->left;
		}
		else{
			p=p->right;
		}
	}
	if(p==NULL)
	{
		printf("NO\n"); 
	}
	else{
		printf("YES\n"); 
	}
	
}
int main()
{
    int a[105],n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
    	scanf("%d",&a[i]);	
	}
	BSTree ro=initBST(a[1]);
	if(ro==NULL)
	{
		printf("错误\n");
		return 0;
	}
	for(int i=2;i<=n;i++)
	{
		ro=insert_BST1(ro,a[i]);
	 } 
	 inOrder(ro);
	 printf("\n");
	 ro=BST_de(ro,10);
	 inOrder(ro);
	 printf("\n");
	return 0;
	 
}

五、BST的缺陷

不同的插入方式会构成不同的斜树,如果序列本身是升序/降序的,就会建成斜树--起不到优化作用。

如下图:

如何优化?

BST+限制---->AVL树

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值