820数据结构(6)查找

本文详细讲解了数据结构中的查找算法(顺序、折半、索引和哈希查找),包括基本概念、查找效率分析、哈希函数构造及冲突解决策略(拉链法、开放定址法)。重点探讨了时间复杂度和空间复杂度,以及如何通过哈希优化查找性能。
摘要由CSDN通过智能技术生成

考纲

  1. 查找的基本概念;静态查找与动态查找
  2. 顺序查找、折半查找、索引查找
  3. 哈希查找
    哈希函数的基本构造方法,解决地址冲突的基本策略
  4. 各种查找算法的时间复杂度和空间复杂度

选填考的概率大,算法考的概率较小,其中折半最可能考算法。此处的“时间复杂度和空间复杂度”实际多考察ASL。

一、查找的基本概念

  1. 查找:在数据集合中寻找满足某种条件的数据元素的过程。
  2. 查找表(查找结构):用于查找的数据集,由同一类型的数据元素或记录组成。
  3. 关键字:数据元素中唯一标识该元素的某个数据项的值。
  4. 静态查找表:只需进行操作①;
    动态查找表:也要进行操作②。
    ①查找符合条件的数据元素
    ②插入、删除某个数据元素
  5. 查找长度:查找运算中,需要比对关键字的次数。
  6. 平均查找长度ASL:所有查找过程中进行关键字的比较次数的平均值。通常认为查找任何一个元素的概率都相同。ASL的数量级反应了查找算法时间复杂度。通常考虑查找成功、查找失败两种情况下的ASL。
    A S L = ∑ i = 1 n P i C i ASL=\sum_{i=1}^{n}P_iC_i ASL=i=1nPiCi
    n:数据元素个数;P:查找第i个元素的概率;C:查找第i个元素的查找长度。

二、顺序查找

  • 顺序查找:也称线性查找,通常用于线性表(顺序/链式存储)
  • 从头到尾或从尾到头依次查找。
  • 对线性的链表只能进行顺序查找

1、一般线性表的顺序查找

  • 0号位置存“哨兵”,不必判断数组是否会越界,效率更高。
  • 查找效率分析(若 P i = 1 n P_i=\frac{1}{n} Pi=n1):
    • A S L 成 功 = ∑ i = 1 n P i ( n − i + 1 ) = ( 1 + 2 + 3 + ⋅ ⋅ ⋅ + n ) n = n + 1 2 ASL_{成功}=\sum_{i=1}^{n}P_i(n-i+1)=\frac{(1+2+3+···+n)}{n}=\frac{n+1}{2} ASL=i=1nPi(ni+1)=n(1+2+3++n)=2n+1
    • A S L 失 败 = n + 1 ASL_{失败}=n+1 ASL=n+1
    • 即时间复杂度 O ( n ) O(n) O(n)

2、有序表的顺序查找

  • 顺序查找判定树
    在这里插入图片描述
  • 一个成功结点的查找长度=自身所在层数;一个失败结点的查找长度=父节点所在层数。有n+1个失败结点,n个成功结点
  • 查找效率分析:
    A S L 失 败 = ∑ j = 1 n q j ( l j − 1 ) = ( 1 + 2 + 3 + ⋅ ⋅ ⋅ + n + n ) n + 1 = n 2 + n n + 1 ASL_{失败}=\sum_{j=1}^{n}q_j(l_j-1)=\frac{(1+2+3+···+n+n)}{n+1}=\frac{n}{2}+\frac{n}{n+1} ASL=j=1nqj(lj1)=n+1(1+2+3++n+n)=2n+n+1n
    q j q_j qj是到达第j个失败结点的概率,假设为 1 n + 1 \frac{1}{n+1} n+11
  • 优化:被查概率不等时,把被查概率大的放在靠前位置,ASL更小。

三、折半查找

  • 折半查找,又称二分查找,仅适用于有序顺序表。顺序表具有随机访问的特性,链表没有。

1、算法

  1. 思想
    1. 在[low, high]之间找目标关键字,每次检查mid=(low+high)/2。
    2. 根据mid所指元素与目标关键字的大小调整low或high,不断缩小low和high的范围。
    3. 若low>high则查找失败。
  2. 手动推导
    在这里插入图片描述
  3. 算法实现
    typedef struct{                //查找表的
        ElemType *elem;            //动态数组基址
        int TableLen;              //表的长度
    }SSTable;
    //折半查找(假设升序)
    int Binary_Search(SSTable L,ElemType key){
        int low=0, high=L, TableLen-1, mid;
        while(low<=high){
            mid = (low+high)/2;    //取中间位置(向下取整)
     	   if(L.elem[mid]==key)
     		   return mid;         //查找成功则返回所在位置
     	   else if(L.elem[mid]>key)
     		   high = mid-1;       //从前半部分继续查找
     	   else
     		   low = mid+1;        //从后半部分继续查找
        }
        return -1//查找失败返回-1
    }
    

