数据结构-查找

目录

1.线性表的查找

1.1顺序查找

1.2折半查找

1.3分块查找

2.散列表

2.1散列表的基本概念

2.2散列表的构造方法

2.3处理冲突的方法

2.3.1开放地址法

2.3.2链地址法

2.4散列表的查找


1.线性表的查找

1.1顺序查找

        顺序查找的查找过程为:从表的一端开始,依次将记录的关键字和给定值进行比较,若某个记录的关键字和给定值相等,则查找成功;反之,若查找整个表后,仍然为找到关键字和给定值相等的记录,则查找失败。

顺序查找方法适用域线性表的顺序存储结构,又适用于线性表的链式存储结构。
下面给出顺序存储的数据元素类型定义和顺序表的定义

typedef struct{

        KeyType key;                //关键字域

        InfoType otherinfo;        //其它域

}ElemType;

typedef struct{

        ElemType *R;          //存储空间基地址

        int length;                      //长度

}SSTable;

顺序查找算法描述

int Search_Seq(SSTable ST,KeyType key)
{
    for(i=ST.length;i>=1;--i)
    {
        if(ST.R[i].key==key) 
            return i   
    }
    return 0
}

        查找过程中每步都要检测整个表是否查找完毕,即每步都要有循环变量是否满足条件i>=1的检测。改进这个程序,可以免去这个检测过程,改进方法是查找之前先对ST.R[0]的关键字赋值key,在此,ST.R[0]起到了监视哨的作用。

顺序查找算法改进

int Search_Seq(SSTable ST,KeyType key)
{
    ST.R[0].key=key;
    for(i=ST.length;ST.R[i].key!=key;--i)
    {
        return i;
    }
}

ST.R[0] 称为 “哨兵”。引入它的目的是免去查找过程中每一步都要检测整个表是否查找完毕,从而提高程序的效率,从实践证明,当length大于1000时进行一次查找所需的平均时间几乎减少了一半

时间复杂度即:ASL= (n+1)/2 为O(n)。

优点:算法简单,对数据元素的存储没有要求,顺序存储或链式存储皆可,无论记录是否按关键字有序均可应用。
缺点:平均查找长度较大,查找效率较低,当n很大时,不宜用顺序查找。

1.2折半查找

        当静态查找表的关键字有序时,可以用折半查找来实现。

        折半查找也称二分查找,它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构, 而且表中元素按关键字有序排列。

基本思想:
key 值与中间元素比较,相等则成功;key大则比较右半边;key小则比较左半边
分别用low和high来表示当前查找区间的下界和 上界,mid为区间的中间位置
数据结构-查找_第1张图片
数据结构-查找_第2张图片

折半查找算法详细

int Search_Bin(SSTable ST, KeyType key) {
    low = 1;
    high = ST.length;           //置查找区间初值
    while (low <= high) 
    {
        mid = (low + high) / 2;
        if 
        {
            (key == ST.R[mid].key)
            return mid;
        }
        else if (key < ST.R[mid].key) 
        {
            high = mid - 1;
        } 
        else
            low = mid + 1;
        return 0;
    }
}

算法很容易理解,循环条件是low<=high,而不是low

1.3分块查找

分块查找又称为索引顺序查找,这是一种性能介于顺序查找和折半查找之间的一种查找方法

分块查找,又称为索引顺序查找,吸取了顺序查找和折半查找各自的优点,既有动态结构,又适于快速查找

基本思想:将查找表分为若干个子块,块内元素可以无序,块间元素有序

​ 块间有序含义: 若a

建立“索引表”,每个结点含有最大关键字域和指向本块第一个结点的指针,且按关键字有序
数据结构-查找_第3张图片

分块查找的过程分为两步:
(1)索引查找:在索引表中确定待查记录所在的块;(可顺序、可折半)
(2)块内查找:在块内顺序查找

分块查找的平均查找长度为索引查找(Lb)和块内查找(Lw)的平均长度之和
ASL(bs)=Lb+Lw

数据结构-查找_第4张图片

优缺点:

  • 优点:插入和删除比较容易,无需进行大量的移动。
  • 缺点:要增加一个索引表的存储空间并对初始索引表进行排序运算。
  • 适用情况:如果线性表既要快速查找又经常动态变化,则可采用分块查找

 三种查找方法的比较

顺序查找 折半查找 分块查找 分块查找
ASL(查找长度) 最大 最小 中间
表结构 有序表、无序表 有序表 分块有序
存储结构 顺序表、线性链表 顺序表 顺序表、线性链表

2.散列表

2.1散列表的基本概念

散列表也叫哈希表

在元素的存储位置和其关键字之间建立某种直接关系,那么在进行查找时,就无需做比较或做很少次的比较,按照这种关系直接由关键字找到相应的记录。这就是散列查找法的思想,它通过对元素的关键字值进行某种运算,直接求出元素的地址,即使用关键字到地址的直接转换方法,而不需要反复比较。因此,散列查找法又叫杂凑法或散列法

