赞
踩
首先定义结构体,并初始化一棵二叉排序树,本质是二叉树的链式存储结构,不作为本篇重点,将代码展示如下
typedef struct BinaryTree{ int data; struct BinaryTree* left; struct BinaryTree* right; }BSTNode, *BSTree;//定义结构体变量与结构体指针变量 BSTree init(int data) { BSTree ro = (BSTree)malloc(sizeof(BSTNode)); if (ro==NULL) { printf("error"); return NULL; } ro->data = data; ro->left = ro->right = NULL; return ro; }
对于二叉排序树的插入操作,可以通过递归实现与非递归实现,下面分别讨论
插入操作是基于查找操作实现的,即要想将一个节点插入BST中,就要查找该节点理应放置的位置。而这个查找操作正类似于二分查找,同时这也体现了BST的一个特性,即便于查找。
首先创建一个节点,数据域存放要插入的数据值,指针域置空。
假定该节点已经插入树中,我们要先进行查找操作。让一个指针p指向根节点,然后判断要插入的值与p指向的节点的值的大小关系,并根据判断结果来让p指针指向其左孩子或者右孩子,如此循环,直到判断结果为值相等。最终结果是找到了理应插入的位置。
但是该节点并不存在于树中,故进行完查找操作后,p指针最终指向的是NULL,代码如下
while (p!=NULL)
{
//x为要插入的数据
if (x > p->data){
p = p->right;
}
else{
p = p->left;
}
}
什么?最终p指向的是NULL,这是一个不确定的位置,我们也就无法找到其父亲。
那么,如何找到其父亲呢?不难想到,我们只需定义一个指针pre始终指向p指针的父亲就好了。代码如下
BSTNode* pre = NULL;//根节点的父亲为空
BSTNode* p = ro;
while (p)
{
if (x > p->data)
{
pre = p;
p = p->right;
}
else
{
pre = p;
p = p->left;
}
}
如此一来,我们就找到了要插入的位置,最终只需进行一次判断,将其插入就好
void insert_1(BSTree ro, int x) { //初始化节点 BSTree s = init(x); //插入节点 BSTNode* pre = NULL; BSTNode* p = ro; while (p) { if (x > p->data) { pre = p; p = p->right; } else { pre = p; p = p->left; } } if (x < pre->data) { pre->left = s; } else { pre->right = s; } }
首先,要理清思路。我们要实现一个函数insert,返回值是指向一棵树的指针(因为你将节点插入树中,必然改变了这棵树,所以要将这棵树返回来),参数是指向要插入树的指针ro与要插入的数据k。需要格外注意的是,该函数的功能是在以ro为根节点的树中插入数据k。
//函数声明
BSTree insert(BSTree ro,int k);
既然是递归函数,那就要有递归出口,同时在运行时要经历两步,一是“递”,二是“归”。
以该函数为例,“递”表现在不断查找所要插入的位置,直到满足递归出口的条件,之后开始“归”,即层层向上返,这里是返回指向一棵树的指针,可以理解为将树或者节点返回给上一层的递归调用。
不难想到,递归出口就是ro==NULL,这也意味着查找的结束。另外,我们要将ro->data与k值大小进行判断,这便是查找的过程。完整代码如下
BSTree insert(BSTree ro,int k) { if (ro == NULL) { BSTree s = init(k); return s; } else if (k < ro -> data) { ro->left=insert(ro->left, k);//去ro的左子树中插入k return ro; } else { ro->right = insert(ro->right, k);//去ro的右子树中插入k return ro; } }
我们要时刻关注insert函数的功能,是在以ro为根节点的树中插入数据k。当k < ro -> data时,就在以ro的左子树为根的树中插入k,插入完成后将函数返回值赋值给ro->left,这样就能在最后两层递归调用时,将k所在节点s插入到合适位置。k > ro -> data时同理。
可以这样理解,当我层层往下递,不断向下查找完成之后,就来到了递归出口,即ro==NULL,注意这里的ro指向的位置正是我们应该把k插入的位置。我们记录这个特殊的ro为c。在最终执行函数insert(c, k)时,由于满足递归出口的条件,我们创建了一个节点s,用来存储数据k,之后我们return s,“归”便开始了,即层层向上返回。
为了方便理解,我们把将要插入的节点s的理应父亲节点记作f,那么,在递归出口这里返回上去就来到了函数insert(f,k);并继续执行里面的语句ro->left=insert(ro->left, k); (假如k < f -> data)这里实际上是f->left=insert(c, k);由于递归出口处我们返回了节点s,于是等价于f->left=s。如此以来我们便将节点s插入到了合适的位置,这就是用ro->left或者ro->right
接收返回值的原因。由于这里的f发生了改变,所以我们还要将其返回。
理解了递归插入,再看删除,也是如法炮制。首先删除也是基于查找,与插入操作不谋而合,代码如下
BSTree del(BSTree ro, int k)
{
if (k < ro->data)
{
ro->left=del(ro->left, k);
return ro;
}
else if (k > ro->data)
{
ro->right = del(ro->right, k);
return ro;
}
//部分代码
}
由于要删除的节点存在于树中,那么递归出口便是k==ro->data。倘若满足递归出口条件,那么就要进行删除ro节点操作,由于ro的孩子数未知,所以我们要分类讨论。
我们尝试写下前三种情况
if(!ro->left&&!ro->right) //ro左右孩子都为NULL { free(ro); ro == NULL; return NULL; } else if(ro->left&&!ro->right) //ro只有左孩子存在 { BSTNode* q = ro->left; free(ro); ro == NULL; return q; } else if(ro->right&&!ro->left) //ro只有右孩子存在 { BSTNode* q = ro->right; free(ro); ro == NULL; return q; }
删除ro节点只需释放ro指向的空间,然后将ro置空,以防变为野指针。至于让孩子代替父亲的位置,我们只需直接返回这个孩子即可,这样在“归”时就可以直接将其插入到合适的位置,从而完成代替。
注意这里可以进一步简化代码,我们可以将第一种情况看作第二种或者第三种(这里以第三种为例)的一个特殊情况,即ro有一个孩子,只不过为NULL。简化的代码如下
if(ro->left&&!ro->right) //ro只有左孩子存在
{
BSTNode* q = ro->left;
free(ro);
ro == NULL;
return q;
}
else if(ro->right&&!ro->left||!ro->left&&!ro->right) //ro只有右孩子存在或者左右孩子都为NULL
{
BSTNode* q = ro->right;
free(ro);
ro == NULL;
return q;
}
//注:这些条件都会在后续完整代码得到简化
对于第四种情况,我们以用中序遍历紧挨着的前一个节点(记作t)代替为例。根据中序遍历,显然t就是ro的左子树中最靠右的节点。那么如何代替呢?我们只需将t节点的data赋给ro的data,然后再将t删除即可,代码如下
if (ro->left && ro->right) //ro左右孩子都存在
{
BSTNode* p = ro->left;
//查找ro的左子树中最靠右的节点
while (p->right)
p = p->right;
//代替
ro->data = p->data;
//在ro的左子树中删除最靠右的节点
ro->left=del(ro->left, p->data);
return ro;
}
这样,删除操作就完成了。完整代码如下
BSTree del(BSTree ro, int k) { if (k < ro->data) { ro->left=del(ro->left, k); return ro; } else if (k > ro->data) { ro->right = del(ro->right, k); return ro; } //以下均为k==ro->data满足时 else if (ro->left && ro->right) //ro左右孩子都存在 { BSTNode* p = ro->left; //查找ro的左子树中最靠右的节点 while (p->right) p = p->right; //代替 ro->data = p->data; //在ro的左子树中删除最靠右的节点 ro->left=del(ro->left, p->data); return ro; } else if(ro->left) //ro只有左孩子存在 { BSTNode* q = ro->left; free(ro); ro == NULL; return q; } else //ro只有右孩子存在或者左右孩子都为NULL { BSTNode* q = ro->right; free(ro); ro == NULL; return q; } }
我们可以通过中序遍历来检验是否插入成功与删除成功
void inOrder(BSTree ro)
{
if (ro == NULL) return;
if (ro->left) inOrder(ro->left);
printf("%d ", ro->data);
if (ro->right) inOrder(ro->right);
}
主函数这里,让用户输入节点总数与各个节点的值,从而完成后续操作
int main() { int n; scanf("%d", &n); int root; scanf("%d", &root); BSTree ro = init(root); int x; for (int i = 0; i < n - 1; i++) { scanf("%d", &x); insert_1(ro, x); } printf("插入完成,遍历结果为:"); inOrder(ro); putchar('\n'); ro=BST_del(ro, 3); printf("删除3后,遍历结果为:"); inOrder(ro); putchar('\n'); ro=BST_del(ro, 10); printf("删除10后,遍历结果为:"); inOrder(ro); putchar('\n'); /*输入样例 9 8 3 15 2 6 10 7 12 13 */ }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。