2、判定树及效率分析

  1. 判定树举例(向下取整)
    在这里插入图片描述
    分析:
    A S L 成 功 = ( 1 ∗ 1 + 2 ∗ 2 + 3 ∗ 4 + 4 ∗ 4 ) / 11 = 3 ASL_{成功}=(1*1+2*2+3*4+4*4)/11=3 ASL=(11+22+34+44)/11=3
    A S L 失 败 = ( 3 ∗ 4 + 4 ∗ 8 ) / 12 = 11 / 3 ASL_{失败}=(3*4+4*8)/12=11/3 ASL=(34+48)/12=11/3
  2. 判定树的构造(向下取整时)
    • 如果当前low和high之间有奇数个元素,则mid分割后,左右两部分元素相等。
    • 若偶数个元素,则左半部分比右半部分少一个元素。
  3. 折半查找的判定树中,向下取整时,对于任何一个结点,必有右子树结点数-左子树结点数=0或1。向上取整时,则是左-右=0/1。
    在这里插入图片描述
    在这里插入图片描述
  4. 折半查找的判定树一定是平衡二叉树,只有最下面一层是不满的,因此树高 h = [ l o g 2 ( n + 1 ) ] h=[log_2(n+1)] h=[log2(n+1)]。计算方法与完全二叉树相同。树高不包含失败节点。
  5. 判定树结点关键字:左<中<右,满足二叉排序树定义。
  6. 对于n个成功结点的判定树,失败结点n+1个(等于成功结点的空链域数量)。
  7. 查找成功/失败的 A S L ≤ h ASL≤h ASLh
  8. 时间复杂度 O ( l o g 2 n ) O(log_2n) O(log2n)

四、分块查找/索引查找

  1. 定义:又称索引顺序查找
  2. 分块查找示意图:
    在这里插入图片描述
  3. 特点:块内无序、块间有序
  4. 算法过程:
    ①在索引表中确定待查记录所属的分块(可顺序、可折半)
    ②在块内顺序查找
  5. 若索引表中不包含目标关键字,则折半查找索引表最终停在low>high,要在low所指分组。
    在这里插入图片描述
    原因:最终low左边一定小于目标关键字,high右边一定大于目标关键字。分块存储的索引表中保存的是各个分块的最大关键字。
  6. 查找效率分析ASL
    索引查找ASL为 L L L_L LL,块内查找ASL L S L_S LS长度为n的查找表分为b块,每块s个记录,等概率情况下:
    • 顺序查找: A S L = L L + L S = b + 1 2 + s + 1 2 = s 2 + 2 s + n 2 s ASL=L_L+L_S=\frac{b+1}{2}+\frac{s+1}{2}=\frac{s^2+2s+n}{2s} ASL=LL+LS=2b+1+2s+1=2ss2+2s+n,此时若 s = n s=\sqrt{n} s=n ,则ASL取最小值 n + 1 \sqrt{n}+1 n +1
    • 折半查找: A S L = L L + L S = [ l o g 2 ( b + 1 ) ] + s + 1 2 ASL=L_L+L_S=[log_2(b+1)]+\frac{s+1}{2} ASL=LL+LS=[log2(b+1)]+2s+1

五、哈希查找/散列查找

考的多,基本为选填

1、基本概念

  1. 散列表/哈希表:根据关键字而直接进行访问的数据结构。散列表建立了关键字和存储地址之间的一种直接映射关系。特点是数据元素的关键字与其存储地址直接相关。
  2. 散列函数:把查找表中的关键字映射成该关键字对应地址的函数。 H a s h ( k e y ) = A d d r Hash(key)=Addr Hash(key)=Addr
  3. 同义词:不同的关键字通过散列函数映射到同一个值。
  4. 冲突:通过散列函数确定的位置已经存放了其他元素。
  5. 装填因子a/负载因子:一个表的装满程度。装填因子a=表中记录数/散列表长度。装填因子会直接影响散列表的查找长度
  6. 散列函数的设计目标:让不同关键字的冲突尽可能少。要结合实际的关键字分布特点来考虑。
  7. 理想情况下,时间复杂度 O ( 1 ) O(1) O(1)
  8. 典型的“用空间换时间”的算法。只要散列函数设计的合理,则散列表越长,冲突的概率越低。
  9. 查找效率取决于:散列函数、处理冲突的方法、装填因子a。

2、常见的散列函数计算

  1. 除留余数法—— H ( k e y ) = k e y % p H(key)=key \% p H(key)=key%p
    • 散列表表长为m,取一个不大于m但最接近或等于m的质数p。
    • 用质数取模,分布更均匀,冲突更少。
  2. 直接定址法—— H ( k e y ) = k e y H(key)=key H(key)=key H ( k e y ) = a ∗ k e y + b H(key)=a*key+b H(key)=akey+b
    • 不会产生冲突,适合关键字的分布基本连续的情况。
  3. 数字分析法——选取数码分布较为均匀的若干位作为散列地址
    • 设关键字为r进制,r个数码在各位上出现的频率不一定相同,选取在某些位上较为均匀的若干位作为散列地址。
    • 适合于已知的关键字集合。更换关键字后需要重新构造哈希。
  4. 平方取中法——取关键字的平方值的中间几位作为散列地址
    • 得到的散列地址与关键字的每位都有关系。使分布较为均匀。
    • 适合关键字的每位取值都不够均匀或均小于散列地址所需的位数。

