转载-为什么是哈希表

 

为什么是哈希表?!

1、提出问题:

       这里有一个 大的跨国公司,公司中的职员信息全部存储在数据库中。对于其中的任何一个职员来说,他们的唯一标识就是员工号,而这个公司的员工号是按照职员工作的地点以 及部门及工作开始时间确定的,比如01-20-09-24-3,这一个职工编号(纯属杜撰,但也有实际作用,因为在像群体查找时会比较方便等),其中的 01代表亚洲办公区员工,20表示在研发部门,09-24表示09年9月24号入职,3表示为当天入职的第三个人。这样每一个员工号就代表唯一的一个员 工,假如现在我们需要随机抽取20000名员工搞一个什么活动,然后我们需要从数据库取出20000个员工的信息存在一个地方,然后对员工信息进行一系列 的操作,无非增、删、改、查,现在就会出现一个问题,我们怎么存储这20000个员工的信息,使得操作的时间更快?

       我们会想到的是什么,数组?链表?

              如果是数组,那我们怎么根据一个员工号来得到员工所在数组的索引,快速返回相应的员工信息呢?

              如果是链表,难道我们每次查找时都要遍历整个链表吗?如果员工数更多,这样可行吗?

       由此就引出为什么是哈希表?

              因为实际中会存在上述的问题,因此哈希表应运而生,在大数据量中进行查找,为了提高速度,我们会选择数组,因为如果知道数据在数组中的索引,那么时间复杂 度就是O(1)的,但是对于实际中的这些问题,数组的索引就不是那么好得到的。比如就像上面那样,知道员工号,来找数据,而员工号根本不是索引!所以我们 需要根据员工号来生成索引,那么我们给定一个员工号,就会对应一个索引,那么就可以直接找到存储的位置,这样就很好了。

             因此,上面最后所说的由员工号拿到数组的索引的过程,就是一个哈希化过程。哈希化过程:给定关键字,通过哈希函数,生成确定的索引值。

 

2、给出上述问题的解决方法(一步步演示用哈希表处理):

       我们现在明确一下目标,就是根据职员号生成数组索引,将职员数据存入数组中,快速进行修改!

       哈希表的方法是什么呢?

       哈希表的方法是,对于键值(这里是职员号)给出一个映射,将键值映射到确定的数组索引上,每次查找时,只需要输入键值,然后根据映射找到索引就可以进行操 作。因为键值的数据类型各式各样,那么这个映射也是五花八门,没有固定的取法,但是对于这个映射最好满足两个要求:计算方便、产生的索引随机性好!

这样映射在哈希表中称为哈 希函数。对于上面的职员号,我们可以简单的定义一个哈希函数为,对于上面的职员号转化为整数,直接对于存储数组的大小进行取余运算,通过这样的方法来生成 数组索引。对于存储的数组一般选取的都会比要存储的元素大,对于现在已知所需存储数据的大小,经证明一般来说,当所存储的数据是整个数组的2/3时,效果 比较好,因此我们的存储数组大小可以设定为30001,为什么是30001呢?选取质数的原因与后面怎么解决哈希冲突有莫大的关系!

         给出哈希函数如下: 

Java代码
/** 

 * 根据key值,进行hash过程 

 * 其中的capacity为数组容量大小 

 * @param key   传进来的键值key 

 * @return  返回hash函数产生的数组索引值 

 */  

public int hash(String key){  

    int k = Integer.parseInt(key);  

    return k % capacity;  

}  

/**
 * 根据key值,进行hash过程
 * 其中的capacity为数组容量大小
 * @param key    传进来的键值key
 * @return    返回hash函数产生的数组索引值
 */
public int hash(String key){
    int k = Integer.parseInt(key);
    return k % capacity;
}

 

       那么从上面很容易就会知道,这样的哈希函数,对于不同的键值可能产生相同的数组索引值,这就是所谓的哈希冲突。

 

       对于解决哈希冲突,有两种方法:开放地址法和链地址法。

      (在这里我们使用开放地址法,因为在下一篇分析HashTable和HashMap博客中会分析链地址法)

        一般来说,对于开放地址法,又可以分为:线性探测法、二次探测和再哈希。(不要被名词吓着......其实都很简单)

        首先概述一下对于开放地址法的这三种方法的大体实现思想:

 

        线性探测:当经过hash方法计算产生数组索引后,如果发生冲突,那么就检查索引的下一位数组是否空着,如果空着,那么就将数据放置进去,如果没有空着,则继续向下一位进行检测,知道将数据放入或是数组已经放满。示例代码:

 

