Frank的专栏

沉默才是我的本性。

检索

  • 检索(Search ) 在一组记录集合中找到关键码值等于给定值的某个记录,或者找到关键码符合某种条件的一些记录。
  • 检索效率很重要,需要对数据进行特殊的存储处理。
  • 特殊处理方法:预排序、建立索引、散列技术、B树方法
  • 检索核心操作:关键码的比较
  • 平均检索长度(Average Search Length ):检索过程中对关键码的平均比较次数ASL
    ASL=npiCi

    其中n是可能检索的所有关键码,Ci是检索第i个关键码的次数,pi是检索第i个的概率。
  • 评估检索的算法:ASL,算法所需存储量,算法的复杂性

基于线性表的检索

顺序检索

对线性表中的所有记录,逐个把它们的关键码和给定值进行比较。

//Item 类(非完全)
template<class Type>
Class Item{
private:
    Type key;
public:
    Item(Type value):key(value){}
    Type getKey() {return key;}
    void setKey(Type value){key = value};
};
vector<Item<Type>*> dataList;
//顺序检索算法
template<class T>
int SeqSearch(vector<Item<Type>*>& dataList,int length,Type k){
    int i = length;
    dataList[0] = k;  //将第一个元素设置为给定值作为监视哨
    while(dataList[i] != k)
        i --;
    return i;  //找到第一个关键码符合的记录,返回
}

算法分析:

  • 最好情况:1次
  • 最坏情况:n+1次
  • 检索成功时,假设检索每个关键码的值相等,平均次数为
    ASL=i=1n1ni=n+12
  • 设检索成功的概率为p
  • ASL=pn+12+(1p)(n+1)=(n+1)(1p2)
  • ASL在n+12n+1之间

二分检索

  • 要求线性表有序
  • 每一次比较缩小一半的范围
template<class Type>
int BinSearch(vector<Item<Type>*>& dataList,int length,Type k){
    int low = 1; high = length;mid;
    while(low < high) //循环
    {
        mid = (low + high)/2;  //二分
        if(dataList[mid] == k)
            return mid;
        else if(dataList[mid] < k)
                low = mid;
        else if(dataList[mid] > k)
                high = mid;
    }
    return 0;  //检索失败返回0
}

算法分析

  • 最大检索长度:[log2[n+1]]

    ASL=i=1[log2(n+1)]1ni2i1=1n[(log2(n+1)1)(n+1)+1]log2(n+1)1

  • 优点:检索速度快。

  • 缺点:需要排序、不易增删

分块检索

  • 顺序与二分法的折衷:兼顾速度与灵活性
  • 分块的条件:不需要均匀,块内不一定要有序,只要保证前一块中的最大关键码小于后一块中的最小关键码。
  • 建立索引表,每个块用一个结点,记录块的最大关键码、起始位置、块的长度(可能不满)。
  • 索引表是递增的有序表。

性能分析

  • n个元素分成b
  • 两级检索:检索块ASLb+块内ASLwASL=ASLb+ASLw
  • 如果两级都用顺序检索
    • ASLb=b+12,ASLw=s+12ASL=b+12+s+12=b+s2+1=n+s22s+1
    • s=n时,ASL取最小值,ASLn
    • 速度比顺序检索快,比二分检索慢。
    • 如果数据块放在外存,还会受到页块大小的制约。
  • 如果采用二分检索确定记录所在子表
    • ASL=log2(b+1)1+s+12log2(1+ns)+s2
  • 优点:插入删除容易、没有大量移动数据
  • 缺点:增加辅助存储空间、初始线性表分块排序、结点分布不均时速度下降

集合的检索

用位向量表示集合

  • 适用于密集型集合(数据范围小,集合中有效元素较多),比如查找“奇素数”

散列

检索是直接面向用户的操作。当问题规模很大时,前述基于关键码检索的时间效率可能使用户无法忍受。最理想的情况就是根据关键码直接找到记录的存储地址。

数组按下标读取就是O(1)的操作,和数组的规模无关。由此产生了散列方法。

基本思想

  • 一个确定的函数h
  • 以结点的关键码K为自变量
  • 函数值h(K)作为结点的存储地址
  • 检索的时候根据这个函数计算存储位置
  • 散列表的存储空间通常是一个一维数组
  • 散列地址是数组下标
  • 负载因子 α=nm=
  • 冲突 不相同的关键码得到相同的散列地址
  • 同义词 发生冲突的两个关键码

