struct Node
{
int value;
Node* left;
Node* right;
};
查找函数:
Node** find(int i){
Node* tmp;
Node** link =&root;
while ((tmp=*link) != NULL)
{
if(tmp->value > i)
link = &tmp->left;
if(tmp->value == i)
return link;
if(tmp->value < i)
link = &tmp->right;
}
return NULL;
}
这时候返回的link其实是指向找到的结点的前一个结点,但是保留了一种联系就是,*link = 上一个结点的left或者right,所以可以在改变tmp的时候,让其指向别的结点,重新建立联系。同时这样还保留的前一个结点的地址(当然可以通过(Node*)(link-1或者2来实现)),不过这是不可移植的,不建议使用。
插入结点:同样的是利用了Link的联系特性,这样我可以把&root也当做其中一个部分,在if开头就进行处理,少去了root==NULL这一条件的判断,可以缩减代码。
void insert(int i){
Node* tmp ;
Node** link = &root;
while ((tmp = *link) != NULL)//用到某个指针的前一个指针的时候,就可以利用二重指针,取地址,保证联系
{
if(tmp->value > i)
link = &tmp->left;
else
link = &tmp->right;
}
Node* new_node = (Node*)malloc(sizeof(Node));
assert(new_node!=NULL);
new_node->left = NULL;
new_node->right = NULL;
new_node->value = i;
*link = new_node;
}
删除结点就如同上一篇BLOG讲的一样,有3种情况,具体实现如下,然后再给出调试过程中遇到的各种BUG:
void Delete(int i){
register Node** link = find(i);
Node* tmp = *link;
//while ((tmp = *link)!=NULL && tmp->value != i) //这时候pre就是link的上一个结点,*link指向tmp,*pre指向tmp的上一个结点后总结!!!此时pre也没用
//{
// if(tmp->value > i)
// {
// link = &tmp->left;
// }
// if(tmp->value < i){
// link = &tmp->right;
// }
//}
//if(flag == 0)
// pre = (Node*)(link-1);//不可移植的 虽然link里面的值是*link上一个结点的地址,但是不好取
//if(flag == 1)
// pre = (Node*)(link-2);
assert((*link)!=NULL);
if(tmp->left == NULL && tmp->right == NULL){
*link =NULL;//防止free后*link指向的东西变成非法的
free(tmp);
}
else if(tmp->left == NULL){
//if(flag == 0)
// pre->left = tmp->right;
//if(flag == 1)
// pre->right = tmp->right;
*link = tmp->right;//link指向的结点不论左右本来指向tmp,选择改变就好
free(tmp);
}
else if(tmp->right == NULL)
{
*link = tmp->left;
free(tmp);
}
else
{
Node* left_right;
Node** the_left = &tmp->left;
Node** pre;
while ((left_right=*the_left) != NULL)
{
pre = the_left;
the_left = &left_right->right;
}
tmp->value = (*pre)->value;//这里增加一条,left_right = *pre;保留最大值结点地址
*pre = NULL;
free(left_right);
}
}
中间注释的部分就是修改了的部分,首先刚开始不太理解二重指针用法,我再获得tmp的时候,我还想知道前一个结点的地址。
第一次尝试:再写了个find源代码插入到Delete函数中,利用Link获得了前一个结点的地址,然后使用了flag去判断到底是左边指向还是右边指向,利用了不可移植的代码获得了上一个结点的地址。非常错误,但是确实实现了功能。这时候还没理解到link这样做的本质,所以还是没有发挥二重指针对于2个结点直接的联系,利用了flag去判断,然后根据这个不可移植代码获得的地址,去链接2个结点。(禁忌之法,不可取)
第二次错误是在删除的结点中有左右子树,这时候我获得了最大左子树的值,给了当前的结点,然后去free哪个左子树结点,这里出现了bug,就是free掉的地址变成了非法地址,但是二重指针指向的是左子树里面值最大的结点,*link是为NULL,不管怎么做,我都不能让LINK这个的结点前一个结点指向他变为NULL,他就会一直指向非法,下一次find访问的时候,就会越界报错。为了取得link所指的结点的地址,我首先利用了一个一重指针,这样可以避免第一次那种不可移植的方法。但是还是没办法改变上一个结点的指向,所以还是利用了再一个二重指针去获得前一个结点地址的办法,又忽略了之间的联系!!!。
第三次尝试:就是上述代码,这时候利用了之间的联系,*pre就是指向现在左子树最大值的结点,不管是左右,反正就是唯一的联系和连接,让*pre为NULL,解决掉了FREE之后的麻烦,然后free掉保留的那个结点就可以了。
test.cpp
#include "tree.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main(){
insert(20);
insert(12);
insert(25);
insert(5);
insert(16);
insert(17);
insert(9);
insert(28);
insert(26);
insert(29);
insert(3);
Delete(25);
Delete(17);
return EXIT_SUCCESS;
}