在操作数据库的时候,经常进行大量的插入删除操作,我们希望达到的效果是能够对其中的某些元素操作,而不影响其他元素的位置,使表中的元素的移动达到最小,这里面一般使用的数据结构就是动态查找表。什么结构能够实现这样的效果呢?这里首先想到的是链表,对数据的组织形式选用最简单的树形结构。下面我们对二叉排序树进行讨论。二叉排序树一般具备以下几个特点:
二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
(3)左、右子树也分别为二叉搜索树;
(4)没有键值相等的节点。
下面我们利用C语言来构建二叉排序树。首先是其数据结构,保存到文件header.h中:
#ifndef _BITREE_TYPE
#define _BITREE_TYPE
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<ctype.h>
#include<time.h>
typedef struct BiTNode{
int data;
struct BiTNode *lchild,*rchild;
}BiTNode,*Bitree;
#endif
然后,在构建二叉树之前,我们需要思考的一个问题是,怎么进行合理的构建?思路是这样的,我们先定义一个二叉树结构,先在树中进行搜索,查看是否有元素存在。存在则不插入,不存在则进行插入,循环进行插入,这样就生成了一棵二叉树。这里涉及到几个函数,保存到function.h文件中。定义如下:
#ifndef BITREE_FUNC
#define BITREE_FUNC
/**declear functions here***/
int searchBST(Bitree T,int key,Bitree f,Bitree *p);
int insertBST(Bitree *T,int key);
void preordertraverse(Bitree T);
void midordertraverse(Bitree T);
void postordertraverse(Bitree T);
/*delete tree*/
int Delete(Bitree *p);
int DeleteBST(Bitree *T,int key);
#endif
首先我们查看生成二叉排序树的两个函数:searchBST与insertBST。保存到algorithm.c文件中,其实现如下:
#include"header.h"
#include"function.h"
int searchBST(Bitree T,int key,Bitree f,Bitree *p)
{
if(!T){
*p=f;
return 0;
}
else if(key == T->data){
*p=T;
return 1;
}
else if(key<T->data)
return searchBST(T->lchild,key,T,p);
else
return searchBST(T->rchild,key,T,p);
}
int insertBST(Bitree *T,int key)
{
Bitree p,s;
if(!searchBST(*T,key,NULL,&p)){
s=(Bitree)malloc(sizeof(BiTNode));
s->data=key;
s->lchild=s->rchild=NULL;
if(!p)
*T=s;
else if(key<p->data)
p->lchild=s;
else
p->rchild=s;
return 1;
}
else
return 0;
}
生成二叉树之后,我们需要对生成的树进行遍历操作以完成对树中数据的读取。这里主要研究树的三种遍历方式,主要是前序遍历,中序遍历,后续遍历。其三种方式的操作分别如下:
前序遍历:先从树根出发,访问树根,然后是左子树,然后是右子树。
中序遍历:从树的最左子树出发,先访问左子树,然后是与之相连的树根,最后是这个右子树。
后续遍历:从叶子出发,先到最左子树,先左子树,再右子树,然后再根。
接下来,对这三种遍历进行实现,并添加到algorithm.c文件中。
void preordertraverse(Bitree T)
{
if(T==NULL)
return ;
printf("%d\t",T->data);
preordertraverse(T->lchild);
preordertraverse(T->rchild);
}
void midordertraverse(Bitree T)
{
if(T==NULL)
return ;
midordertraverse(T->lchild);
printf("%d\t",T->data);
midordertraverse(T->rchild);
}
void postordertraverse(Bitree T)
{
if(T==NULL)
return ;
postordertraverse(T->lchild);
postordertraverse(T->rchild);
printf("%d\t",T->data);
}
讨论完二叉排序树的生成与遍历,还有一个重要的东西就是二叉排序树的删除。这里需要我们考虑几个问题,进行一下分类。首先是最简单的情况,如果要删除的节点仅仅只有一个左子树或者右子树。或者节点子树为左斜树或者右斜树(PS:这种情况是子树无两个分叉)。这样可以考虑使用链表的删除操作。如果待删除节点含有两个子树,这时候删除节点前,需要为节点寻找一个新的继承节点。那怎么寻找这个节点?我们注意到,这个新的节点要满足大于左子树的所有节点,而小于右子树的所有节点。这里我们的思路就是寻找左子树的子树内的最右节点,或者寻找右子树的子树内的最左节点。这里我们是以前一种思路为主,寻找左子树中的最右节点,下面为代码实现:
int Delete(Bitree *p)
{
Bitree q,s;
if((*p)->rchild==NULL){
q=*p;
*p=(*p)->lchild;
free(q);
}
else if((*p)->lchild==NULL){
q=*p;
*p=(*p)->rchild;
free(q);
}
else{
q=*p;
s=(*p)->lchild;
while(s->rchild){
q=s;
s=s->rchild;
}
(*p)->data=s->data;
if(q!=*p)
q->rchild=s->lchild;
else
q->lchild=s->lchild;
free(s);
}
return 1;
}
int DeleteBST(Bitree *T,int key)
{
if(!*T)
return 0;
else{
if(key==(*T)->data)
return Delete(T);
else if(key<(*T)->data)
return DeleteBST(&(*T)->lchild,key);
else
return DeleteBST(&(*T)->rchild,key);
}
}
然后我们在创建主函数进行测试:
#include"header.h"
#include"function.h"
#define DEBUGPRINT printf("file_name:%s;\ncurrent_line:%d;\ncompile_time:%s:%s;\n\nprogram start...:\n",__FILE__,__LINE__,__DATE__,__TIME__)
int main()
{
DEBUGPRINT;
Bitree T=NULL;
srand(time(0));
printf("please input treenode number:\n");
int n,i;
scanf("%d",&n);
for(i=0;i<=n;i++)
{
insertBST(&T,rand()%100);
}
printf("preordertraverse,root->lchild->rchild.:\n");
preordertraverse(T);
printf("\n");
printf("midordertraverse,lchild->root->rchild.:\n");
midordertraverse(T);
printf("\n");
printf("postordertraverse,visit leaves firstly.lchild->rchild.:\n");
postordertraverse(T);
printf("\n");
printf("delete element of tree...\n");
if(DeleteBST(&T,99)>0){
midordertraverse(T);
printf("\n");
}
else
printf("element does not in this tree\n");
return 0;
}
测试结果如下: