【数据结构】第八章 查找

1.顺序查找和折半查找

1.1 顺序查找

1.1.1 一般线性表的顺序查找

  1. 算法思想:
  1. 将查找值放入哨兵中 S T . e l e m [ 0 ] ST.elem[0] ST.elem[0]
  2. 从后向前判断是否为查找值,找到退出循环。
  3. 返回循环判断条件中的 i i i,即为查找值的位置,若返回的是0,这说明查找到哨兵退出循环,未查找到元素
  1. 实现:
typedef int ElemType;
typedef struct{
     
    ElemType *elem;
    int TableLen;
}SSTable;

int seq_search(SSTable ST, ElemType key){
     
    ST.elem[0] = key;
    int i ;
    for(i = ST.TableLen;ST.elem[i]!=key;i--);//查找元素为哨兵时 i = 0;
    return i;//i返回查找到的元素相应位置,未找到,则返回0;
}
  1. 平均查找长度:(每个元素查找概率相等)
  1. 查找成功: A S L ( s u c c ) = ∑ i = 1 n P i ( n − i + 1 ) = ( n + 1 ) / 2 ASL_(succ) = \sum\limits_{i=1}^{n}P_i(n-i+1) = (n+1)/2 ASL(succ)=i=1nPi(ni+1)=(n+1)/2 (从后向前定位第 i i i个元素,需进行 n − i + 1 n-i+1 ni+1次关键字比较)
  2. 查找不成功: A S L ( u n s u s s ) = n + 1 ASL_(unsuss) = n+1 ASL(unsuss)=n+1(表中关键字比较 n + 1 n+1 n+1次,与哨兵也比较一次)
  1. 注:
  1. 引入哨兵可以避免很多不必要的判断语句,提高程序效率。
  2. 存储结构通常为顺序结构,也可为链式结构。
  3. 缺点: n n n比较大时,平均查找长度大,效率低。优点:数据元素的存储没有要求(可顺序可链式)

1.1.2 有序表的顺序查找

  1. 查找判定树:
    【数据结构】第八章 查找_第1张图片
    失败结点是不存在的,所以到达失败结点时所查找的长度等于它上面的一个圆形结点的所在层数。
  2. 平均查找长度:

A S L ( u n s u s s ) = ∑ j = 1 n q j ( l j − 1 ) = ( 1 + 2 + . . . + n + n ) / ( n + 1 ) = n / 2 + n / ( n + 1 ) ASL_(unsuss) = \sum\limits_{j=1}^{n}q_j(l_j-1) = (1+2+...+n+n)/(n+1) = n/2+n/(n+1) ASL(unsuss)=j=1nqj(lj1)=(1+2+...+n+n)/(n+1)=n/2+n/(n+1)
q j q_j qj是到达第 j j j个失败结点的概率,为 1 / ( n + 1 ) 1/(n+1) 1/n+1) l j l_j lj是第 j j j个失败结点所在的层数, 式中 n + n n+n n+n为最后一层上有两个失败结点,层数为 n n n

  1. 注:

不用从后向前遍历全表才能返回失败信息,降低顺序查找失败的平均查找长度

1.2 折半查找(二分查找)

仅适用于有序的顺序表

  1. 算法思想:
  1. 每次比较中间位置元素,可将表分为两部分,缩小查找范围
  2. 循环判断条件为 l o w < = h i g h low<=high low<=high,当 l o w = h i g h low = high low=high m i d mid mid才能指向此时的元素,否则会出现没有比较过的数。
  1. 实现:
typedef int ElemType;
typedef struct{
     
    ElemType data[MaxSize];
    int length;
}sqlist;

int binary_search(sqlist L,int key){
     
    int low = 0,high = L.length-1;
    while(low<=high){
     //此处为 <= 当low = high 时 mid = low=high mid才能取到此值
        int mid = (low+high)/2;
        if(L.data[mid]==key) return mid;
        else if(L.data[mid]>key) {
     high = mid-1;}
        else {
     low = mid+1;}
    }
    return 0;
}
  1. 平均查找长度:

