一、查找及部分基本概念

  部分概念:

  1. 查找表: 要进行查找的数据结构,可以是线性表、树表、散列表等。

  2. 关键字: 能够标识一个元素的数据项。

  3. 动态查找和静态查找: 查找过程中可以对查找表进行操作(比如说插入、删除)称为动态查找表,不能操作则为静态查找表。

  4. 平均查找长度(ASL):为确定要查找的元素而需要与其他元素进行比较的期望值。 

  ASL = (c1p1 + c2p2 + ... + cnpn) / n  其中pi是第i条记录的概率,ci为找到第i条记录时与给定值已经比较过的次数。

 

  如果单说查找,我们其实已经非常熟悉了。但是本章着重解决的是在不同的情境下不同查找方法的效率问题,下面我会回顾书上学习到的几个查找的基本方法并分析其在不同应用情境下的优劣性。

 

二、线性表的查找

 

  1. 顺序查找  从头遍历到尾(也可以倒过来)

   特别指出:设置监视哨的顺序查找,其相较于普通查找的区别在于:通过改变判断条件减小了比较次数

int Search(SSTable ST , KeyType key){
  ST.R[0]. key = key;
  for(i = ST.length; ST.R[i] . key != key ; --i);
  return i;
}
设置监视哨的顺序查找

  ASL分析:若查找概率相同且进行顺序查找,概率相同:p1 + p2 +... +pn = 1; 顺序查找:(1+2+...+n)/n = (1+n)*n/2n =   (n+1)/2;

  故ASL = (1+n)/2   时间复杂度为O(n)  优点:适合任何线性表  缺点: n值很大时效率很低 

  2. 折半查找  

   从表的中间记录开始,比较给定值和中间记录是否相等。如果不相等,则在大于或者小于中间值的一部分再次进行折半查找。

