二叉查找树,它亦或是一颗空树,在或者是有以下性质的二叉树
1:某个结点左子树存在则该左孩子数值必小于该结点数值
2:某个结点右子树存在则该右孩子数值必不小于该结点数值
写代码之前,需要了解两个概念
一:后继结点
某个结点比如Q的后继结点也就是将Q所在的树进行中序排序后排序在Q后面的一个结点。
如果一个结点Q有右孩子,那么中序排序的时候,这个结点的右子树的最左节节点会出现Q后面,如果没有右孩子,那么去寻找Q结点的父辈结点X,这个X需要符合其属于其父结点M的左孩子。那么这个结点M在中序排序时会出现在Q后面。自己想想中序遍历是怎样的就好了
二:删除结点Q出现的情况
1:该节点没有孩子,那么直接删除这个节点Q就好了,并清除相应关系,比如其父节点指向其的指针需要清除
2:如果只有一个孩子,那么把Q的父节点和Q的那个孩子进行连接,并配置好两者之间的关系,比如是左孩子还是
右孩子等并相应删除Q
3:如果有两个孩子,那么把这个节点的后继结点M找到,这时候删除M就好了而不是Q,且需要将Q的值改为M的值
——————————————————————————————————————————————
代码是用数组模拟链表结构(请耐心看删除那一部分,当然一定会有更好的写法):
#include<stdio.h>
#include<stdlib.h>
#define MAX_node 100
//分别代表父结点,数据,左子树,右子树
int p[MAX_node],data[MAX_node],lc[MAX_node],rc[MAX_node];
int root=-1;//初始化
int stack[MAX_node];//建立一个栈便于查找空位
int top=0;
/*初始化*/
void init(){
int i;
for(i=0;i<MAX_node;++i)
{
p[i]=data[i]=lc[i]=rc[i]=-1;//默认空值为-1
stack[i]=i;
}
}
/*正式插入数据*/
void tree_insert(int z)
{ //x为从根节点遍历插入的数据应该位于哪里
int x=root,y=-1;//y的设定是为了保存最后存入数据位置的父结点
while(x!=-1){//去寻找最后要插入的位置
y=x;//保存最后插入位置对应的父结点
if(data[z]<data[x])
x=lc[x];
else
x=rc[x];
}
p[z]=y;//数据z的父节点就是y了
if(y==-1)//第一个插入数据的情况(第一次插入时第一个数据就是根了!!!)
root=z;
else if(data[z]<data[y])
lc[y]=z;
else
rc[y]=z;
return ;
}
/*插入预处理操作*/
void pre_insert(int nowdata)
{
int z;
z=stack[top++];//体现栈的作用
data[z]=nowdata;//将该值获取
tree_insert(z);//进行插入
}
/*搜索树上值为k的结点,返回数组下标*/
int tree_search(int x,int k)
{
while(x!=-1 && data[x]!=k){//迭代寻找
if(k<data[x])
x=lc[x];
else
x=rc[x];
}
return x;
}
/*返回最小值*/
int tree_minnum(int x)
{
while(lc[x]!=-1)
x=lc[x];
return x;
}
/*求某节点的后继结点 为删除做准备*/
/*这里很关键*/
int tree_successor(int x)
{
if(rc[x]!=-1)//如果该节点存在右子数,则直接寻找右子数的最左结点(这里删除操作需要用到)
return tree_minnum(rc[x]);
int y=p[x];
//这里需要额外注意 如果没有右子树,则一直向上寻找,找到一个父辈的结点Q,
//Q的父结点为W,Q正好为W的左子树,那么这个结点W就是后继结点了
while(y!=-1 && x==rc[y])
{
x=y;
y=p[y];
}
return y;
}
/*正式删除*/
void tree_delete(int z)
{
int x,y;
//查看是否有孩子
if(lc[z]==-1 || rc[z]==-1)//有一个或者没有
y=z;//用y拷贝一份
else
y=tree_successor(z);//有两个
//如果没有孩子 x还是-1
//如果有一个孩子的话确定这个孩子是左是右
/*如果有两个孩子那么,此时y是后继结点,y这时是没有左子树的
否则不符合求后继结点,只会存在右子树,如果有,那么需要重新指向,没有就删除了
这个y是要被删除的
*/
if(lc[y]!=-1)
x=lc[y];
else
x=rc[y];
//(正式重新指向)如果真的z有一个孩子或者后继结点y有右子树孩子,那么此时这个孩子的父结点应该是y的
//父结点了。
if(x!=-1)
p[x]=p[y];
//接下来需要删除这个y
if(p[y]==-1)//删除的是根结点(父节点不存在就是根了)
root=x;
//这里重新指引与p[x]=p[y]不同,就好比我认你当孩子,你还要认我当父节点一样
//如果是两个孩子进入这里判断那么此时y自己有孩子就重新指引,没有就直接删除y
//如果是一个孩子进入这里判断那么这个孩子重新被指引了
//如果是没有孩子进入这里判断那么这个结点直接被删除了
else if(y==lc[p[y]])
lc[p[y]]=x;
else
rc[p[y]]=x;
//两个孩子情况,这时候要把数据赋值给要本要删除的结点即可
if(y!=z)
data[z]=data[y];
/*最后删除可能还没有删除的y*/
data[y]=-1;
lc[y]=-1;
rc[y]=-1;
p[y]=-1;
stack[--top]=y;
return;
}
/*删除预处理操作*/
void pre_delete(int data)
{
int k;
k=tree_search(root,data);
if(k!=-1)
tree_delete(k);//找到这个数据下标位置并进行删除
return;
}
void display(int x)
{
if(x==-1)
return;
if(lc[x]!=-1)
display(lc[x]);
printf("%4d%4d%4d%4d\t\n",data[x],lc[x],rc[x],p[x]);
if(rc[x]!=-1)
display(rc[x]);
return;
}
int main(){
int i,t;
init();
/*
1 检查插入
2 检查删除
3 检查展示
4 寻找
*/
while(scanf("%d",&i)!=EOF)
{ switch(i){
case 1:
printf("insert num:\n");
scanf("%d",&t);
pre_insert(t);
printf("数据 左孩子 右孩子 父节点\n");
display(root);
break;
case 2:
printf("delete num:\n");
scanf("%d",&t);
pre_delete(t);
printf("数据 左孩子 右孩子 父节点\n");
display(root);
break;
case 3:
printf("数据 左孩子 右孩子 父节点\n");
display(root);
break;
case 4:
int a,b;
printf("what num do you want to find?\n");
scanf("%d",&a);
b=tree_search(root,a);
printf("该值下标为%d",b);
break;
default:
return 0;
}}
return 0;
}