一、定义
在二叉树的基础上,增加以下约束/规则,构成BST
(1)如果树中一个节点的左子树非空,则左子树所有节点的值都小于该节点的值
(2)如果树中一个节点的右子树非空,则右子树所有节点的值都大于该节点的值
(3)左右子树也满足BST树的规则---->说明BST是递归定义的
BST-->二叉树+顺序性(左子树<根节点<右子树)
二、性质
(1)BST的中序遍历有序(递增)
原因:中序遍历为:(左子树) 根 (右子树)
BST规则为: 左子树<根节点<右子树
(2)减少查找次数
假设有九个数:8 3 10 1 6 14 4 7 13 在其中找数字x,时间复杂度为
将其转换为BST,可通过比较根节点的大小来查找,若x比根节点小,则比较x与左子树的根的大小,由此类推,最后找到x。
由图易知最大查找次数为树的高度,将BST补全成完全二叉树,假设共有m个节点,则最大查找次数为 ,时间复杂度为
。
三、如何建立二叉排序树?----如何插入操作?
(1)在空树的基础上,先用第一个数据建立根节点
(2)执行n-1此插入操作
插入操作:插入数据x=4
(2.1)创建一个节点,把x放进去
(2.2)查找4应该在的位置:同二(2)中的查找操作的步骤,查找到应该为NULL的位置时,即插入位置,如下图(写代码时,真正找的应该是NULL的父亲节点)
代码:
#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树