第八章:查找

查找表是称为集合的数据结构。是元素间约束力最差的数据结构:元素间的关系是元素仅共在同一个集合中。(同一类型的数据元素构成的集合)

1.静态查找表

  1)顺序查找(线性查找)

  技巧:把待查关键字key存入表头或表尾(俗称“哨兵”),这样可以加快执行速度。

int Search_Seq( SSTable  ST , KeyType  key ){

ST.elem[0].key =key;

for( i=ST.length; ST.elem[ i ].key!=key;  - - i  );

      return i;

} // Search_Seq

//ASL=(1+n)/2,时间效率为 O(n),这是查找成功的情况:

顺序查找的特点:

优点:算法简单,且对顺序结构或链表结构均适用。

       缺点: ASL 太大,时间效率太低。

  2)折半查找(二分查找)——只适用于有序表,且限于顺序存储结构。

  若关键字不在表中,怎样得知并及时停止查找?

  典型标志是:当查找范围的上界≤下界时停止查找。

  ASL的含义是“平均每个数据的查找时间”,而前式是n个数据查找时间的总和,所以:
 

3)分块查找(索引顺序查找)

思路:先让数据分块有序,即分成若干子表,要求每个子表中的数据元素值都比后一块中的数值小(但子表内部未必有序)。然后将各子表中的最大关键字构成一个索引表,表中还要包含每个子表的起始地址(即头指针)。

特点:块间有序,块内无序。

查找:块间折半,块内线性

查找步骤分两步进行:

① 对索引表使用折半查找法(因为索引表是有序表);

② 确定了待查关键字所在的子表后,在子表内采用顺序查找法(因为各子表内部是无序表);

查找效率ASL分析:
 

2.动态查找表

1)二叉排序树和平衡二叉树

二叉排序树的定义----或是一棵空树;或者是具有如下性质的非空二叉树:

 (1)若它的左子树不空,则左子树上所有结点的值均小于根的值;

 (2)若它的右子树不空,则右子树的所有结点的值均大于根的值;

 (3)它的左右子树也分别为二叉排序树。

二叉排序树又称二叉查找树。

二叉排序树的查找过程:

BiTree SearchBST(BiTree T, KeyType key)

{

        //在根指针T所指二叉排序树中递归地查找某关键字等于key的数据元素,

        //若查找成功,则返回指向该数据元素结点的指针,否则返回空指针

        if ((!T)||EQ(key, T->data.key))  return(T); //查找结束

        else if LT(key, T->data.key) return (SearchBST(T->lchild, key)); //在左子树中继续查找

        else return (SearchBST(T->rchild,key)); //在右子树中继续查找

}

二叉排序树的插入

        思路:查找不成功,生成一个新结点s,插入到二叉排序树中;查找成功则返回。

   SearchBST (K,  &t) { //K为待查关键字,t为根结点指针

   p=t;       //p为查找过程中进行扫描的指针

   while(p!=NULL)

{

   case {

               K= p->data:  {查找成功,return true;}

               K< p->data :  {q=p;p=p->lchild }  //继续向左搜索

               K> p->data :  {q=p;p=p->rchild } //继续向右搜索

            }

  }  //查找不成功则插入到二叉排序树中

s =(BiTree)malloc(sizeof(BiTNode)); 

s->data=K; s ->lchild=NULL; s ->rchild=NULL;

      //查找不成功,生成一个新结点s,插入到二叉排序树叶子处

case {

            t=NULL:   t=s;   //若t为空,则插入的结点s作为根结点

            K < q->data: q->lchild=s;  //若K比叶子小,挂左边

            K > q->data: q->rchild=s; //若K比叶子大,挂右边

        }

return OK;

}

二叉排序树的删除

假设:*p表示被删结点的指针; PL和PR 分别表示*P的左、右孩子指针;

*f表示*p的双亲结点指针;并假定*p是*f的左孩子;则可能有三种情况:
 

*p有两颗子树,有两种解决方法:

法1:令*p的左子树为 *f的左子树,*p的右子树接为*s的右子树;如下图(c)所示  //即 fL=PL  ;   SR=PR   ;