散列函数和散列地址:在记录的存储位置p和其关键字key之间建立一个确定的对应关系H,使 p= H(key) ,称这个对应关系H为散列函数,p为散列地址。
散列表:采用散列技术将记录存放在一块连续有限的存储空间中,这块连续存储空间称为散列表或哈希表。通常散列表的存储空间是一个一维数组,散列地址是数组的下标。
冲突和同义词:对不同的关键字可能得到同一散列地址,即 key1≠key2, 而H(key1) =H( key2),这种现象称为冲突。具有相同函数值的关键字对该散列函数来说称作同义词,key1 与 key2 互称为同义词。

2.2散列表的构造方法

构造散列函数的方法很多,一般来说,应根据具体问题选用不同的散列函数,通常要考虑以下因素:

1.散列表的长度
2.关键字的长度
3.关键字的分布情况
4.计算散列函数所需的时间
5.记录的查找频率

构造一个好的散列函数需要遵循以下两条原则:

1.函数计算要简单,每一关键字只能有一个散列地址与之对应。
2.函数的值域需在表长的范围内,计算出的散列地址的分布应均匀,尽可能减少冲突。

下面介绍构造散列函数的几种常用方法

1.数字分析法
如果事先知道关键字集合,且每个关键字的位数比散列表的地址码位数多,每个关键字由n位数组成,则可以从关键字中提取数字分布比较均匀的若干位作散列地址。

2、平方取中法
这个方法计算很简单,假设关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227,用做散列地址。
平方取中法比较适合不知道关键字的分布,而位数又不是很大的情况。

3、折叠法
折叠法是将关键字从左到右分割成位数相等的几部分(注意最后一部分位数不够时可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位(舍去进位)作为散列地址。
比如关键字是9876543210,散列表表长为三位,将它分为四组,987|654|321|0,然后将它们叠加求和987+654+321+0=1962,去掉进位得到散列地址962。
折叠法事先不需要知道关键字的分布,适合关键字位数较多的情况。

4、除留余数法
此方法为最常用的构造散列函数方法。对于散列表长为m的散列函数公式为: H(key) = key%p

%是取模(求余数)的意思。事实上,这方法不仅可以对关键字直接取模 ,也可以再折叠、平方取中后再取模。
很显然,本方法的关键在于选择合适的p,p如果选不好,就可能会容易产生冲突。
根据前辈们的经验,若散列表的表长为m,通常p为小于表长的最大质数或不包含小于20质因子的合数。

2.3处理冲突的方法

2.3.1开放地址法

所谓的开放地址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。

通常把寻找下一个空位的过程称为探测

它的公示为:Hi = (H(key)+di)%m                i =1,2,3,…,k(k≤m-1)

其中di 为增量序列,根据 di 取值的不同,可以分为以下3种探测方法
(1)线性探测法

di=1,2,3,…,m-1
这种探测方法可以将散列表假想成一个循环表,发生冲突时,从冲突地址的下一单元顺序寻找空单元,如果到最后一个位置也没找到空单元,则回到表头开始继续查找,一旦找到一个空位,就把此元素放入此空位中,如果找不到空位,则说明散列表已满,需要进行溢出处理。

(2)二次探测法

(3)伪随机探测法

既然是随机,那么查找的时候不也随机生成id吗?如何取得相同的地址呢?这里的随机其实是伪随机数。伪随机数就是说,如果设置随机种子相同,则不断调用随机函数可以生成不会重复的数列,在查找时,用同样的随机种子,它每次得到的数列是想通的,相同的id 当然可以得到相同的散列地址。

2.3.2链地址法

链地址法的基本思想是:把具有相同散列地址的记录放在同一个单链表中,称之为同义词链表。有m个散列地址就有m个单链表,同时用数组HT[0...m-1]存放各个链表的头指针,凡是散列地址为i的记录都以节点方式插入以HT[i]为头节点的单链表。

已知一组关键字为(19, 14, 23, 1, 68,20, 84, 27, 55, 11, 10, 79),设散列函数H(key) = key %13,用链地址法处理冲突,试构造这组关键字的散列表。
        由散列函数H(key) = key %13得知散列地址的值域为0~12,故整个散列表由13个单链表组成,用数组HT[0..12]存放各个链表的头指针。如散列地址均为1的同义词14、1、27、79构成一个单链表,链表的头指针保存在HT[1]中,同理,可以构造其他几个单链表,整个散列表的结构如下图所示。

2.4散列表的查找

算法步骤:

1.给定待查找的关键字key,根据创建表时设定的散列函数计算H0=H(key)。
2.若单元H0为空,则所查元素不存在。
3.若单元H0中元素的关键字为key,则查找成功。
4.否则重复下述解决冲突的过程:
        按处理冲突的方法,计算下一个散列地址Hi;
        若单元Hi为空,则所查元素不存在;
        若单元Hi中元素的关键字为key,则查找成功。

int SearchHash(HashTable HT,KeyType key)
{
    H0=H(key);
    if(HT[H0].key==0) return -1;
    else if(HT[H0].key==key) return H0;
    else
    {
        for(i=1;i

你可能感兴趣的:(数据结构)