A S L ( s u c c ) = 1 / n ∑ i = 1 n l i = l o g 2 ( n + 1 ) − 1 ASL_(succ) = 1/n\sum\limits_{i=1}^{n}l_i = log_2(n+1)-1 ASL(succ)=1/ni=1nli=log2(n+1)1
折半查找法查找到给定值的比较次数最多不会超过树的高度(元素个数为 n n n,树高 h = ⌈ l o g 2 ( n + 1 ) ⌉ h = \lceil log_2(n+1)\rceil h=log2(n+1)
时间复杂度: O ( l o g 2 n ) O(log_2n) O(log2n)

  1. 注:
  1. 折半查找平均情况下比顺序查找效率要高
  2. 折半查找仅适合顺序存储(具有随机存取的特性)

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

将查找表分为若干子块,块内无序,块间有序。查找过程分两步1,确定待查记录所在块(顺序查找或折半查找),2.块内查找(顺序查找)

平均查找长度:

  1. 均采用顺序查找: A S L = L i + L s = ( b + 1 ) / 2 + ( s + 1 ) / 2 = ( s 2 + 2 ∗ s + n ) / 2 ∗ s ASL = L_i+L_s = (b+1)/2 + (s+1)/2 = (s^2+2*s+n)/2*s ASL=Li+Ls=(b+1)/2+(s+1)/2=(s2+2s+n)/2s
  2. 先采用折半查找则: A S L = L i + L s = ⌈ l o g 2 ( b + 1 ) ⌉ + ( s + 1 ) / 2 ASL = L_i+L_s = \lceil log_2(b+1)\rceil + (s+1)/2 ASL=Li+Ls=log2(b+1)+(s+1)/2
  1. 由n个数据元素组成的两个表:一个递增有序,一个无序。采用顺序查找算法,对有序表从头开始查找,发现当前元素已不小于待查元素时,停止查找,确定查找不成功,已知查找任一元素的概率是相同的,则在两种表中成功查找().
    A.平均时间后者小B.平均时间两者相同
    C.平均时间前者小D.无法确定
    B
    对于顺序查找,不管线性表是有序的还是无序的,成功查找第一个元素的比较次数为1,成功查找第二个元素的比较次数为2,以此类推,即每个元素查找成功的比较次数只与其位置有关(与是否有序无关),因此查找成功的平均时间两者相同。
  2. 在一个顺序存储的有序线性表上查找一个数据时,既可以采用折半查找,也可以采用顺序查找,但前者比后者的查找速度()。
    A.必然快B.取决于表是递增还是递减
    C.在大部分情况下要快D.不能确定
    D
    折半查找的快体现在一般情况下,对于某些特殊情况,顺序查找可能会快于折半查找,并不能绝对地说哪种查找方法快。(查找一个含1000个元素的有序表中的第一个元素时,顺序查找的比较次数为1次,而折半查找的比较次数却将近10次)
  3. 折半查找过程所对应的判定树是一棵()。
    A.最小生成树B.平衡二又树
    C.完全二又树D.满二又树
    B
    折半查找每次把一个数组从中间结点分割时,总是把数组分为结点数相差最多不超过1的两个子数组,从而使得对应的判定树的两棵子树高度 差的绝对值不超过1,所以应是平衡二又树。
  4. 已知一个长度为16的顺序表 L L L,其元素按关键字有序排列,若采用折半查找法查找一个 L L L中不存在的元素,则比较的次数至少是(),至多是()。
    A.4B.5
    C.6D.7
    B
    折半查找法在查找不成功时和给定值进行关键字的比较次数最多为树的高度,即 ⌈ l o g 2 ( n + 1 ) ⌉ \lceil log_2(n+1)\rceil log2(n+1) ⌊ l o g 2 n ⌋ + 1 \lfloor log_2n\rfloor+1 log2n+1在折半查找判定树中的方形结点是虚构的,它并不计入比较的次数中。
  5. 折半查找和二又排序树的时间性能()。
    A.相同B.有时不相同
    C.完全不同D.无法比较
    B
    折半查找的性能分析可以用二又判定树来衡量,平均查找长度和最大查找长度都是 O ( l o g 2 n ) O(log_2n) O(log2n);二叉排序树的查找性能与数据的输入顺序有关,最好情况下的平均查找长度与折半查找相同,但最坏情况即形成单支树时,其查找长度为 O ( n ) O(n) O(n)
  6. 已知一个长度为16的顺序表,其元素按关键字有序排列,若采用折半查找算法查找一个不存在的元素,则比较的次数至少是(),至多是()。
    A.4B.5
    C.6D.7
    A, B
    直接用公式求出最小的分支高度和最大分支高度。最大分支高度为 H = ⌈ l o g 2 ( n + 1 ) ⌉ = 5 H=\lceil log_2(n+1)\rceil=5 H=log2(n+1)=5这对应的就是最多比较次数,然后由于判定树不是一棵满树,所以至少应该是4(由判定树的各分支高度最多相差1得出)。注意,若是求查找成功或查找失败的平均查找长度,则需要画出判定树进行求解。此外,对长度为 n n n的有序表,采用折半查找时,查找成功和查找失败的最多比较次数相同,均为 ⌈ l o g 2 ( n + 1 ) ⌉ \lceil log_2(n+1)\rceil log2(n+1)最少比较次数也相同。
  7. 对有2500个记录的索引顺序表(分块表)进行查找,最理想的块长为()。
    A.50B.125
    C.500D. ⌈ l o g 2 2500 ⌉ \lceil log_22500 \rceil log22500
    A
    设块长为b,索引表包含 n / b n/b n/b项,索引表的 A S L = ( n / b + 1 ) / 2 ASL=(n/b+1)/2 ASL=(n/b+1)/2,块内的 A S L = ( b + 1 ) / 2 ASL=(b+1)/2 ASL=(b+1)/2,总 A S L = ASL= ASL=索引表的 A S L + ASL+ ASL+块内的 A S L = ( b + n / b + 2 ) / 2 ASL=(b+n/b+2)/2 ASL=(b+n/b+2)/2,其中对于 b + n / b b+n/b b+n/b,由均值不等式知 b = n / b b=n/b b=n/b时有最小值,此时 b = n b=\sqrt n b=n 。则最理想块长为 2500 = 50 \sqrt{2500}=50 2500 =50
  8. 为提高查找效率,对有65025个元素的有序顺序表建立索引顺序结构,在最好情况下查找到表中已有元素最多需要执行()次关键字比较。
    A.10B.14
    C.16D.21
    C
    为使查找效率最高,每个索引块的大小应是 65025 = 255 \sqrt {65025}=255 65025 =255. 对索引项和索引块内部都采用折半 l o g 2 ( 255 + 1 ) + l o g 2 ( 255 + 1 ) = 16 log_2(255+1) +log_2(255+1) = 16 log2(255+1)+log2(255+1)=16

2.B树和B+树

2.1 B树(多路平衡查找树)

一棵 m m m B B B树或为空树,或为满足如下特性的 m m m叉树:

  1. 树中每个结点至多有 m m m棵子树(即至多含有 m − 1 m-1 m1个关键字)。
  2. 若根结点不是终端结点,则至少有两棵子树。
  3. 除根结点外的所有非叶结点至少有 ⌈ m / 2 ⌉ \lceil m/2\rceil m/2棵子树(即至少含有 ⌈ m / 2 ⌉ − 1 \lceil m/2\rceil-1 m/21个关键字)。
  4. 所有的叶结点都出现在同一层次上,并且不带信息
  5. B树是所有结点的平衡因子均等于0的多路查找树
  1. B树的高度:

B树中的大部分操作所需的磁盘存取次数与B树的高度成正比。
l o g ⌈ m / 2 ⌉ ( ( n + 1 ) / 2 ) + 1 < = h < = l o g m ( n + 1 ) log_{\lceil m/2 \rceil}((n+1)/2)+1 <= h <= log_m(n+1) logm/2((n+1)/2)+1<=h<=logm(n+1)

  1. B树的查找:
  1. 在B树中找结点(在磁盘上进行)
  2. 在结点内找关键字(在内存中进行)
  1. B树的插入:
  1. 定位
  2. 插入
  1. B树的删除:
    1)所删除的关键字k不在终端结点
  1. 若小于 k k k的子树中关键字个数 > ⌈ m / 2 ⌉ − 1 >\lceil m/2\rceil-1 >m/21,则找 k k k的前驱值 k " k^" k",并用 k " k^" k"来取代 k k k,再递归地删除 k " k" k"即可。
  2. 若大于 k k k的子树中关键字个数 > ⌈ m / 2 ⌉ − 1 >\lceil m/2\rceil-1 >m/21,则找出 k k k的后继值 k " k^" k",并用 k " k^" k"来取代 k k k,再递归地删除 k k k即可。
    3.若前后两个子树中的关键字个数均为 ⌈ m / 2 ⌉ − 1 \lceil m/2\rceil-1 m/21,则直接将两个子结点合并,直接删除 k k k即可
    2)被删除的关键字在终端结点(最低层非叶结点)中
  3. 直接删除关键字。
  4. 兄弟够借。
  5. 兄弟不够借。