法2:直接令*p的直接前驱(或直接后继)替代*p,然后再从二叉排序树中删去它的直接前驱(或直接后继) 。如图(d),当以直接前驱*s替代*p时,由于*s只有左子树SL,则在删去*s之后,只要令SL为*s的双亲*q的右子树即可。 // *s为*p左子树最右下方的结点
 

删除算法如下:

Status Delete(BiTree &p)

{

    //从二叉排序树种删除结点p,并重接它的左或右子树

    if(!p->rchild) //右子树空,只需重接它的左子树

    {

        q=p;

        p=p->lchild;

        free(q);

    }

    else if(!p->lchild) //左子树空,只需重接它的右子树

    {

        q=p;

        p=p->rchild;

        free(q);

    }

    else //左右子树都不空

    {

        q=p; 

        s=p->lchild;

        while(s->rchild)  //转左,然后向右到尽头(找p的直接前驱) 图(b)

        {

            q=s;

            s=s->rchild;

        }

        p->data = s->data; //s指向被删结点的“前驱”

        if(q!=p)  //重接*q的右子树

        {

            q->rchild=s->lchild;

        }

        else  //q=p,说明s->rchild为空(即:p->lchild->rchild为空),重接*q的左子树

        {

            q->lchild=s->lchild;

        }

         delete s;

    }//end else 左右子树都不空

    return TRUE;

}

二叉排序树查找分析:和折半查找类似,与给定值比较的关键字个数不超过树的深度。然而,折半查找长度为n的表的判定树是惟一的,而含有n个结点的二叉排序树却不惟一。

含有n个结点的二叉排序树的平均查找长度和树的形态有关。当先后插入的关键字有序时,构成的二叉排序树蜕变为单支树。树的深度为n,其平均查找长度为(n+1)/2(和顺序查找相同),这是最差的情况。最好的情况是二叉排序树的形态和折半查找的判定树相同,其平均查找长度和log2n成正比。
 

平衡二叉树

又称AVL树,即它或者是一颗空树,或者具有如下性质:它的左子树和右子树都是平衡二叉树,且左子树与右子树的深度之差的绝对值不超过1。

平衡因子:该结点的左子树的深度减去它的右子树的深度。

平衡二叉树的特点:任一结点的平衡因子只能取:-1、0 或 1。

如果在一棵AVL树中插入一个新结点,就有可能造成失衡,此时必须重新调整树的结构,使之恢复平衡。我们称调整平衡过程为平衡旋转。

平衡旋转可以归纳为四类:单向右顺时针旋转(LL);单向左逆时针旋转(RR);双向旋转先左逆时针后右顺时针(LR);双向旋转先右顺时针后左逆时针(RL)
 

平衡二叉树查找分析:

时间复杂度为O(logn)

3.B-树和B+树

B+树是应文件系统所需而出的一种B树的变型树。一棵m阶的B+树和m阶的B-树的差异在于:

1.有n棵子树的结点中含有n个关键字,每个关键字不保存数据,只用来索引,所有数据都保存在叶子节点。

2.所有的叶子结点中包含了全部关键字的信息,及指向含这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。

3.所有的非终端结点可以看成是索引部分,结点中仅含其子树(根结点)中的最大(或最小)关键字。

通常在B+树上有两个头指针,一个指向根结点,一个指向关键字最小的叶子结点。

B-树:一棵m阶的B-树或者是一棵空树,或者是满足下列要求的m叉树:

树中的每个结点至多有m棵子树;

若根结点不是叶子结点,则至少有两棵子树;

除根结点外,所有非终端结点至少有[ m/2 ] ( 向上取整 )棵子树。

所有的非终端结点中包括如下信息的数据(n,A0,K1,A1,K2,A2,….,Kn,An)

其中:Ki(i=1,2,…,n)为关键码,且Ki < K(i+1),

Ai 为指向子树根结点的指针(i=0,1,…,n),且指针A(i-1) 所指子树中所有结点的关键码均小于Ki (i=1,2,…,n),An 所指子树中所有结点的关键码均大于Kn。n 为关键码的个数。

