二叉排序树(简称BST)又称二叉查找(搜索)树,其定义为:二叉排序树或者是空树,或者是满足如下性质的二叉树:
(1)若它的左子树非空,则左子树上所有记录的值均小于根记录的值;
(2)若它的右子树非空,则右子树上所有记录的值均大于根记录的值;
(3)左、右子树本身又各是一棵二叉排序树。
注意:二叉排序树中没有相同关键字的节点。
对二叉排序树进行中序遍历,便可得到一个有序序列,该有序序列中的各元素按照从小到大的顺序排列,因此一个无序序列可以通过构造一棵二叉排序树而变成一个有序序列。
二叉排序树相关操作
二叉排序树的生成、插入、查找、查找最大值、查找最小值、删除、中序遍历、销毁等操作。见下面的代码,注释的非常清楚。直接看代码理解吧。
/*********************************
树表的查找——二叉排序树的相关操作实现(创建,插入,查找,删除)
Author:_牧之 Date:2015年4月27日
Email:bzhou84@163.com
**********************************/
/* 二叉树的二叉链表结点结构定义 */
typedef struct node /* 结点结构 */
{
int data; /* 结点数据 */
struct node *lchild,*rchild; /* 左右孩子指针 */
}BSTNode,*BSTree;
//插入操作
/*节点插入时需要从根节点开始比较,比根节点的data小,指针移到左子树,否则移到右子树,直到指针为空,创建节点,指针指向它,便完成插入。
在以*p为根节点的BST中插入一个关键字为key的节点。插入成功返回1,否则返回0*/
int InsertBST(BSTree &p,int key) /*或者写成int InsertBST(BSTNode *&p,int key),树空时插入会改变根节点p的值,所以一定要用引用类型*/
{
if (p==NULL) /*原树为空, 新插入的记录为根节点*/
{
p=(BSTNode *)malloc(sizeof(BSTNode));
p->data=key;
p->lchild=p->rchild=NULL;
return 1;
}
else if (key < p->data)
return InsertBST(p->lchild,key); /*插入到*p的左子树中*/
else if (key > p->data)
return InsertBST(p->rchild,key); /*插入到右子树中*/
else /*树中存在相同关键字的节点,返回0*/
return 0;
}
//生成操作
/*
二叉排序树的生成,是从一个空树开始,每插入一个关键字,就调用一次插入算法将它插入到当前已生成的二叉排序树中。任何节点插入到二叉排序树时,都是以叶子节点插入的。
元素插入的先后次序不同,构成的二叉排序树的形态和深度也可能不同。
CreateBST()返回二叉排序树的根节点指针*/
BSTree CreateBST(int a[],int n)
{
BSTree bst=NULL; /*初始时bst为空树*/
int i=0;
while(i<n)
{
InsertBST(bst,a[i]); /*将关键字a[i]插入二叉排序树bst中*/
i++;
}
return bst; /*返回建立的二叉排序树的根指针*/
}
//查找操作
/*
递归查找算法SearchBST()如下(在二叉排序树bst上查找关键字为key的记录,成功时返回该节点指针,否则返回NULL)*/
BSTNode *SearchBST(BSTree bst,int key)
{
if(bst==NULL||bst->data==key) /*递归终结条件*/
return bst;
if (key < bst->data)
return SearchBST(bst->lchild,key); /*在左子树中递归查找*/
else
return SearchBST(bst->rchild,key); /*在右子树中递归查找*/
}
/*
非递归查找算法SearchBST1()如下(在二叉排序树bst上查找关键字为key的记录,成功时返回该节点指针,否则返回NULL)*/
BSTNode *SearchBST1(BSTree bst,int key)
{ while (bst!=NULL)
{
if (key==bst->data)
return bst;
else if (key<bst->data)
bst=bst->lchild; //在左子树中查找
else
bst=bst->rchild; //在右子树中查找
}
return NULL; //没有找到返回NULL
}
/*查找关键字key,同时找到父节点
递归查找算法SearchBST2()如下(在二叉排序树bst上查找关键字为key的记录,成功时返回该节点指针,f返回其双亲节点;否则返回NULL)*/
BSTNode *SearchBST2(BSTree bst,int key,BSTNode *f1,BSTNode *&f)
//f1为中间参数,用于求f,初始设为NULL,其目的是跟踪查找路径上访问的当前节点的父节点(即上一个访问节点)
{
if (bst==NULL)
{
f=NULL;
return NULL;
}
else if (key==bst->data)
{
f=f1;
return bst;
}
else if(key < bst->data)
return SearchBST2(bst->lchild,key,bst,f);
else
return SearchBST2(bst->rchild,key,bst,f);
}
/*查找二叉排序树中最大节点关键字key
一棵二叉排序树中的最大节点为根节点的最右下节点
查找算法MaxNode()如下(在二叉排序树bst上查找最大节点关键字,返回该节点关键字*/
int MaxNode(BSTree bst)
{
while(bst->rchild!=NULL)
bst=bst->rchild;
return bst->data;
}
/*查找二叉排序树中最小节点关键字key
一棵二叉排序树中的最小节点为根节点的最左下节点
查找算法MinNode()如下(在二叉排序树bst上查找最小节点关键字,返回该节点关键字*/
int minnode(BSTree bst)
{
while (bst->lchild!=NULL)
bst=bst->lchild;
return(bst->data);
}
//删除操作
/*(1)被删除的节点是叶子节点:直接删去该节点。
(2)被删除的节点只有左子树或者只有右子树,用其左子树或者右子树代替它。
(3)被删除的节点既有左子树,也有右子树,以其前驱替代之,然后再删除该前驱节点。前驱是左子树中最大的节点。
(也可以用其后继替代之,然后再删除该后继节点。后继是右子树中最小的节点。)
删除会改变bst的值,所以一定要用引用类型
删除算法DeleteBST()如下(在二叉排序树bst上删除关键字为key的记录,成功时返回1,否则返回0)*/
void Delete(BSTNode *&p);
void Delete1(BSTNode *p,BSTNode *&r);
int DeleteBST(BSTree &bst,int key)
{
if(bst==NULL)
return 0; //空树删除失败
else
{
if(key < bst->data)
return DeleteBST(bst->lchild,key); //递归在左子树中删除为k的节点
else if (key > bst->data)
return DeleteBST(bst->rchild,key); //递归在右子树中删除为k的节点
else
{
Delete(bst); //调用Delete(bt)函数删除*bt节点
return 1;
}
}
}
void Delete(BSTNode *&p) //从二叉排序树中删除*p节点
{
BSTNode *q;
if (p->rchild==NULL) //*p节点没有右子树的情况
{
q=p;
p=p->lchild; //直接将其左子树的根节点放在被删节点的位置上
free(q);
}
else if(p->lchild==NULL) //*p节点没有左子树
{
q=p;
p=p->rchild; //直接将其右子树的根节点放在被删节点的位置上
free(q);
}
else
Delete1(p,p->lchild); //*p节点既有左子树又有右子树的情况
}
void Delete1(BSTNode *p,BSTNode *&r) //当被删*p节点有左右子树时的删除过程
{
BSTNode *q;
if (r->rchild!=NULL)
Delete1(p,r->rchild); //递归找最右下节点
else //找到了最右下节点*r
{
p->data=r->data; //将*r的关键字值赋给*p
q=r; //将左子树的根节点放在被删节点的位置上
r=r->lchild;
free(q); //释放最右下节点*r的空间
}
}
/*
递归中序遍历二叉排序树,得到元素从小到大有序排列的序列
*/
void in_traverse(BSTree bst)
{
if(bst)
{
if(bst->lchild)
in_traverse(bst->lchild);
printf("%d ",bst->data);
if(bst->rchild)
in_traverse(bst->rchild);
}
printf("二叉排序树已销毁");
}
/*
递归销毁二叉排序树
销毁时会改变bst的值,所以一定要用引用类型
*/
void destroy_BSTree(BSTree &bst)
{
if(bst)
{
if(bst->lchild)
destroy_BSTree(bst->lchild);
if(bst->rchild)
destroy_BSTree(bst->rchild);
free(bst);
bst = NULL;
}
}
int main()
{
/* int a[8] = {3,1,1,7,2,4,9,62};
print(a,8);
cout<<"查找元素值为7的数(成功返回逻辑序号,否则为0):"<<BinSearch(a,8,7)<<endl;
cout<<"查找元素值为8的数(成功返回逻辑序号,否则为0):"<<BinSearch(a,8,8)<<endl;
*/
int i;
int num;
printf("请输入节点个数:");
scanf("%d",&num);
//输入num个整数
int *arr = (int *)malloc(num*sizeof(int));
printf("请依次输入这%d个整数(必须互不相等):",num);
for(i=0;i<num;i++)
scanf("%d",arr+i);
//中序遍历该二叉排序树,使数据按照从小到大的顺序输出
BSTree bst = CreateBST(arr,num);
printf("中序遍历该二叉排序树的结果:");
in_traverse(bst);
printf("\n");
//查找给定的整数
int key;
printf("请输入要查找的整数:");
scanf("%d",&key);
if(SearchBST(bst,key))
printf("查找成功\n");
else
printf("查找不到该整数\n");
//插入给定的整数
printf("请输入要插入的整数:");
scanf("%d",&key);
if(InsertBST(bst,key))
{
printf("插入成功,插入后的中序遍历结果:");
in_traverse(bst);
printf("\n");
}
else
printf("插入失败,该二叉排序树中已经存在整数%d\n",key);
//删除给定的整数
printf("请输入要删除的整数:");
scanf("%d",&key);
if(DeleteBST(bst,key))
{
printf("删除成功,删除后的中序遍历结果:");
in_traverse(bst);
printf("\n");
}
else
printf("删除失败,该二叉排序树中不存在整数%d\n",key);
//销毁bst
destroy_BSTree(bst);
in_traverse(bst);
system("pause");
return 0;
}
性能分析
每次插入的新的结点都是二叉排序树上新的叶子结点,在进行插入操作时,不必移动其它结点,只需改动某个结点的指针,由空变为非空即可。搜索、插入、删除的时间复杂度等于树高,期望O(logn),最坏O(n)(数列有序,树退化成线性表,如右斜树)。
每个结点的Ci为该结点的层次数。最好的情况是二叉排序树的形态和折半查找的判定树相同,其平均查找长度和logn成正比(O(log2(n)))。最坏情况下,当先后插入的关键字有序时,构成的二叉排序树为一棵斜树,树的深度为n,其平均查找长度为(n + 1) / 2。也就是时间复杂度为O(n),等同于顺序查找。因此,如果希望对一个集合按二叉排序树查找,最好是把它构建成一棵平衡的二叉排序树(平衡二叉树)。
Reference:
[1] 《算法导论》
[2]数据结构教程(李春葆)
[3] http://www.cnblogs.com/zhuyf87/archive/2012/11/09/2763113.html
[4]http://blog.csdn.net/ns_code/article/details/19823463