核心问题

  1. 如何构造散列函数?
    Address=Hash(Key)

    • 运算尽可能简单
    • 值域必须在表长范围内
    • 尽可能使避免冲突
    • 综合考虑:关键码长度、散列表大小、关键码分布情况、记录的检索频率
  2. 如何解决冲突?
    • 开散列方法(open hash ),也叫拉链法(separate chaining ),把发生冲突的关键码存在散列表主表之外。
    • 闭散列方法(closed hashing ),也叫开地址法(open addressing),把发生冲突的关键码存在表中另一个槽内。

常用散列函数

  1. 除余法
    • 用散列长度模关键码h(x)x(modM)
    • M一般取质数,增大分布均匀的可能性:函数值依赖于关键码x所有位
    • 不用偶数、幂
    • 缺点:连续的关键码映射成连续的函数值,可能导致散列性能降低。
  2. 乘余取整法
    • 先让关键码乘一个数A(0<A<1),取小数部分,乘以散列长度n,向下取整。h(x)=[(x×A[x×A])×n]
    • 优点:对n的选择无关紧要:地址空间有k位,就取n=2k
    • Knuth认为A可以取任何值,但是取黄金分割最理想
  3. 平方取中法
    通过平方扩大关键码之间的差别,再取其中几位或组合作为散列地址
  4. 数字分析法

    • nd位数为例
    • 每一位可能有r个可能的符号
    • r种不同的符号在各位上出现的频率不一定相同
    • 根据散列表的大小,选取各符号分布均匀的若干位作为散列地址
    • 计算各个位上符号分布的均匀度λk=ri=1(αkin/r)2
    • 其中αi表示第i个符号在第k位上出现的次数。
    • λ越小,说明在k位上分布越均匀
    • 仅适用于事先明确知道表中所有关键码在每一位上的分布情况,完全依赖于关键码的集合
    • 如果换一个关键码的集合,就要重新进行分析。
  5. 基数转换法

    • 把关键码看成另一进制上的数
    • 把它转换成原来进制上的数
    • 取其中若干位作为散列地址
    • 一般取大于原来基数的数作为转换的基数,并且两个基数要互素
  6. 折叠法
    • 将关键码分割成位数相同的几部分(最后一部分位数不一定相同),取叠加和作为散列地址。
    • 分为移位叠加和分界叠加。
  7. ELFhash字符串散列函数

开散列方法

  1. 拉链法
    • 插入同义词时,可以对同义词链排序插入。
    • 性能分析:给定一个大小为M存储n个记录的表,理想情况下,散列把记录在表中M个位置平均放置,使得每个链表中有nM个记录。如果M>n,散列方法的平均代价就Θ(1)
    • 不适用与外存检索

2.桶式散列

  • 适合于存储在磁盘的散列表
  • 一个文件分成多个存储桶,每一个存储桶包含一个和多个页块。每个页块包含若干记录,页块之间用指针连接。
  • 散列函数h(K)表示关键码是K的记录所在的存储桶序号。