int Search(SSTable ST, KeyType key){
  int low = 1;
  int high = ST.length;
  while(low<=high){
          int 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 ;
}
折半查找
int Search(SSTable ST, KeyType key, int low , int high){
    while(low<=high){
        int mid = (low+high)/2;
        if(key == ST.R[mid].key) return mid;
        else if(key<ST.R[mid].key)  Search(ST, key,low, mid-1);
        else Search(ST, key, mid+1, high);
}
return 0;
}
折半查找-递归实现

 ASL分析:若查找概率相同,ASL = (c1+c2+...+cn)/n ;  c min = 1 , c max = 不大于log2 n的整数 +1 ;ASL = (1 * 2^(1-1) + 2 * 2^(2-1) + ... + (log2 n +1)*2^(log2n))/n = (n+1)*log2(n+1)/n -1 n较大时取近似值log2(n+1)-1

  ASL = log2(n+1) - 1  时间复杂度为O(log2n)  优点:n值大时效率高  缺点: 只适用于顺序存储结构;不适用于动态查找表

  

三、树表的查找

  1. 二叉排序树

  需要满足以下要求:①若左子树不空,左子树上的所有结点的值均小于根节点的值 ② 若右子树不空,右子树上的所有结点的值均小于根节点的值 ③左右子树分别也是二叉排序树

   ASL = [(n+1)/n] * log2(n+1) - 1  时间复杂度是O(log2n)  优点:对于经常进行插入、删除、查找运算很方便

  2. 平衡二叉树

  满足以下要求:①左子树和右子树的深度之差的绝对值不超过1 ②左子树和右子树也是平衡二叉树

  构造方法:把不平衡消灭在最早的时候

  查找、删除、插入时间复杂度是O(log2n)

  3. B树

  B树又分为B-树和B+树,B树存在的意义在于降低对外存设备的访问次数。适用于数据库系统中的索引组织、文件索引系统。

  

四、散列表查找(重点)

  部分概念:

  1. p = H(key)  p:记录的存储位置  key:关键字 H() 对应关系的散列函数 散列表查找法的核心思想就是建立元素的存储位置和其关键字之间的关系,减少查找过程中的比较次数。

  2.  散列表: 存储按照散列函数计算得到的散列地址的数据记录的连续地址空间。

  3.  冲突: 不同关键字却对应同样的散列地址。这些冲突的关键字之间互称同义词。

  4.  装填因子: 表中填入的记录数/散列表的长度

  构造散列函数:

  1. 数字分析法 适合有多位重复,此时可选那几位真正区分的数。

  2. 平方取中法  这个方法计算很简单,假设关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227,用做散列地址。

                          适合位数不多,也不方便看出规律的情况。

  3. 折叠法  把关键字分为位数相同的几部分,再相加取和作为散列地址。

                          适合关键字的位数较多的情况。

  4. 保留余数法 H(key)= key%p 一般选小于表长的最大质数。(最常用)

 

  处理冲突:

  选再好的散列函数,仍可能有冲突的情况发生。处理冲突的方法有两种:一种是比较“与世无争”的方法:我的位置被人占了,我边上去(开放地址法);另一种是“合作共赢”的办法,既然都坐这个位置,那谁也别独享,大伙儿都共用这个位置(链地址法)。

  开放地址法按照找空位的方法不同,又分为线性探测法和二次探测法。线性探测法是见空就钻,这个位置发现有人后,挨个儿往后面搜,一有位置咱赶紧给坐上。但是这种方法显然还是有点“自私”,后边儿的人想要找到空位可得多走远路,这样算下来,时间复杂度就上来了。二次探测法则公平些,我发现没位置后我就摇号儿,摇到哪个位置我走过去看看,没人就坐下。

  H = (H(key) + di)%m

  线性探测法:di = 1,2,3...m-1 (找位置也不能走出散列表)

  二次探测法:di = 1^2,-1^2,2^2,-2^2,...,k^2,-k^2(k<=m/2)

  刚刚忘记说了,其实还有一种方法,di随机产生,这种方法被称为伪随机探测法。

  线性探测表的好处:不超出表长的情况下,一定可以保证每一个元素都找得到位置。

  二次、伪随机:可以避免线性探测表中可能出现的时间复杂度上升问题,但是缺点在于可能找不到不冲突的地址。

 

  链地址法:

  将所有关键字为同义词的记录存储在一个单链表中,称这种表为同义词子表,在散列表中只存储所有同义词子表前面的指针。

作业的第一题就是散列表的查找,利用了二次探测法。

#include<iostream>
#include<math.h>
#include<cstring>
using namespace std;

typedef struct {
    int key;
}HashTable;

int Isprime(int i, int MSize){
    for(i = 2 ; MSize%i != 0 && i<MSize;i++);
    return i;
}

void Hashing(int MSize , int NSize , HashTable &H){
    int key , visited[MSize] , n ;
    memset(visited,0,sizeof(visited));
        while(NSize --){
        cin>>key;
         for(n = 0 ; n<MSize ; n++){
            if(visited[(key+n*n)%MSize] == 0){
                if(NSize != 0){
                    cout<< (key+n*n)%MSize<<' ';
                }
                else{
                    cout<< (key+n*n)%MSize;
                }
                visited[(key+n*n)%MSize] = 1;
                  break;
        }
    }
        if(n == MSize) {
            if(NSize != 0)
            {
            cout<<'-'<<' ';
            }
            else{
                cout<<'-';
            }    
        }
    }
    }    


int main(){
    int MSize,NSize;
    HashTable H;
    cin>>MSize>>NSize;
    //判断用户所输入的MSize是否为质数,如果不是将该数字转化为比他大的最小质数。
    int i;
    while(Isprime(i , MSize) != MSize){
        MSize ++;
    }
    Hashing(MSize , NSize , H);    
    return 0;
}
hashing

 补充知识点:召回率和精度(书上没有,但是PTA上考了)

  相关 不相关
检索到 A B
未检索到 C D

                                                                                                                         召回率R = A / A+C

                                                                                                             精度(准确率)P = A / A+B 

 

举一个栗子:

某池塘有1400条鲤鱼,300只乌龟,300只甲鱼。现在以捕鲤鱼为目的。撒一大网,逮着了700条鲤鱼,200只乌龟,100只甲鱼。那么:

正确率 = 700 / (700 + 200 + 100) = 70% 捉到之中符合要求的 / 捉到的全部的

召回率 = 700 / 1400 = 50%                      捉到之中符合要求的 / 总的符合要求的