顺序查找、二分(折半)查找和索引查找都是静态查找表,其中二分查找的效率最高。
静态查找表的缺点是当表的插入或删除操作频繁时,为维护表的有序性,需要移动表中很多记录。
这种由移动记录引起的额外时间开销,就会抵消二分查找的优点(二分查找和分块查找只适用于静态查找表)。
若要对动态查找表进行高效率的查找,可以使用树表。
以二叉树或树作为表的组织形式,称为树表。
一、二叉排序树
二叉排序树(简称BST)又称二叉查找(搜索)树,其定义为:二叉排序树或者是空树,或者是满足如下性质的二叉树:
(1)若它的左子树非空,则左子树上所有记录的值均小于根记录的值;
(2)若它的右子树非空,则右子树上所有记录的值均大于根记录的值;
(3)左、右子树本身又各是一棵二叉排序树。
在讨论二叉排序树上的运算之前,定义其节点的类型如下:
typedef struct node //记录类型{ KeyType key; //关键字项
InfoType data; //其他数据域
struct node *lchild,*rchild; //左右孩子指针
} BSTNode;
1. 二叉排序树上的查找
因为二叉排序树可看做是一个有序表,所以在二叉排序树上进行查找,和二分查找类似,也是一个逐步缩小查找范围的过程。
递归查找算法SearchBST()如下(在二叉排序树bt上查找关键字为k的记录,成功时返回该节点指针,否则返回NULL):
BSTNode *SearchBST(BSTNode *bt,KeyType k){ if (bt==NULL || bt->key==k) //递归终结条件
return bt;
if (k<bt->key)
return SearchBST(bt->lchild,k); //在左子树中递归查找
else
return SearchBST(bt->rchild,k); //在右子树中递归查找
}
也可以采用如下非递归算法:
BSTNode *SearchBST1(BSTNode *bt,KeyType k)
{ while (bt!=NULL)
{
if (k==bt->key)
return bt;
else if (k<bt->key)
bt=bt->lchild; //在左子树中递归查找
else
bt=bt->rchild; //在左子树中递归查找
}
else //没有找到返回NULL
return NULL;
}
2. 二叉排序树的插入和生成
在二叉排序树中插入一个关键字为k的新记录,要保证插入后仍满足BST性质。
插入过程:
(1)若二叉排序树T为空,则创建一个key域为k的节点,将它作为根节点;
(2)否则将k和根节点的关键字比较,若两者相等,则说明树中已有此关键字k,无须插入,直接返回0;
(3)若k 小于T->key,则将k插入根节点的左子树中。
(4)否则将它插入右子树中。
对应的递归算法InsertBST()如下:
int InsertBST(BSTNode *&p,KeyType k)
//在以*p为根节点的BST中插入一个关键字为k的节点。插入成功返回1,否则返回0
{ if (p==NULL) //原树为空, 新插入的记录为根节点
{ p=(BSTNode *)malloc(sizeof(BSTNode));
p->key=k;p->lchild=p->rchild=NULL;
return 1;
}
else if (k==p->key) //存在相同关键字的节点,返回0
return 0;
else if (k<p->key)
return InsertBST(p->lchild,k); //插入到左子树中
else
return InsertBST(p->rchild,k); //插入到右子树中
}
二叉排序树的生成,是从一个空树开始,每插入一个关键字,就调用一次插入算法将它插入到当前已生成的二叉排序树中。
从关键字数组A[0..n-1]生成二叉排序树的算法CreatBST()如下:
BSTNode *CreatBST(KeyType A[],int n) //返回树根指针{ BSTNode *bt=NULL; //初始时bt为空树
int i=0;
while (i<n)
{ InsertBST(bt,A[i]); //将A[i]插入二叉排序树T中
i++;
}
return bt; //返回建立的二叉排序树的根指针
}
3. 二叉排序树的节点删除
(1)被删除的节点是叶子节点:直接删去该节点。
(2)被删除的节点只有左子树或者只有右子树,用其左子树或者右子树代替它。
(3)被删除的节点既有左子树,也有右子树:以其前驱替代之,然后再删除该前驱节点。前驱是左子树中最大的节点。也可以用其后继替代之,然后再删除该后继节点。后继是右子树中最小的节点。
int DeleteBST(BSTNode *&bt,KeyType k)
//在bt中删除关键字为k的节点
{ if (bt==NULL) return 0; //空树删除失败
else
{ if (k<bt->key)
return DeleteBST(bt->lchild,k); //递归在左子树中删除为k的节点
else if (k>bt->key)
return DeleteBST(bt->rchild,k); //递归在右子树中删除为k的节点
else{ Delete (bt); //调用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;
//将*p节点的右子树作为双亲节点的相应子树/
free(q);
}
else Delete1(p,p->lchild);
//*p节点既没有左子树又没有右子树的情况
}
删除二叉排序树节点的算法DeleteBST()如下(指针变量p指向待删除的节点,指针变量q指向待删除节点*p的双亲节点):
void Delete1 (BSTNode *p,BSTNode *&r)//当被删*p节点有左右子树时的删除过程
{ BSTNode *q;
if (r->rchild!=NULL)
Delete1(p,r->rchild); //递归找最右下节点
else //找到了最右下节点*r
{ p->key=r->key; //将*r的关键字值赋给*p
q=r; r=r->lchild;
//将左子树的根节点放在被删节点的位置上
free(q); //释放原*r的空间
}
}
二、平衡二叉树(AVL)
若一棵二叉树中每个节点的左、右子树的高度至多相差1,则称此二叉树为平衡二叉树。
在算法中,通过平衡因子(balancd factor,用bf表示)来具体实现上述平衡二叉树的定义。
平衡因子:平衡二叉树中每个节点有一个平衡因子域,每个节点的平衡因子是该节点左子树的高度减去右子树的高度。从平衡因子的角度可以说,若一棵二叉树中所有节点的平衡因子的绝对值小于或等于1,即平衡因子取值为1、0或-1,则该二叉树称为平衡二叉树。
定义AVL树的节点的类型如下:
typedef struct node //记录类型{ KeyType key; //关键字项
int bf; //增加的平衡因子
InfoType data; //其他数据域
struct node *lchild,*rchild; //左右孩子指针
} BSTNode;
平衡二叉树中插入新节点方式与二叉排序树相似,只是插入后可能破坏了平衡二叉树的平衡性,解决方法是调整。
调整操作可归纳为下列四种情况:
(1)LL型调整
如图所示,将α的根节点B向左上翻转与点A互换位置,调整节点将β放在A的左子树
如图所示,将4向左上翻转与5互换位置,2仍旧为4的左子树,5调整为4的右子树
(2)RR型调整
如图所示,将γ的根节点B向右上翻转与点A互换位置,调整节点将β放在A的右子树
如图所示,将6向左上翻转与4互换位置,8仍旧为6的右子树,4调整为6的左子树,节点5调整为4的右子树
(3)LR型调整
如图所示,将β的根节点C向左上翻转与点B互换位置,再向右上翻转与点A互换位置,调整节点将B作为C的左子树,A作为C的右子树,β作为B的右子树,γ作为A的左子树
例9.3 输入关键字序列{16,3,7,11,9,26,18,14,15},给出构造一棵AVL树的步骤。
在平衡二叉树上进行查找的过程和在二叉排序树上进行查找的过程完全相同,因此,在平衡二叉树上进行查找关键字的比较次数不会超过平衡二叉树的深度。
在最坏的情况下,普通二叉排序树的查找长度为O(n)。含有n个节点的平衡二叉树的平均查找长度为O(log2n)。