有序二叉树的操作
初始化
//初始化
//tree_t tree; tree.root tree.cnt
void tree_init(tree_t* t){
t->root = NULL;
t->cnt = 0;//cnt为节点计数器
}
插入节点
//插入节点
void tree_insert(tree_t* t,int data){
//创建新节点
node_t* new = malloc(sizeof(node_t));
new->data = data;
new->left = NULL;
new->right = NULL;
//如果root为空,说明还没有根节点
//此时的新节点即作为根节点
if(t->root == NULL){
t->root = new;
t->cnt++;
return;
}
//找位置插入
//p1,p2确定插入位置
node_t* p1,*p2;
p1 = p2 = t->root;
while(p2 != NULL){
//p1 慢 p2 一步
p1 = p2;
if(p2->data > data){
p2 = p2->left;
}else{
p2 = p2->right;
}
}
//循环结束p2指向NULL,p1指向的节点即为新节点的父节点
//判断新节点,挂在父节点的哪一侧
if(p1->data > data){
p1->left = new;
}else{
p1->right = new;
}
//计数加一
t->cnt++;
}
在插入节点操作中我们用了两个指针p1和p2来寻找新节点的插入位置,p1和p2起初都置于根节点,让p2先走,p1始终比p2慢一步,这样在循环结束时p2指向NULL,p1指向的节点即为新节点的父节点。此时我们又需要进行判断,虽然通过画图我们可以清晰地知道新节点应该挂载的位置,然而计算机并不知道新节点挂在父节点的左侧还是右侧,因此我们还要通过新节点与父节点比较大小可以确定位置。
!!!通过这张图让我们来理解一下为什么要进行判断来获取新节点的挂载位置,例如上图我们引入一个新节点X,如果我们经过查找发现X应插入节点1的后面,但是这时就要考虑新节点X应该挂载在父节点的左侧还是右侧,将新节点X与父节点比较大小就容易找到确定的挂载位置。 又例如新节点X经过循环条件判断确定X节点应插入在p2位置,通过图显而易见的是新节点X插入在节点8的左侧,但是计算机还得通过逻辑判断来确定最后新节点是挂在父节点的左侧还是右侧。
删除节点(难点)
删除节点的具体情况可以细分为4种
1.目标节点(即所删除节点,后文不在说明)的左右子树都为空,那么直接删除即可
2.目标节点只有左子树,那么删除后需要将原来的左子树挂回目标节点的父节点上
3.目标节点只有右子树,那么删除后需要将原来的右子树挂回目标节点的父节点上
4.目标节点的左右子树均存在,那么删除后需要将原来的左子树挂在原来的右子树的最左侧(因为目标节点的左子树大小一定小于右子树,按照有序二叉树的构造规则它应该挂在原来右子树的最左侧位置)重新构成一颗新树,此时又需要将这颗新树的父节点挂到目标节点的父节点上
5.特殊情况,找不到删除节点,删除节点不存在
//删除节点
void tree_del(tree_t* t,int data){
node_t* ptar = t->root;//要删除的节点
node_t* pnode = t->root;// 要删除节点的父节点
while(ptar->data != data || ptar == NULL){
//pnode 慢 ptar 一步
pnode = ptar;
//ptar先走,找要删除的节点
if(ptar->data > data){
ptar = ptar->left;
}else{
ptar = ptar->right;
}
}
//如果ptar指向节点的数据和要删除节点的数据相同,则找到删除目标
//ptar指向要删除的目标,pnode指向的就是目标父节点/
//如果循环结束后,ptar为空,则要删除的节点不存在
if(ptar == NULL){
printf("没找到要删除的节点\n");
return;
}
//left记录要删除节点的左子树
//right记录要删除节点的右子树
node_t* left = ptar->left;
node_t* right = ptar->right;
//左右子树都为空
if(left == NULL && right == NULL){
//让父节点 指向 目标节点的指针为空
if(ptar->data > pnode->data){
pnode->right = NULL;
}else{
pnode->left = NULL;
}
//释放目标节点
free(ptar);
ptar = NULL;
t->cnt--;
}
//目标节点只有右子树
if(left == NULL && right != NULL){
//判断目标节点的右子树应该挂在父节点的哪一侧
if(ptar->data > pnode->data){
pnode->right = right;
}else{
pnode->left = right;
}
//释放节点
free(ptar);
ptar = NULL;
t->cnt--;
}
//目标节点只有左子树
if(left != NULL && right == NULL){
//判断目标节点的右子树应该挂在父节点的哪一侧
if(ptar->data > pnode->data){
pnode->right = left;
}else{
pnode->left = left;
}
//释放节点
free(ptar);
ptar = NULL;
t->cnt--;
}
//目标节点既有左子树也有右子树
if(left != NULL && right != NULL){
//将左右子树合成一颗新树
//找到右子树最左侧的位置,将左子树挂上
node_t* p1,*p2;
p1 = p2 = right;
while(p2 != NULL){
//p1 慢 p2 一步
p1 = p2;
//p2不停左移
p2 = p2->left;
}
//循环结束后,p2为NULL,p1记录右子树上最左侧位置
//将左子树挂在右子树最左侧位置
//right表示新合成的树
p1->left = left;
//新树挂在父节点的哪一侧?
if(ptar->data < pnode->data){
pnode->left = right;
}else{
pnode->right = right;
}
//释放节点
free(ptar);
ptar = NULL;
t->cnt--;
}
}
目标节点不存在左右子树
查找到目标节点后,将其父节点的左右指针置空即可,实现比较简单不另做赘述。
目标节点只存在一棵子树
查找到目标节点后,只需要判断目标节点的子树应该挂在父节点的哪一侧即可
这里我们引入两个指针来存储目标节点的左右子树
//left记录要删除节点的左子树
//right记录要删除节点的右子树
node_t* left = ptar->left;
node_t* right = ptar->right;
最复杂的一种情况 :目标节点既有左子树也有右子树
第一步:引入指针p1和p2让他们同时指向目标节点的右子树,p2先走,p1跟随p2走,当p2为空时,p1记录的是该右子树的最左侧位置,找到这个位置后我们就可以将目标节点的左子树挂在右子树的最左侧位置。
第二步:比较目标节点与其父节点的大小,以确定新树挂在目标节点的父节点的哪一侧