二叉查找树的基本操作
因为是尽可能的为了让小伙伴们都能看的明白,博主不会用冗杂的概念,品质保证,还请,多多关照。
首先我们得知道二叉查找树是什么,它就是一种结构,只不过这种结构的重点应用在于查找,它也是一个二叉树。
我们必须得清楚的一个性质:二叉查找树的一个节点的左节点中的元素,一定是小于该节点的元素的,而右节点的元素一定是大于该节点的。我们可以用图片来直观的反映
(取自博客园walfud)
这条性质非常重要,将有利于我们后面要进行的一些操作。我们可以这样记忆:左元小于上元小于右元
好,既然二叉查找树是什么,我们已经清楚了,那么我们就开始,我们将用链表来实现这项看似很复杂的结构
代码:
/*以递归算法为核心。二叉查找树*/
/*头文件及声明*/
#include<stdio.h>
#include<stdlib.h>
#define error(str) fprintf(stderr,"%s",str),exit(1)
typedef struct node
{
int n;
struct node *l;
struct node *r;
}tree;
/*函数区*/
tree* makeempty(tree* t);//建立一个二叉查找树
tree* find(int x,tree* t);//查找
tree* findmin(tree* t);//找最小元
tree* findmax(tree* t);//找最大元
tree* insert(int x,tree* t);
tree* del(int x,tree* t);
/*主函数*/
int main()
{
return 0;
}
/*函数实现*/
tree* makeempty(tree* t)//递归的调用
{
if(t!=NULL)
{
makeempty( t->l);
makeempty( t->r);
free(t);
}
return NULL;
}
tree* find(int x,tree* t)//查找操作,
{
if(t==NULL)
return NULL;
if(x<t->n)
return find(x,t->l);
else if(x>t->n)
return find(x,t->r);
else
return t;
}
tree* findmin(tree* t)//找最小元
{
if(t==NULL)
return NULL;
else if(t->l==NULL)
return t;
else
return findmin(t->l);
}
tree* findmax(tree* t)//查找最大元 这两步操作也可非递归实现
{
if(t==NULL)
return NULL;
else if(t->r==NULL)
return t;
else
return findmax(t->r);
}
tree* insert(int x,tree* t)
{
if(t==NULL)
{
t=(tree*)malloc(sizeof(tree));//开辟空间
if(t==NULL)
error("out of space");
t->n=x;
t->l=t->r=NULL;
}
else if(x<t->n)
t->l=insert(x,t->l);
else if(x>t->n)
t->r=insert(x,t->r);
return t;
}
tree* del(int x,tree* t)
{
tree *q;
if(t==NULL)
error("the tree is empty");
else if(x<t->n)
t->l=del(x,t->l);
else if(x>t->n)
t->r=del(x,t->r);
else if(t->l&&t->r)
{
q=findmin(t->r);
t->n=q->n;
t->r=del(t->n,t->r);
}
else
{
q=t;
if(t->l==NULL)
t=t->r;
else if(t->r==NULL)
t=t->l;
free(q);
}
return t;
}
- 如果小伙伴们之前已经在某些地方看过树结构之类的,那么应该很明白,我们要实现的这个结构,是以递归算法为核心的,甚至我们对二叉查找树的操作,从头到尾都贯穿递归的思想。
- 准备工作一定要做足,因为要用链表实现,而很明显这个结构单纯要一个next指针肯定是不够的,我们说过有左节点,和右节点,因此,我们需要一个left指针和一个right指针,这里放麻烦,我直接用首字母表示,同时我们将需要的函数都字函数区提前声明好,可以帮助我们理清思路,不至于写着写着就混乱了。
- 首先是建立二叉查找树,之前已经说过,我们将递归到底,因此,我们用递归来建立一个空的二叉查找树,很多小伙伴看到这里可能会疑惑,因为后面的一个free(t)直接释放了t指向的空间,那么这算什么创建二叉查找树呢,大家注意,这一步并不多余,我们所清除的只是指针t所指向的空间,我们无法保证本身的空间有什么,甚至可能是一堆乱码,而我们创建的这个二叉查找树,实际上,我们只需要它的指针,仅此而已。
- 我们获得这个指针之后,便可以对它进行结构的搭建,这里的指针从理论来说是一个叫根的概念,跟树的根一样,很好理解不用多说。接着我们便开始对它进行搭建,这里我用的tree* insert(int x,tree* t)这个函数,我们刚开始的这个指针指向为空,因此我们直接if判断,将x赋为这个根的元素,并且,给他一个指向左和指向右的指针,如果我们继续赋值,也很简单,先前说过,左元小于上元小于右元,因此我们要判断,如果它小于根元,我们就继续往左递归,如果它还大于该左元元素那我们就往右递归,注意,这里的递归都有一个统一的方向,那就是向下,不管是往左还是往右本质上都得带一个下字。即往左下递归,或往右下递归。我们添加的元素,一定都是在这颗树的目前的一个尾端。
- 接着我们来看查找操作,查找分三类
这些查找操作都得秉着左元小于上元小于右元的原则去查找。
首先特定元素,我们先对它进行比较判断,与根元的大小关系,如果相等的话,我们直接返回根元的指针域即可,如果不等的话,判断大小关系,如果小的话,我们往左下递归,如果大的话,我们继续往右下递归,递归到底都没有的话,直接返回NULL表示没有包含这个元素的指针域这里我们要注意,我们的查找树返回的是那个地址,因为指针是无法用明确的字符串去表示出来的,我们不可能说返回“这个数字在xx位置”,而计算机也不需要这个信息,我们要查找,计算机得到地址后,就能将元素反馈出来。所以不需要在这里迷茫。
6.我们最后来看删除操作,删除操作算是,所以操作中最复杂的操作。因此我们需要特别的注意 ,这里我们的思路是利用右节下所包含的最小的数据来代替需要删除的节点的数据,之后将那个节点递归的进行删除,很多人在这会迷惑一下,我这里可以用图反映一下。
那么我们为什么要这样处理呢,首先,右节点下含有的最小数据的节点,一定没有左下节点这意味着,我们不需要考虑复杂的双指针情况,或者说我们将复杂的双指针删除化为了单一的指针删除,而由于我们用来代替的数据是最小元,因此,他一定比它原先所在的分支任何数据都要小,同时因为左元小于上元小于右元,它又要比需要删除的节点的左下分支所以的节点都要大,满足了二叉查找树的性质。
从上图可以看到,最终删除后其实是相当于完成了一个替换,不过确确实实抹去了那个节点。