第九章 查找
9.2 动态查找表
一、二叉排序树(二叉查找树)
1.定义:
二叉排序树或者是一棵空树;或者是具有如下特性的二叉树:
(1)若它的左子树不空,则左子树上所有结点的值均小于根结点的值;
(2)若它的右子树不空,则右子树上所有结点的值均大于根结点的值;
(3)它的左、右子树也都分别是二叉排序树。
通常,取二叉链表作为二叉排序树的存储结构
typedef struct BiTNode { // 结点结构
TElemType data;
struct BiTNode *lchild, *rchild; // 左右孩子指针
} BiTNode, *BiTree;
2.二叉排序树的查找算法:
若二叉排序树为空,则查找不成功;否则,
1)若给定值等于根结点的关键字,则查找成功;
2)若给定值小于根结点的关键字,则继续在左子树上进行查找;
3)若给定值大于根结点的关键字,则继续在右子树上进行查找。
从上述查找过程可见,
在查找过程中,生成了一条查找路径:
从根结点出发,沿着左分支或右分支逐层向下直至关键字等于给定值的结点;
——查找成功
从根结点出发,沿着左分支或右分支逐层向下直至指针指向空树为止。
——查找不成功
算法描述如下:
Status SearchBST (BiTree T, KeyType key, BiTree f, BiTree &p ) {
// 在根指针 T 所指二叉排序树中递归地查找其
// 关键字等于 key 的数据元素,若查找成功,
// 则返回指针 p 指向该数据元素的结点,并返回
// 函数值为 TRUE; 否则表明查找不成功,返回
// 指针 p 指向查找路径*问的最后一个结点,
// 并返回函数值为FALSE, 指针 f 指向当前访问
// 的结点的双亲,其初始调用值为NULL
} // SearchBST
if (!T)
{ p = f; return FALSE; } // 查找不成功
else if ( EQ(key, T->data.key) )
{ p = T; return TRUE; } // 查找成功
else if ( LT(key, T->data.key) )
SearchBST (T->lchild, key, T, p ); // 在左子树中继续查找
else SearchBST (T->rchild, key, T, p ); // 在右子树中继续查找
3.二叉排序树的插入算法
·根据动态查找表的定义,“插入”操作在查找不成功时才进行;
·若二叉排序树为空树,则新插入的结点为新的根结点;否则,新插入的结点必为一个新的叶子结点,其插入位置由查找过程得到。
Status Insert BST(BiTree &T, ElemType e )
{
// 当二叉排序树中不存在关键字等于 e.key 的
// 数据元素时,插入元素值为 e 的结点,并返
// 回 TRUE; 否则,不进行插入并返回FALSE
if (!SearchBST ( T, e.key, NULL, p ))
{ }
else return FALSE;
} // Insert BST
s = (BiTree) malloc (sizeof (BiTNode));// 为新结点分配空间
s->data = e;
s->lchild = s->rchild = NULL;
if ( !p ) T = s; // 插入 s 为新的根结点
else if ( LT(e.key, p->data.key) )
p->lchild = s; // 插入 *s 为 *p 的左孩子
else p->rchild = s; // 插入 *s 为 *p 的右孩子
return TRUE; // 插入成功
4.二叉排序树的删除算法
和插入相反,删除在查找成功之后进行,并且要求在删除二叉排序树上某个结点之后,仍然保持二叉排序树的特性。
可分三种情况讨论:
(1)被删除的结点是叶子;
(2)被删除的结点只有左子树或者只有右子树;
(3)被删除的结点既有左子树,也有右子树。
(1)被删除的结点是叶子结点
(2)被删除的结点只有左子树或者只有右子树
(3)被删除的结点既有左子树,也有右子树
算法描述如下:
Status DeleteBST (BiTree &T, KeyType key ) {
// 若二叉排序树 T 中存在其关键字等于 key 的
// 数据元素,则删除该数据元素结点,并返回
// 函数值 TRUE,否则返回函数值 FALSE
if (!T) return FALSE;
// 不存在关键字等于key的数据元素
else { }
} // DeleteBST
if ( EQ (key, T->data.key) )
{ Delete (T); return TRUE; }
// 找到关键字等于key的数据元素
else if ( LT (key, T->data.key) )
DeleteBST ( T->lchild, key );
// 继续在左子树中进行查找
elseD eleteBST ( T->rchild, key );
// 继续在右子树中进行查找
其中删除操作过程如下所描述:
void Delete ( BiTree &p ){
// 从二叉排序树中删除结点 p,
// 并重接它的左子树或右子树
if (!p->rchild) { }
else if (!p->lchild) { }
else { }
} // Delete
// 右子树为空树则只需重接它的左子树
q = p; p = p->lchild; free(q);
// 右子树为空树则只需重接它的左子树
q = p; p = p->lchild; free(q);
// 左右子树均不空
q = p; s = p->lchild;
while (s->rchild) { q = s; s = s->rchild; }
// s 指向被删结点的前驱
p->data = s->data;
if (q != p ) q->rchild = s->lchild;
else q->lchild = s->lchild;
// 重接*q的左子树
free(s);
5.查找性能的分析
对于每一棵特定的二叉排序树,均可按照平均查找长度的定义来求它的 ASL 值,显然,由值相同的 n 个关键字,构造所得的不同形态的各棵二叉排序树的平均查找长 度的值不同,甚至可能差别很大。
例如:
由关键字序列 1,2,3,4,5构造而得的二叉排序树,ASL =(1+2+3+4+5)/ 5= 3
由关键字序列 3,1,2,5,4构造而得的二叉排序树,ASL =(1+2+3+2+3)/ 5 = 2.2
下面讨论平均情况:
不失一般性,假设长度为 n 的序列中有 k 个关键字小于第一个关键字,则必有 n-k-1 个关键字大于第一个关键字,由它构造的二叉排序树:
的平均查找长度是 n 和 k 的函数P(n, k) ( 0< =k<=n-1 )。
假设 n 个关键字可能出现的 n! 种排列的可能性相同,则含 n 个关键字的二叉排序树的平均查找长度:在等概率查找的情况下,
由此
可类似于解差分方程,此递归方程有解:
二、二叉平衡树
二叉平衡树是二叉查找树的另一种形式,其特点为: 树中每个结点的左、右子树深度之差的绝对值不大于1
结点的平衡因子:该结点的左子树的深度减去
( -1 0 1 ) 右子树的深度
构造二叉平衡(查找)树的方法是:在插入过程中,采用平衡旋转技术。
假设由于在二叉排序树上插入结点而失去平衡的最小子树根结点为A,沿插入
路径取直接下两层的结点为B和C,如果这三个结点处于一条直线上,则采用单旋转进行平衡化;如果这三个结点处于一条折线线上,则采用双旋转进行平衡化。失去平衡后进行调整的规律可归纳为下列4种情况:
(1)单向右旋平衡处理:(LLR)
原因:在A 的左子树根结点的左子树上插入结点,A的平衡因子由1增至2,至使以A为根的子树失去平衡。
调整方法:以结点B为旋转轴,将结点A向右旋转成为B的右子树,结点B代替原来A的位置,原来B的右子树成为A的左子树。
(2)单向左旋平衡处理:(RRL)
原因:在A 的右子树根结点的右子树上插入结点,A的平衡因子由-1增至-2,至使以A为根的子树失去平衡。
调整方法:以结点B为旋转轴,将结点A向左旋转成为B的左子树,结点B代替原来A的位置,原来B的左子树成为A的右子树。
(3)先左后右平衡处理:(LRLR)
原因:在A 的左子树根结点的右子树上插入结点,A的平衡因子由1增至2,至使以A为根的子树失去平衡。
调整方法:先以C为旋转轴,将结点B向左旋转成为C的左子树,结点C代替原来B的位置,原来C的左子树成为B的右子树。再以C为旋转轴,将A向右旋转成为C的右子树,结点C代替原来A的位置,原来C的右子树成为B的左子树。
(4)先右后左平衡处理:(RLRL)
原因:在A 的右子树根结点的左子树上插入结点,A的平衡因子由-1增至-2,至使以A为根的子树失去平衡。
调整方法:先以C为旋转轴,将结点B向右旋转成为C的右子树,结点C代替原来B的位置,原来C的右子树成为B的左子树。再以C为旋转轴,将A向左旋转成为C的左子树,结点C代替原来A的位置,原来C的左子树成为B的右子树。
平衡树的查找性能分析:
在平衡树上进行查找的过程和二叉排序树相同,因此,查找过程中和给定值进行比较的关键字的个数不超过平衡 树的深度。
问:含 n 个关键字的二叉平衡树可能达到的最大深度是多少?
先看几个具体情况:
反过来问,深度为 h 的二叉平衡树中所含结点的最小值 Nh 是多少?
由此推得,深度为 h 的二叉平衡树中所含结点的最小值 Nh = jh+2/5 - 1。
反之,含有 n 个结点的二叉平衡树能达到的最大深度 hn = logj(Ö5 (n+1)) - 2。
因此,在二叉平衡树上进行查找时,查找过程中和给定值进行比较的关键字的个数和 log(n) 相当。