所有的叶子结点都出现在同一层次上,并且不带信息(可以看作是外部结点或查找失败的结点,实际上这些结点不存在,指向这些结点的指针为空)。

 

4.哈希表

哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

 

1)哈希函数构造方法

直接定址法

取关键字或关键字的某个线性函数值为散列地址。

即 H(key) = key 或 H(key) = a*key + b,其中a和b为常数。

除留余数法

取关键字被某个不大于散列表长度 m 的数 p 求余,得到的作为散列地址。

即 H(key) = key % p, p < m。

数字分析法

当关键字的位数大于地址的位数,对关键字的各位分布进行分析,选出分布均匀的任意几位作为散列地址。

仅适用于所有关键字都已知的情况下,根据实际应用确定要选取的部分,尽量避免发生冲突。

平方取中法

先计算出关键字值的平方,然后取平方值中间几位作为散列地址。

随机分布的关键字,得到的散列地址也是随机分布的。

折叠法(叠加法)

将关键字分为位数相同的几部分,然后取这几部分的叠加和(舍去进位)作为散列地址。

用于关键字位数较多,并且关键字中每一位上数字分布大致均匀。

随机数法

选择一个随机函数,把关键字的随机函数值作为它的哈希值。

通常当关键字的长度不等时用这种方法。

构造哈希函数的方法很多,实际工作中要根据不同的情况选择合适的方法,总的原则是尽可能少的产生冲突。

通常考虑的因素有关键字的长度和分布情况、哈希值的范围等。

如:当关键字是整数类型时就可以用除留余数法;如果关键字是小数类型,选择随机数法会比较好。

 

2)哈希冲突的解决方法

 

开放定址法

Hi=(H(key) + di) MOD m i=1,2,…,k (k<=m)

当冲突发生时,使用某种探测技术在散列表中形成一个探测序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探测到开放的地址则表明表中无待查的关键字,即查找失败。

当冲突发生时,使用某种探查(亦称探测)技术在散列表中寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到。

按照形成探查序列的方法不同,可将开放定址法区分为线性探查法、二次探查法、双重散列法等。

a.线性探查法

hi=(h(key)+i) % m ,0 ≤ i ≤ m-1

基本思想是:探查时从地址 d 开始,首先探查 T[d],然后依次探查 T[d+1],…,直到 T[m-1],此后又循环到 T[0],T[1],…,直到探查到 有空余地址 或者到 T[d-1]为止。

b.二次探查法

hi=(h(key)+i*i) % m,0 ≤ i ≤ m-1

基本思想是:探查时从地址 d 开始,首先探查 T[d],然后依次探查 T[d+1^2],T[d+2^2],T[d+3^2],…,等,直到探查到 有空余地址 或者到 T[d-1]为止。缺点是无法探查到整个散列空间。

c.双重散列法

hi=(h(key)+i*h1(key)) % m,0 ≤ i ≤ m-1

基本思想是:探查时从地址 d 开始,首先探查 T[d],然后依次探查 T[d+h1(d)], T[d + 2*h1(d)],…,等。

该方法使用了两个散列函数 h(key) 和 h1(key),故也称为双散列函数探查法。

定义 h1(key) 的方法较多,但无论采用什么方法定义,都必须使 h1(key) 的值和 m 互素,才能使发生冲突的同义词地址均匀地分布在整个表中,否则可能造成同义词地址的循环计算。

该方法是开放定址法中最好的方法之一。

链接法(拉链法)

将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为 m,则可将散列表定义为一个由 m 个头指针组成的指针数组 T[0..m-1] 。

凡是散列地址为 i 的结点,均插入到以 T[i] 为头指针的单链表中。

T 中各分量的初值均应为空指针。

在拉链法中,装填因子 α 可以大于 1,但一般均取 α ≤ 1。
 

3.哈希表的查找及其分析

哈希表是实现关联数组(associative array)的一种数据结构,广泛应用于实现数据的快速查找。

查找过程中,关键字的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。

影响产生冲突多少有以下三个因素:

1)哈希函数是否均匀;

2)处理冲突的方法;

3)哈希表的加载因子。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值