3、处理冲突的方法

(1)拉链法(含查找分析)

  • 拉链法/链接法/链地址法:为了避免非同义词发生冲突,把所有同义词存储在一个链表中。适用于经常进行插入和删除的情况。
    在这里插入图片描述
  • 上例中,查找目标若为21时,21的查找长度=0。(因为查找长度=对比关键字的次数,而21%13=8中为空指针,无比较)
    • A S L 成 功 = 1 ∗ 6 + 2 ∗ 4 + 3 + 4 12 = 1.75 ASL_{成功}=\frac{1*6+2*4+3+4}{12}=1.75 ASL=1216+24+3+4=1.75
    • A S L 失 败 = 0 + 4 + 0 + 2 + 0 + 0 + 2 + 1 + 0 + 0 + 2 + 1 + 0 13 = 0.92 ASL_{失败}=\frac{0+4+0+2+0+0+2+1+0+0+2+1+0}{13}=0.92 ASL=130+4+0+2+0+0+2+1+0+0+2+1+0=0.92,即装填因子。
  • 在插入新元素时,保持关键字有序,可略微提高查找效率。

(2)开放定址法

定义+计算方法

  • 开放定址法:可存放新表项的空闲地址既向它的同义词表项开放,又向它的非同义词表项开放。
  • 递推公式: H I = ( H ( k e y ) + d i ) % m , i = 0 , 1 , 2 , ⋅ ⋅ ⋅ , k ( k ≤ m − 1 ) H_I=(H(key)+d_i)\%m,i=0,1,2,···,k(k≤m-1) HI=(H(key)+di)%mi=0,1,2,,k(km1)
    m m m:散列表表长; d i d_i di:增量序列; i i i:第i次发生冲突
  • 采用“开放定址法”时,删除结点不能简单地将被删结点的空间置为空,否则将截断在它之前的散列表的同义词结点的查找路径,可以做一个“删除标记”,进行逻辑删除。
  • 确定增量序列的方法如下:
①线性探测法(含查找分析)
  • 线性探测法—— d i = 0 , 1 , 2 , 3 , ⋅ ⋅ ⋅ , m − 1 d_i=0,1,2,3,···,m-1 di=0,1,2,3,,m1。发生冲突时,每次往后探测相邻的下一个单元是否为空。
    在这里插入图片描述
    在这里插入图片描述
    注:上例中冲突处理函数可能落于[0,15],但哈希函数取值落在[0, 12]
  • 查找操作
    • 空位置的判断也要算作一次比较
    • 越早遇到空位置,就可以越早确定查找失败
  • 查找效率分析ASL
    在这里插入图片描述
    A S L 成 功 = 1 + 1 + 1 + 2 + 4 + 1 + 1 + 3 + 3 + 1 + 3 + 9 12 = 2.5 ASL_{成功}=\frac{1+1+1+2+4+1+1+3+3+1+3+9}{12}=2.5 ASL=121+1+1+2+4+1+1+3+3+1+3+9=2.5
    A S L 失 败 = 1 + 13 + 12 + 11 + 10 + 9 + 8 + 7 + 6 + 4 + 3 + 2 13 = 7 ASL_{失败}=\frac{1+13+12+11+10+9+8+7+6+4+3+2}{13}=7 ASL=131+13+12+11+10+9+8+7+6+4+3+2=7
  • 线性探测法容易造成同义词、非同义词的“聚集(堆积)”现象,严重影响查找效率。因为冲突后再探测一定是放在某个连续的位置。
②平方探测法
  • 平方探测法,又称二次探测法。 d i = 0 2 , 1 2 , − 1 2 , ⋅ ⋅ ⋅ , k 2 , − k 2 , 其 中 k ≤ m / 2 d_i=0^2,1^2,-1^2,···,k^2,-k^2,其中k≤m/2 di=02,12,12,,k2,k2km/2
  • 比起线性探测法更不易产生“聚集(堆积)”问题。
  • 散列表长度m必须是一个可以表示成 4 j + 3 4j+3 4j+3的素数,才能探测到所有位置。
    在这里插入图片描述
③伪随机序列法
  • 伪随机序列法: d i d_i di是一个伪随机序列,如 d i = 0 , 5 , 24 , 11 , ⋅ ⋅ ⋅ d_i=0,5,24,11,··· di=0,5,24,11,

(3)再散列法

  • 再散列法/再哈希法:除了原始的散列函数 H ( k e y ) H(key) H(key)之外,多准备几个散列函数,当散列函数冲突时,用下一个散列函数计算一个新地址,直到不冲突为止。 H i = R H i ( k e y ) , i = 1 , 2 , 3 , ⋅ ⋅ ⋅ , k H_i=RH_i(key),i=1,2,3,···,k Hi=RHi(key)i=1,2,3,,k
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值