详解哈希表(散列表)

来源:互联网 发布:手机淘宝如何看总消费 编辑:程序博客网 时间:2024/04/28 23:30

       昨天看了哈希表,起先一直在看哈希表的实现,看的有点头大。于是硬着头皮反复看了几遍,终于看懂了一些眉目。现在做下记录

       首先,为什么要有哈希表?为什么,这很重要,凡是存在的都应该有其存在的理由。就像我的另外一篇博客中说的Java中为什么要有接口一样。这里,假设我们有一个很大的数组(如一个学生考试系统中的学生成绩),我们可以很方便的根据数据元素的下标定位到该元素。但是如果现在要求根据某个学生的学号来查找该学生呢?是的,只有学号的话,我们只能通过遍历比较实现数据的查找。但是比较是耗时的。能否直接使用检索关键字(如这里的学号)来得到元素的下标呢?答案是肯定的,事实上,哈希表就是做了这件事,使用哈希表可以使算法复杂度降低到O(1)。

       我们将数组元素通过哈希函数(这个哈希函数可以自己定义,其实就是一个映射,将检索关键字映射到某个下标)。那么假如现在要存储、检索的是一个单词表。单词表中的单词来自与字典,而字典中有50000个单词。那么现在我们需要存储、检索1000个单词,我们该怎么做?

       一、需要能够将单词直接映射成数组下标(这个可以通过自定义哈希算法实现),

       二、合理地检索速度和内存开销(这个就需要对数组进行压缩了)。

       下面来使用哈希表存储80个范围从aaaa到zzzz的四位字母的字符串,实现添加、查找功能。

新建一个节点对象DataItem

package com.wly.hashing;/** * 要保存的元素对象,设定其value值是一个4位长的英文字母即(aaaa-zzzz) * @author wly * */public class DataItem {private String value;public DataItem(String value) {this.value = value;}/** * 哈希函数,得到从key映射的为处理过的index * @return */public int hashFunc() {char[] cArray = value.toCharArray();//这里之所以乘以26,是因为这里的26相对于十进制里的“十”,是为了确保每个元素都有一个唯一的key值int key = cArray[0]*26*26*26 + cArray[1]*26*26 + cArray[2]*26 + cArray[3];return key;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}}
       然后新建一个实现哈希表功能的类MyHashTable:
package com.wly.hashing;/** * 哈希表,使用数组实现 * @author wly * */public class MyHashTable {private DataItem[] array;private int arraySize;public MyHashTable(int size) {arraySize = size;array = new DataItem[arraySize];}/** * 插入元素,插入位置由元素的哈希值决定 * @param item */public void insert(DataItem item) {//得到插入下标int insertPos = item.hashFunc() % arraySize;//插入线性探测插入数据while(insertPos < arraySize && (array[insertPos] != null)) {insertPos ++;}if(insertPos < arraySize) {array[insertPos] = item;}}/** * 查找数据项 * @param item * @return */public DataItem find(DataItem item) {int findPos = item.hashFunc() % arraySize;while(findPos < arraySize && array[findPos] != null) {if(array[findPos].getValue() == item.getValue()) {System.out.println("找到value为" + item.getValue() + "的数据记录");return array[findPos];}findPos ++;}System.out.println("未找到value为" + item.getValue() + "的数据记录");return null;}public void displayItems() {for(int i=0;i<array.length;i++) {if(array[i] != null) {System.out.println(array[i].getValue());} else {System.out.println("null");}}}}
      最后测试一下:
package com.wly.hashing;public class Test {public static void main(String[] args) {MyHashTable hashTable = new MyHashTable(120);//产生随机元素,注意这里可能会产生重复元素,为了避免问题变得更复杂,先暂时不管for(int i=0;i<80;i++) {char[] temp = new char[4];for(int j=0;j<4;j++) {temp[j] = (char)((Math.random() * 26) + 97);}DataItem item = new DataItem(String.valueOf(temp));hashTable.insert(item);}hashTable.insert(new DataItem("abcd")); //插入一个确定的数据,用以测试查找hashTable.displayItems(); //显示所有元素//测试查找hashTable.find(new DataItem("abcd"));}}
        输出结果:

null

null

null

。。。

。。。

。。。

null

abcd

djaa

null

vsiw

。。。

。。。

找到value为abcd的数据记录

       数组元素压缩,因为要存储的元素范围大于压缩后的容器范围,不论使用何种哈希函数(只要是使用数组,而不是数组链表实现哈希表),冲突是不可避免的,这里说的冲突就是元素的聚集,需要注意一点的时,一个小的聚集会加速大得聚集的产生,这也就是哈希表在快要被填满的时候,效率会严重下降的原因。解决具体的方法在向哈希表中添加元素时使用以下方法

       线性探测:如果要插入的元素指向的下标位置已经存在其他元素了,则将向后递增。这种做法会导致大量聚集的产生。

       二次探测:如果要插入的元素指向的下标位置已经存在其他元素了,则向后以二次方的增量递增,如x+1,x+4,x+9,x+16。这种方法是对线性探测的优化。

       再哈希法:对当前指向的下标在进行一次哈希化处理。是每个元素的探测步长都不同。

       O啦~~~

       转帖请保留出处:http://blog.csdn.net/u011638883/article/details/11794205

       谢谢!!

 


原创粉丝点击