2.2 B+树

一棵m阶的B+树需满足下列条件

  1. 每个分支结点最多有 m m m棵子树(子结点)
  2. 非叶根结点至少有两棵子树,其他每个分支结点至少有 ⌈ m / 2 ⌉ \lceil m/2\rceil m/2棵子树。
  3. 结点的子树个数与关键字个数相等。
  4. 所有叶结点包含全部关键字及指向相应记录的指针,叶结点中将关键字按大小顺序排列,并且相邻叶结点按大小顺序相互链接起来。
  5. 所有分支结点中仅包含它的各个子结点中关键字的最大值及指向其子结点的指针。

m阶的B+树与m阶的B树的主要差异

  1. 在B+树中,叶结点包含信息,所有非叶结点仅起索引作用,
  2. 在B+树中,叶结点包含了全部关键字,即在非叶结点中出现的关键字也会出现在叶结点中;
  3. B+树进行两种查找运算:一种是从最小关键字开始的顺序查找,另一种是从根结点开始的多路查找。
  4. 在B+树中查找时,无论查找成功与否,每次查找都是一条从根结点到叶结点的路径。

3.散列表

3.1 散列函数的构造方法

  1. 直接定址法:
    H ( k e y ) = a ∗ k e y + b H(key)=a*key+b H(key)=akey+b
    a和b是常数。适合关键字的分布基本连续的情况,若关键字分布不连续,空位较多,则会造成存储空间的浪费。
  2. 留有余数法:
    H ( k e y ) = k e y % p H(key)=key\%p H(key)=key%p
    假定散列表表长为 m m m,取一个不大于 m m m但最接近或等于 m m m的质数 p p p。除留余数法的关键是选好 p p p
  3. 数字分析法:
    设关键字是 r r r进制数(如十进制数),选取数码分布较为均匀的若干位作为散列地址。这种方法适合于已知的关键字集合,若更换了关键字,则需要重新构造新的散列函数。
  4. 平方取中法:
    这种方法取关键字的平方值的中间几位作为散列地址。适用于关键字的每位取值都不够均匀或均小于散列地址所需的位数。
  5. 折叠法:
    将关键字分割成位数相同的几部分(最后一部分的位数可以短一些),然后取这几部分的叠加和作为散列地址。适用于i关键字位数很多,而且关键字中的每位上数字分布大致均匀。