Java代码 

 

   1. public void put(String key,Clerk clerk){  
   2.     int index = hash(key);  
   3.     while(clerkArray[index] != null){  
   4.         ++index;  
   5.         index %= clerkArray.length;  
   6.     }  
   7.     clerkArray[index] = clerk;  
   8. }  

public void put(String key,Clerk clerk){
    int index = hash(key);
    while(clerkArray[index] != null){
        ++index;
        index %= clerkArray.length;
    }
    clerkArray[index] = clerk;
}

 

        二次探测:同样的类似上面的线性探测,但是这次不是移向下一位,而是这样移动,第一个移动1^2位,如果非空,则继续再移动2^2位,如果还是非空,那么再移动3^2位,以此类推。

 

Java代码 
   1. public void put(String key,Clerk clerk){  
   2.     int index = hash(key);  
   3.     int step = 1;  
   4.     while(clerkArray[index] != null){  
   5.         index += Math.pow(step++, 2);  
   6.         index %= clerkArray.length;  
   7.     }  
   8.     clerkArray[index] = clerk;  
   9. }  

public void put(String key,Clerk clerk){
    int index = hash(key);
    int step = 1;
    while(clerkArray[index] != null){
        index += Math.pow(step++, 2);
        index %= clerkArray.length;
    }
    clerkArray[index] = clerk;
}

 

 

           再哈希: 对于上面两种处理冲突的方法,只要是映射到同一索引位置,如果发生冲突,所有的冲突元素的移位步长都是相同的,所以为了避免这种情况,才会有了再哈希这个 方法,方法是,在冲突时,对于键值,再经过一个hash方法的计算,来生成对于特定键值有特定步长,这样即使是映射到同一索引位置放生冲突,但是对于不同 的键值,处理冲突移动的步长会不同。

 

Java代码 
收藏代码

   1. public void put(String key,Clerk clerk){  
   2.     int index = hash(key);  
   3.     int step = hashStep(key);  
   4.     while(clerkArray[index] != null){  
   5.         index += step;  
   6.         index %= clerkArray.length;  
   7.     }  
   8.     clerkArray[index] = clerk;  
   9. }  
  10.       
  11. public int hashStep(String key){  
  12.     return 5 - Integer.parseInt(key) % 5;  
  13. }  

public void put(String key,Clerk clerk){
    int index = hash(key);
    int step = hashStep(key);
    while(clerkArray[index] != null){
        index += step;
        index %= clerkArray.length;
    }
    clerkArray[index] = clerk;
}
    
public int hashStep(String key){
    return 5 - Integer.parseInt(key) % 5;
}

 

        也许我们会问,为什么会有这三种方法呢?产生的原因是什么呢?

        产生这三个方法的原因是,在处理哈希冲突的时候会引起聚焦(就是元素会聚集在发生冲突的地方,从而会影响哈希表的性能),因此这三种方法依次减弱了这种聚 焦效应。同时在这里也可以解释为什么数组的长度要选择质数?如果不选择质数,那么总有一个比原数小,而比1大的数整除这个长度,所以当我们按照我们选择的 步长去移动时,可能会出现整除数组长度的情况,那么这会使得移动跳过某些空位,而在固定的几个位置上进行检测,但是如果长度是质数,就会避免这种情况。

 

3、问题总结

 

      现在我们可以由上面的结果,就可以实现我们自己的哈希表了,因为上面已经描述了,如何进行哈希化处理得到数组索引,在得到索引冲突时,该如何处理冲突。然后剩下的工作就是围绕这两点展开的,来实现查找,删除或是添加等方法,在这里就不再赘述了。

 

       下一篇博客会来分析自带的Hashtable和HashMap源码,来提高对哈希的进一步认识,及Hashtable和HashMap之间的比较!

注:本篇文章转载自 java EYE wojiaolongyinong的文章《为什么是哈希表》  原文链接 http://wojiaolongyinong.iteye.com/blog/1967089

转载于:https://www.cnblogs.com/CHSelf/p/3402423.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值