昨天看了哈希表,起先一直在看哈希表的实现,看的有点头大。于是硬着头皮反复看了几遍,终于看懂了一些眉目。现在做下记录
首先,为什么要有哈希表?为什么,这很重要,凡是存在的都应该有其存在的理由。就像我的另外一篇博客中说的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
谢谢!!