数据结构学习笔记(7)——查找

查找的基本概念

  • 平均查找长度
    A S L = ∑ i = 1 n P i C i ASL = \sum_{i=1}^nP_iC_i ASL=i=1nPiCi​​

顺序查找和折半查找

一般线性表的顺序查找

基本思想:从线性表的一端开始,逐个检查关键字是否满足给定的条件。

  • 静态查找表的顺序结构

    typedef struct {
    	ElemType *elem;		//元素存储空间基址,建表时按照实际长度分配,0号单元留空
    	int tableLen;		//表长
    }SSTable;
    
  • 顺序查找算法(普通版)

    int search_Seq(SSTable ST, ElemType key) {
    	int i;
    	for (i = 0; i < ST.tableLen && ST.elem[i] != key; i++);	//从前往后查找
    	return i == ST.tableLen ? -1 : i;						//查找成功,返回i,查找失败,返回-1
    }
    
  • 顺序查找算法(哨兵版)

    int search_Seq(SSTable ST, ElemType key) {
    	ST.elem[0] = key;										//哨兵,将关键字赋值给ST.elem[0]
        int i;
    	for ( i = ST.tableLen; ST.elem[i] != key; --i);			//从后往前查找,找到关键字后退出,返回i
    	return i;
    }
    

    建表时将ST.elem[0]留空,在查找时将关键字赋值给ST.elem[0],称为哨兵

  • 引入哨兵的目的:不必判断数组是否会越界,减少了很多不必要的判断语句。即使没有查找成功,当i=0时,循环也一定会退出。

  • 平均查找长度:

    A S L 成 功 = ∑ i = 1 n P i C i = ∑ i = 1 n 1 n ( n − i + 1 ) = n + 1 2 ASL_{成功} = \sum_{i=1}^nP_iC_i=\sum_{i=1}^n\frac{1}{n}(n-i+1)=\frac{n+1}{2} ASL=i=1nPiCi=i=1nn1(ni+1)=2n+1

    A S L 失 败 = ∑ i = 1 n P i C i = ∑ i = 1 n 1 n ( n + 1 ) = n + 1 ASL_{失败} = \sum_{i=1}^nP_iC_i=\sum_{i=1}^n\frac{1}{n}(n+1)=n+1 ASL=i=1nPiCi=i=1nn1(n+1)=n+1​​

  • 优点:对数据元素存储没有要求,顺序存储或者链式存储都可以进行查找;对表中记录是否有序也没有要求;

  • 缺点:当n比较大时,平均查找长度大,效率较低。

  • 线性链表只能用顺序查找。

有序表的顺序查找

有序表的顺序查找更方便一些,因为提前知道了表是有序的,查找失败时就不需要遍历整个表再返回查找失败的信息。

假如表是按照从小到大的顺序排列的,从前往后查找,则查找失败退出循环需要满足的条件:elem[i] < key && elem[i+1] > key

  • 有序表的顺序查找判定树

在这里插入图片描述

引入失败结点,所谓失败结点,是指那些不在表中的数据值的集合。如果key落在了失败结点代表的区间里,即可返回查找失败。

如果表中有n个结点,那么自然地会有n+1个失败结点。

  • 查找成功时,查找长度为成功结点所在的层数

  • 查找失败时,查找长度为失败结点的父结点所在的层数

  • 平均查找长度:

  1. 查找成功时的平均查找长度和一般线性表的顺序查找一样:

    A S L 成 功 = ∑ i = 1 n P i C i = ∑ i = 1 n 1 n ( n − i + 1 ) = n + 1 2 ASL_{成功} = \sum_{i=1}^nP_iC_i=\sum_{i=1}^n\frac{1}{n}(n-i+1)=\frac{n+1}{2} ASL=i=1nPiCi=i=1nn1(ni+1)=2n+1​​

  2. 查找失败:

    qi表示key落到第i个失败结点的概率:1/(n+1)

    li是第j个失败结点所在的层数,从2开始,一直到n+1。那么li-1就是该失败结点的查找长度。

    A S L 失 败 = ∑ i = 1 n q i l i = 1 n + 1 ( 1 + 2 + . . . + n + n ) = n 2 + n n + 1 ASL_{失败} = \sum_{i=1}^nq_il_i=\frac{1}{n+1}(1+2+...+n+n)=\frac{n}{2}+\frac{n}{n+1} ASL=i=1nqili=n+11(1+2+...+n+n)=2n+n+1n

    注意,最后两个失败结点的查找长度都是n。

被查找概率不相同

当线性表中每个元素被查找的概率不相等时,应该按照被查找概率从大到小排序,把被查找概率大元素放在前面,那么可以有效减小平均查找长度。

折半查找(二分查找)

首先,折半查找是由限制的,即**折半查找只适用于有序的顺序表。**因为链表没有随机访问的特性。(二分查找不能用来查找链表

  • 折半查找的算法描述:

    用给定值key和表中间位置的元素比较:

  1. 若相等,则查找成功,返回下标;

  2. 若不相等,且中间位置的值大于key,在表的前半部分继续查找;

  3. 若不相等,且中间位置的值小于key,在表的后半部分继续查找。

    重复上述过程,直到查找成功或者表中没有所需要查找的元素为止。

  • 折半查找算法

    //折半查找
    int Binary_Search(SeqList L, ElemType key) {
        //需要用到3个辅助指针,分别表示待查找表的开始位置,中间位置和末端位置
    	int low = 0;					
        int mid;
    	int high = L.TableLen - 1;
        //通过不断的比较mid位置上的值和key的大小关系来缩小查找范围
    	while (low <= high) {		
    		mid = (low + high) / 2;		//中间位置。不能平分就向下取整,int自带的功能
    		if (L.elem[mid] == key)		//等于key,查找成功,返回mid
    			return mid;
    		if (L.elem[mid] > key)		//大于key,则在左半部分继续查找,对应的操作是修改high指针的位置
    			high = mid - 1;
    		else						//小于key,则在右半部分继续查找,对应的操作是修改low指针的位置
    			low = mid + 1;
    	}
    	return -1;
    }
    

    算法中默认 m i d = ⌊ ( l o w + h i g h ) / 2 ⌋ mid=⌊(low+high)/2⌋ mid=(low+high)/2​​(向下取整),其实向上取整也不影响结果,只对下面的判定树有所影响。

折半查找的判定树

  • 判定树的构造

假设待查找的有序顺序表是:

在这里插入图片描述

将对应的折半查找的过程用二叉树描述,即可得到判定树

在这里插入图片描述

由mid所指的元素将表中的元素分开放在左右子树中,如果 m i d = ⌊ ( l o w + h i g h ) / 2 ⌋ mid=⌊(low+high)/2⌋ mid=(low+high)/2,那么当:

  1. n是奇数时,左右子树元素个数相等
  2. n是偶数时,右子树比左子树的元素个数多1个

折半查找的判定树中,若 m i d = ⌊ ( l o w + h i g h ) / 2 ⌋ mid=⌊(low+high)/2⌋ mid=(low+high)/2​,则对于任何⼀个结点有:右子树结点数-左子树结点数=0或1 (判定树是平衡树)

  1. 结点值:关键字值

  2. 叶结点:n+1个, 表示查找失败的情况

  3. 非叶子结点:n个,表示查找成功的情况

  • 判定树的性质
  1. 折半查找的判定树一定是一棵平衡树

  2. 折半查找的判定树一定是一棵二叉排序树。

  3. 平均查找长度:

    查找成功:查找长度为结点所在的层数;

    查找失败:查找长度为失败结点的父结点所在的层数。

    所以,用折半查找法找到给定值的比较次数最多不会超过树的高度 h = ⌈ l o g 2 ( n + 1 ) ⌉ h=⌈log_2^{(n + 1)}⌉ h=log2(n+1)(该树高不包含失败结点)​。

  4. 折半查找的时间复杂度: O ( l o g 2 n ) O(log_2^n) O(log2n),相比于顺序查找的 O ( n ) O(n) O(n),平均查找速度更快。​

分块查找

基本思想:将查找表分为若干块,块内元素无序,块间元素有序。再建立一个索引表,索引表中的每个元素含有各块的最大关键字和块的首尾地址,索引表按照关键字有序排列。所以,分块查找也叫作“索引顺序查找”。
在这里插入图片描述

  • 分块查找的过程:
  1. 在索引表中确定待查找记录所在的块(顺序查找或者折半查找都可以)
  2. 在块内按照顺序查找
  • 注意

    如果对索引表按照折半查找,若索引表中不包含⽬标关键字,则折半查找索引表最终停在 low>high,最终low左边(包括high)⼀定⼩于⽬标关键字,high右边(包括low)⼀定⼤于⽬标关键字。⽽分块存储的索引表中保存的是各个分块的最⼤关键字,low恰好是大于关键字的第一个分块,要在low所指分块中查找

  • 平均查找长度

    按照分块查找的特性,分块查找的平均查找长度为索引查找和块内查找的查找长度之和。

    设索引内查找长度 L i L_i Li,块内查找长度 L s L_s Ls​,如果将长度为n的查找表平均分为b块,每块有s个记录,即n=sb。若:

    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=\frac{b+1}{2}+\frac{s+1}{2}=\frac{s^2+2s+n}{2s} ASL=Li+Ls=2b+1+2s+1=2ss2+2s+n

      此时,当 s = n s=\sqrt{n} s=n 时,ASL有最小值 n + 1 \sqrt{n}+1 n +1​。

      比如,对于含有10000个元素的表,想要对其使用分块查找,将其分为100块,每块100个元素时,ASL最小。

则平均查找长度为: 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=\frac{b+1}{2}+\frac{s+1}{2}=\frac{s^2+2s+n}{2s} ASL=Li+Ls=2b+1+2s+1=2ss2+2s+n

此时,当 s = n s=\sqrt{n} s=n 时,ASL有最小值 n + 1 \sqrt{n}+1 n +1​。

比如,对于含有10000个元素的表,想要对其使用分块查找,将其分为100块,每块100个元素时,ASL最小。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值