闭散列方法

  • 基地址位置d0=h(K)
  • 当冲突发生时,使用某种探查方法为关键码K生成一个散列地址序列d1,d2,,所有的di是后继散列地址
  • 当插入K时,如果基地址上的结点已经被其他数据元素占据,按这种探查方法后继地址,找到第一个空闲位置。如果满了,报告溢出。
  • 检索的策略(探查方法)必须和插入相同。
  • 探查方法

    1. 线性(顺序)探查
      • 如果记录的基位置被占用,顺序地查找表中第一个空闲地址。
        p(K,i)=i
      • 缺点:会发生聚集,导致很长的探查序列。
      • 改进:每次跳c个位置,但是还是可能导致聚集(clustering)。
      • p(K,i)=ic
    2. 二次探查
      探查的增量是
      p(K,i)={12,12,22,22,}
    3. 伪随机数序列探查
      p(K,i)=perm[i1]

      • 优点:二次探查和伪随机数序列探查都可以消除聚集。
      • 缺点:二次探查和伪随机数序列探查都不能消除二次聚集,即如果两个关键码散列到同一个基地址,得到的探查序列还是相同的,因而发生聚集。
    4. 双散列探查
      • 探查序列不仅仅是基地址的函数,还是是原来关键码的函数
      • 使用两个散列函数h1,h2
      • 利用第二个散列函数作为常数,每次跳过常数项,做线性检查
      • p(K,i)=ih2(key)
      • h2(key)必须与M互素。
      • 不易产生聚集,但是计算量增大。
  • 算法实现

    • 字典(dictionary),一种特殊的集合,元素是(关键码,属性值)二元组。
    • 同一个字典内关键码必须是不同的。
    • 主要操作:依据关键码来存储和析取值 insert(key,value),lookup(key)

      template<class Key,class Elem,class KEComp,class EEComp>class hashdict{
      private:
          Elem * HT;  //散列表
          int M;  //散列表大小
          int current;  //现有元素数目
          Elem EMPTY;  //空槽
          int p(Key K,int i);  //探查函数
          int h(int x) const;  //散列函数
          int h(char * x) const;  //字符串散列函数
      public: 
          hashdict(int sz,Elem e){
          M = sz;
          EMPTY = e;
          current = 0;
          HT = new Elem[sz];
          for(int i = 0;i < M;i++)
              HT[i] = EMPTY;
          }
          ~hashdict(){delete [] HT;}
      
          bool hashSearch(const Key&,Elem &) const;
          bool hashInsert(const Key& K);
          Elem hasDelete(const Key& K);
          int size() {return current;}
      };
      • 插入算法:基地址未被占用则直接插入,如果非空闲且已经是K则报告已经插入,否则按照探查方法找到下一个地址并循环,直到插入成功。

        //插入算法
        template<class Key,class Elem,class KEComp,class EEComp>
        bool hashdict<Key,Elem,KEComp,EEComp>::hashInsert(const Elem& e){
            int home = h(getkey(e));  //找到新结点基地址
            int i = 0;
            int pos  = home;
            while(!EEComp::eq(EMPTY,HT[pos])){
                if(EEComp::eq(e,HT[pos])) 
                    return false;
                i++;
                pos = (home + p(getkey(e),i)) % M;  //探查
            }
            HT[pos] = e;
            return true;
        }
      • 检索算法:查找K对应的基地址,如果空闲则报错,如果非空闲且等于K则检索成功,否则则按照探查方法找到下一个地址并循环,知道检索成功。

        //检索算法
        template<class Key,class Elem,class KEComp,class EEComp>
        bool hashdict<Key,Elem,KEComp,EEComp>::hashSearch(const Key& K,Elem e){
            int  i = 0;
            pos =  home = h(K);  // 初始位置
            while(!EEComp::eq(HT[pos],EMPTY){
                if(KEComp(HT[pos],K)){  //KE、EE的定义是什么?
                    e = HT[pos]; //这是说e=K?检索的结果不应该是地址吗?
                    return true;
                }
                i ++;
                pos = (home + p(K,i)) % M;
            }
            return false;
        }
    • 删除算法:删除不能影响后面的检索,空出的位置应该可以放新的插入。只有开散列方法才可以真正实现删除,闭散列方法只能标记墓碑,不能真正删除。

      //带墓碑的删除算法
      template<class Key,class Elem,class KEComp,class EEComp>
      bool hashdict<Key,Elem,KEComp,EEComp>::hashDelete(const Key& K,Elem e){
          int i = 0;
          pos = home = h(K);  //初始位置
          while(!EEComp::eq(EMPTY,HT[pos])){
              if(EEComp::eq(K,HT[pos])){
                  Key temp = HT[pos];
                  HT[pos] = TOMB;
                  return temp;
              }
              i++;
              pos = (home + p(K,i)) % M;
          }
          return EMPTY;
      }
      
      //带墓碑的插入算法
      template<class Key,class Elem,class KEComp,class EEComp>
      bool hashdict<Key,Elem,KEComp,EEComp>::hashInsert(const Elem& e){
          int insplace;
          int i = 0;
          int pos  = home = h(getkey(e));  
          bool tomb_pos = false;
          while(!EEComp::eq(EMPTY,HT[pos])){
              if(EEComp::eq(e,HT[pos])) 
                  return false;
              if(EEComp::eq(TOMB,HT[pos]) && !tomb_pos)  
              {
                  insplace = pos;
                  tomb_pos = true;
              }
              i++;
              pos = (home + p(getkey(e),i)) % M;  //探查
          }
          if(!tomb_pos)
              insplace = pos;  //如果没有墓碑,则insplace位于空槽位置
          HT[implace] = e;
          return true;
      }

本文主要参考Wang Tengjiao课件

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013795675/article/details/49943215
个人分类: 数据结构与算法
想对作者说点什么? 我来说一句

文件检索 检索检索检索

2010年09月24日 24KB 下载

信息资源检索信息资源检索

2011年07月06日 22KB 下载

检索文件的源代码资源

2010年07月01日 60KB 下载

CSCD检索CSCD检索

2009年07月02日 865KB 下载

solr大数据检索

2017年09月04日 139.76MB 下载

java文件检索

2011年11月02日 5KB 下载

dedecms5.7字母检索插件

2012年05月19日 58KB 下载

万方医学网检索方法

2012年11月29日 5.26MB 下载

文献阅读与检索

2011年09月24日 217KB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