详解哈希表(散列表)

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

       首先,为什么要有哈希表?为什么,这很重要,凡是存在的都应该有其存在的理由。就像我的另外一篇博客中说的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

       谢谢!!

 


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值