zoukankan      html  css  js  c++  java
  • 哈希表的查找

    一、哈希表相关概念

    1、哈希函数的基本概念

    哈希表又称散列表。

    哈希表存储的基本思想是:以数据表中的每个记录的关键字 k为自变量,通过一种函数H(k)计算出函数值。把这个值解释为一块连续存储空间(即数组空间)的单元地址(即下标),将该记录存储到这个单元中。在此称该函数H为哈希函数或散列函数。按这种方法建立的表称为哈希表或散列表。

    理想情况下,哈希函数在关键字和地址之间建立了一个一一对应关系,从而使得查找只需一次计算即可完成。由于关键字值的某种随机性,使得这种一一对应关系难以发现或构造。因而可能会出现不同的关键字对应一个存储地址。即k1k2,但H(k1)=H(k2),这种现象称为冲突。把这种具有不同关键字值而具有相同哈希地址的对象称“同义词”。

    在大多数情况下,冲突是不能完全避免的。这是因为所有可能的关键字的集合可能比较大,而对应的地址数则可能比较少。

    对于哈希技术,主要研究两个问题:

    1)如何设计哈希函数以使冲突尽可能少地发生。

    2)发生冲突后如何解决。

    2、哈希函数的构造方法

    常见的构造方法有很多种,如直接定址法,数字分析法,平方取中法等。接下来,我们介绍其中的几种:

    1)除留余数法

    取关键字k被某个不大于表长m的数p除后所得余数作为哈希函数地址的方法。即:

               Hk)=k  mod        

    这种方法的关键是选择好p。使得数据集合中的每一个关键字通过该函数转化后映射到哈希表的任意地址上的概率相等。理论研究表明,一般取p为小于m的最大质数或不包含小于20的质因素的合数。  

    2)平方取中法

    先将关键字平方,然后取其中间几位作为散列地址。所取位数由地址空间范围决定。若地址空间小于所取位数值决定的范围,可通过乘以一比例因子来解决。

    3)折叠法

        把关键字分割成位数相等(最后一部分的位数可以不同)的几部分,然后通过折叠后将几部分进行相加,丢掉进位位,所得值即为散列地址。散列的位数由地址空间的位数而定。

       分割方法:从右至左

       相加方法有两种:

       移位叠加:将分割后的各部分低位对齐相加。

       界间叠加:将某些部分倒置后再相加。相当于把关键字看成一张纸,从一端向另一端沿间界逐层折叠,再把相应位数相加。

    3、哈希函数的冲突检测方法

    假设哈希表的地址范围为0m-l,当对给定的关键字k,由哈希函数H(k)算出的哈希地址为i0im-1)的位置上已存有记录,这种情况就是冲突现象。

        处理冲突就是为该关键字的记录找到另一个“空”的哈希地址。即通过一个新的哈希函数得到一个新的哈希地址。如果仍然发生冲突,则再求下一个,依次类推。直至新的哈希地址不再发生冲突为止。

    常用的处理冲突的方法有开放地址法、链地址法等几类。

    1)开放地址法

    当发生冲突时,将依次探测“下一个位置”,直到找到其关键字相匹配的元素或找到一个空位插入。设哈希空间长度为m,“下一个位置”由下式确定:

        Hi=(H(key)+di) mod m

        H(key):哈希函数

        m:哈希表长度

        di:求“下一个位置”的增量

     di的确定方法

    a) 线性探测再散列

    di=12,…,m-1

      这种di的取法称为线性探测再散列。即“下一个位置”为哈希表的直接后继。若当di=m-1时仍未查到,则说明表满,还要查找另外的溢出表。缺点:容易产生“二次聚集” 

    b)二次探测再散列

           di=12,-1222-22,…,±k2                              (km/2)

    c)伪随机探测再散列

        di由一个伪随机函数发生器产生的一个伪随机数序列来确定。

    2)链地址法

    将所有关键字为同义词的记录存储在同一链表中。设哈希地址在区间[0..m-1]上,设置一个指针向量:

         Chain chainhash[m];

    每个分量的初始状态为空,凡哈希地址为i的的记录则插入到chainhash[i]的链表中。插入的位置可以在表头、表尾,也可在中间。为了查找的方便,可以使同一链表中记录的关键字有序。如

    K={19,14,23,01,68,20,84,27,55,11,10,79}

        H(key)=key mod 13,存储链表如图中所示:

    28、哈希表(Hash)的查找 - 墨涵 - 墨涵天地

    二、哈希表C语言描述

    28、哈希表(Hash)的查找 - 墨涵 - 墨涵天地

    28、哈希表(Hash)的查找 - 墨涵 - 墨涵天地28、哈希表(Hash)的查找 - 墨涵 - 墨涵天地

    三、哈希表C语言实现

      1 #include "stdio.h"
      2 
      3 #include "stdlib.h"
      4 
      5 #define SUCCESS 1
      6 
      7 #define UNSUCCESS 0
      8 
      9 #define DUPLICATE -1
     10 
     11 #define OK 1
     12 
     13 #define ERROR -1
     14 
     15 #define EQ(a,b) ((a)==(b))
     16 
     17 #define LT(a,b) ((a)< (b))
     18 
     19 #define LQ(a,b) ((a)<=(b))
     20 
     21 #define BT(a,b) ((a)> (b))
     22 
     23 #define NULLKEY -111
     24 
     25 int hashsize[]={11,19,29,37}; // 哈希表容量递增表,
     26 
     27                               //一个合适的素数序列
     28 
     29 int m=0; // 哈希表表长,全局变量
     30 
     31 typedef int KeyType;
     32 
     33 typedef int info;
     34 
     35 typedef struct
     36 
     37 {
     38 
     39 KeyType key;
     40 
     41 //info otherinfo;
     42 
     43 }ElemType;
     44 
     45 typedef struct
     46 
     47 {
     48 
     49 ElemType *elem;
     50 
     51 int count;
     52 
     53 int sizeindex;
     54 
     55 }HashTable;
     56 
     57  
     58 
     59 int InitHashTable(HashTable &H)
     60 
     61  { // 操作结果: 构造一个空的哈希表
     62 
     63    int i;
     64 
     65    H.count=0; // 当前元素个数为0
     66 
     67    H.sizeindex=0; // 初始存储容量为hashsize[0]
     68 
     69    m=hashsize[0];
     70 
     71    H.elem=(ElemType*)malloc(m*sizeof(ElemType));
     72 
     73    if(!H.elem)
     74 
     75      exit(0); // 存储分配失败
     76 
     77    for(i=0;i<m;i++)
     78 
     79      H.elem[i].key=NULLKEY; // 未填记录的标志
     80 
     81    return OK;
     82 
     83  }
     84 
     85 void DestroyHashTable(HashTable &H)
     86 
     87  { // 初始条件: 哈希表H存在。操作结果: 销毁哈希表H
     88 
     89    free(H.elem);
     90 
     91    H.elem=NULL;
     92 
     93    H.count=0;
     94 
     95    H.sizeindex=0;
     96 
     97  }//DestroyHashTable
     98 
     99 int Hash(KeyType K)
    100 
    101  { // 一个简单的哈希函数(m为表长,全局变量)
    102 
    103    //除留余数法
    104 
    105    return K%m;
    106 
    107  }//Hash
    108 
    109  void collision(int &p,int d) // 线性探测再散列
    110 
    111  { // 开放定址法处理冲突
    112 
    113    p=(p+d)%m;
    114 
    115  }//collision
    116 
    117  
    118 
    119 int SearchHash(HashTable H,KeyType K,int &p,int &c)
    120 
    121 {    
    122 
    123 p=Hash(K); //构造哈希函数
    124 
    125 while(H.elem[p].key!=NULLKEY&&!EQ(K,H.elem[p].key))
    126 
    127        {
    128 
    129        collision(p,++c); //冲突检测
    130 
    131        if(c>=m) break;
    132 
    133        }
    134 
    135 if(EQ(K,H.elem[p].key))
    136 
    137        return SUCCESS;
    138 
    139 else return UNSUCCESS;
    140 
    141 }//SearchHash
    142 
    143 int InsertHash(HashTable &H,ElemType e);
    144 
    145 void RecreateHashTable(HashTable &H) // 重建哈希表
    146 
    147  { // 重建哈希表
    148 
    149    int i,count=H.count;
    150 
    151    ElemType *p,*elem=(ElemType*)malloc(count*sizeof(ElemType));
    152 
    153    p=elem;
    154 
    155    printf("重建哈希表
    ");
    156 
    157    for(i=0;i<m;i++) // 保存原有的数据到elem中
    158 
    159      if((H.elem+i)->key!=NULLKEY) // 该单元有数据
    160 
    161        *p++=*(H.elem+i);
    162 
    163    H.count=0;
    164 
    165    H.sizeindex++; // 增大存储容量
    166 
    167    m=hashsize[H.sizeindex];
    168 
    169    p=(ElemType*)realloc(H.elem,m*sizeof(ElemType));
    170 
    171    if(!p)
    172 
    173      exit(-1); // 存储分配失败
    174 
    175    H.elem=p;
    176 
    177    for(i=0;i<m;i++)
    178 
    179      H.elem[i].key=NULLKEY; // 未填记录的标志(初始化)
    180 
    181    for(p=elem;p<elem+count;p++) // 将原有的数据按照新的表长插入到重建的哈希表中
    182 
    183      InsertHash(H,*p);
    184 
    185  }//RecreateHashTable
    186 
    187  
    188 
    189 int InsertHash(HashTable &H,ElemType e)
    190 
    191  { // 查找不成功时插入数据元素e到开放定址哈希表H中,并返回OK;
    192 
    193    // 若冲突次数过大,则重建哈希表
    194 
    195    int c,p;
    196 
    197    c=0;
    198 
    199    if(SearchHash(H,e.key,p,c)) // 表中已有与e有相同关键字的元素
    200 
    201      return DUPLICATE;
    202 
    203    else if(c<hashsize[H.sizeindex]/2) // 冲突次数c未达到上限,(c的阀值可调)
    204 
    205    { // 插入e
    206 
    207      H.elem[p]=e;
    208 
    209      ++H.count;
    210 
    211      return OK;
    212 
    213    }
    214 
    215    else
    216 
    217      RecreateHashTable(H); // 重建哈希表
    218 
    219    return ERROR;
    220 
    221  }
    222 
    223 int InsertHashD(HashTable &H)
    224 
    225 {
    226 
    227 ElemType e;
    228 
    229 printf("input the data until -1
    ");
    230 
    231 scanf("%d",&e.key);
    232 
    233 while(e.key!=-1)
    234 
    235   {
    236 
    237   InsertHash(H,e);
    238 
    239   printf("input the data until -1
    ");
    240 
    241   scanf("%d",&e.key);
    242 
    243   }//while
    244 
    245 return 1;
    246 
    247 }//InsertHashD
    248 
    249 int SearchHashD(HashTable &H)
    250 
    251 {
    252 
    253 KeyType key;
    254 
    255 int p=0,c=0;
    256 
    257 printf("input the data you want to search:
    ");
    258 
    259 scanf("%d",&key);
    260 
    261 if(SearchHash(H,key,p,c))
    262 
    263        printf("the location is %d,%d
    ",p,H.elem[p].key);
    264 
    265 else printf("Search Failed!
    ");
    266 
    267 return 1;
    268 
    269 }//SearchHashD
    270 
    271 void print(int p,ElemType r)
    272 
    273  {
    274 
    275    printf("address=%d (%d)
    ",p,r.key);
    276 
    277  }//print
    278 
    279  void TraverseHash(HashTable H,void(*Vi)(int,ElemType))
    280 
    281  { // 按哈希地址的顺序遍历哈希表
    282 
    283    printf("哈希地址0~%d
    ",m-1);
    284 
    285    for(int i=0;i<m;i++)
    286 
    287      if(H.elem[i].key!=NULLKEY) // 有数据
    288 
    289        Vi(i,H.elem[i]);
    290 
    291  }//TraverseHash
    292 
    293 void TraverseHashD(HashTable &H)
    294 
    295 {
    296 
    297 TraverseHash(H,print);
    298 
    299 }//TraverseHashD
    300 
    301 int main()
    302 
    303 {
    304 
    305 HashTable H;
    306 
    307 InitHashTable(H);
    308 
    309 InsertHashD(H);
    310 
    311 SearchHashD(H);
    312 
    313 TraverseHashD(H);
    314 
    315 DestroyHashTable(H);
    316 
    317 return 1;
    318 
    319 }

    四、复杂度分析

    从哈希表的查找过程可见:

    1、虽然哈希表在关键字与记录的存储位置之间建立了直接映象,但由于冲突的产生,使得哈希表的查找过程仍然是一个给定值和关键字进行比较的过程。因此,仍需以平均查找长度作为衡量哈希表的查找效率的度量。

    2、查找过程中需与给定值进行比较的关键字的个数取决于下面三种因素:

        哈希函数

        处理冲突的方法

        哈希表的装填因子

        哈希函数的好坏首先影响出现冲突的频繁程度

    假定哈希函数是“均匀的”,即不同的哈希函数对同一组随机的关键字,产生冲突的可能性相同。

    对同一组关键字,设定相同的哈希函数,则不同的处理冲突的方法得到的哈希表不同,它的平均查找长度也不同。

    若处理冲突的方法相同,其平均查找长度依赖于哈希表的装填因子。

    冲突的多少与表的填满程度有关,填满程度用α表示:

            α=表中记录数/哈希表的长度

    α标志哈希表的装满程度。

    α越小,发生冲突的可能性越小,反之,α越大,表中已填入的记录越多,再填记录时,发生冲突的可能性越大。查找时,给定值需与之进行比较的关键字个数就越多,检索越慢。

    28、哈希表(Hash)的查找 - 墨涵 - 墨涵天地

    这世界上有一种鸟是没有脚的,它只能够一直的飞呀飞呀,飞累了就在风里面睡觉,这种鸟一辈子只能下地一次,那一次就是它死亡的时候。
  • 相关阅读:
    Java入门
    Java入门
    Java入门
    Java入门
    Java入门
    Java入门
    Java入门
    random库的使用
    程序的控制结构
    数据类型
  • 原文地址:https://www.cnblogs.com/xuyinghui/p/4593254.html
Copyright © 2011-2022 走看看