注:

设计好的散列函数应尽量减少冲突,然而冲突总是不可避免的。

3.2 处理冲突的方法

3.2.1 开放定址法

H i = ( H ( k e y ) + d i ) % m H_i=(H(key)+d_i)\%m Hi=(H(key)+di)%m
式中, i = 0 , 1 , 2 … , k ( k ≤ m − 1 ) i=0,1,2…,k(k≤m-1) i=012k(km1) m m m表示散列表表长; d i d_i di为增量序列。

  1. 线性探测法:
    d i = 0 , 1 , 2 … , m − 1 d_i=0,1,2…,m-1 di=012m1。特点是:冲突发生时,顺序查看表中下一个单元,直到找出一个空闲单元。易造成大量元素在相邻的散列地址上“聚集”(或堆积)起来,大大降低了查找效率。

  2. 平方探测法:
    d i = 0 2 , 1 2 , − 1 2 , 2 2 , − 2 2 … , k 2 , − k 2 , k < = m / 2 d_i=0^2,1^2,-1^2,2^2,-2^2…,k^2,-k^2 , k<=m/2 di=02,12,12,22,22,k2,k2,k<=m/2
    列表长度 m m m必须是一个可以表示成 4 k + 3 4k+3 4k+3的素数,以避免出现“堆积”问题,缺点:不能探测到散列表上的所有单元,但至少能探测到一半单元。

  3. 再散列法:
    d i = H a s h 2 ( k e y ) d_i=Hash_2(key) di=Hash2(key)具体散列函数形式如下:
    H i = ( H ( k e y ) + i ∗ H a s h 2 ( k e y ) ) % m H_i=(H(key)+i*Hash_2(key))\%m Hi=(H(key)+iHash2(key))%m
    初始探测位置 H 0 = H ( k e y ) % m H_0=H(key)\%m H0=H(key)%m。在散列法中,最多经过 m − 1 m-1 m1次探测就会遍历表中所有位置,回到 H 0 H_0 H0位置。

  4. 伪随机数法:
    d i = d_i= di=伪随机数序列.

注:

  1. 不能随便物理删除表中的已有元素,会截断其他具有相同散列地址的元素的查找地址。因此,要删除一个元素时,可给它做一个删除标记,进行逻辑删除。
  2. 副作用:执行多次删除后,表面上看起来散列表很满,实际上有许多位置未利用,因此需要定期维护散列表,要把删除标记的元素物理删除。
  3. 聚集是因选取不当的处理冲突的方法,而导致不同关键字的元素对同一散列地址进行争夺的现象。用线性再探测法时,容易引发聚集现象。
  4. 产生堆积现象,即产生了冲突,它对存储效率、散列函数和装填因子均不会有影响,而平均查找长度会因为堆积现象而增大

3.2.2 拉链法

避免非同义词发生冲突,可以把所有的同义词存储在一个线性链表中。拉链法适用于经常进行插入删除的情况。

3.3 性能分析

散列表的查找效率取决于三个因素:散列函数、处理冲突的方法和装填因子。
装填因子 α = n m \alpha = \frac nm α=mn
n n n:表中记录数, m m m: 散列表长度
注:

  1. 散列表的平均查找长度依赖于散列表的填装因子 α \alpha α,而不直接依赖于 n n n m m m
  2. α \alpha α越大,表示装填的记录越“满”,发生冲突的可能性越大

你可能感兴趣的:(笔记)