现在的位置: 首页 > 综合 > 正文

重温数据结构:哈希表,MyHashMap与MyHashSet的Java实现

2017年12月10日 ⁄ 综合 ⁄ 共 4988字 ⁄ 字号 评论关闭

一.哈希表的相关概念

哈希函数的构造方法:

1.      直接定址法

2.      数字分析法

3.      平法取中法

4.      折叠法

5.      除留余数法

6.      随机数法

二.处理冲突的方法

1.      开放定制法(线性探测再散列、二次探测再散列、伪随机探测再散列)

2.      再哈希法

3.      链地址法

4.      建立一个公共溢出区

三.MyHashMap的实现

要点:哈希函数的方法选用“除留余数法”,处理冲突的方法是用“链地址法”,也即是当发生冲突时,凡是哈希地址为i的记录,都插入到头指针为tables[i]的链表中,插入的位置可以是表头,也可以是表尾,也可以在中间(以保持key的有序)。

本次实现的MyHashMap是将其插入表头。

package edu.njupt.zhb;

/*
 *@author: ZhengHaibo  
 *web:     http://blog.csdn.net/nuptboyzhb
 *mail:    zhb931706659@126.com
 *2014-3-25  Nanjing,njupt,China
 */
public class MyHashMap<K,V>{
	/**
	 * HashMap数组中的节点
	 * @param <K>
	 * @param <V>
	 */
	public class MyEntry<K,V>{
		 public K key;//键
		 public V value;//值
		 public MyEntry<K,V> next;//下一个结点
	}
	
	private MyEntry<K, V> []tables;
	private final int INIT_LEN=50;
	public MyHashMap() {
		tables=new MyEntry[INIT_LEN];
	}
	/**
	 * 哈希函数的构造方法:除留余数法
	 * @param hashcode
	 * @return
	 */
	public int indexFor(int hashcode){
		return Math.abs(hashcode)%tables.length;
	}
	/**
	 * 获取键为key的值
	 * @param key
	 * @return
	 */
	public V get(K key){
		if(key==null){
			return null;
		}
		int hashcode=key.hashCode();//计算键的哈希值
		int index=indexFor(hashcode);//根据哈希值计算其在表中的下标index
		MyEntry<K, V> pNode=tables[index];//取出第一个节点,也即是链表的头
		//在链表中查找
		while(pNode!=null){//对链表进行遍历
			//当key的哈希值和真实值相等时返回
			if(pNode.key.hashCode()==hashcode&&(key==pNode.key || key.equals(pNode.key))){
				return pNode.value;
			}
			pNode=pNode.next;//链表:遍历下一个值
		}
		return null;
	}
	/**
	 * 将键值对放入map当中
	 * 解决冲突的方法:链地址法
	 * @param key
	 * @param value
	 * @return
	 */
	public V put(K key,V value){
		if(key==null){
			return null;
		}
		int hashcode=key.hashCode();
		int index=indexFor(hashcode);
		MyEntry<K, V> head=tables[index];//取出第一个节点,也即是链表的头
		MyEntry<K, V> pNode=head;
		//首先查找是否已经存在key
		while(pNode!=null){
			//当key的哈希值和真实值相等时
			if(pNode.key.hashCode()==hashcode&&(key==pNode.key || key.equals(pNode.key))){
				pNode.value=value;//更新当前key的值
				return pNode.value;
			}
			pNode=pNode.next;//链表:遍历下一个值
		}
		//如果没有查找到,将该值插入到链表的表头
		MyEntry<K, V> newNode=new MyEntry<K,V>();
		newNode.key=key;
		newNode.value=value;
		newNode.next=head;
		tables[index]=newNode;
		return value;
	}
	/**
	 * 根据key删除对应的值
	 * 思路:先查找,后删除
	 * @param key
	 * @return
	 */
	public V remove(K key){
		if(key==null){
			return null;
		}
		int hashcode=key.hashCode();
		int index=indexFor(hashcode);
		MyEntry<K, V> head=tables[index];
		MyEntry<K, V> pNode=head;
		if(pNode==null){
			return null;
		}
		//表头
		if(pNode.key.hashCode()==hashcode&&(key==pNode.key || key.equals(pNode.key))){
			V value=pNode.value;
			tables[index]=pNode.next;//删除头节点
			return value;
		}
		//首先查找是否已经存在key
		while(pNode.next!=null){
			//当key的哈希值和真实值相等时
			if(pNode.next.key.hashCode()==hashcode&&(key==pNode.next.key || key.equals(pNode.next.key))){
				V value=pNode.value;
				pNode.next=pNode.next.next;//删除该节点
				return value;
			}
			pNode=pNode.next;//链表:遍历下一个值
		}
		return null;
	}
	
	public void print(){
		for(int i=0;i<tables.length;i++){
			MyEntry<K, V> entry=tables[i];
			if(entry!=null){
				while(entry!=null){
					System.out.println("key="+entry.key+",value="+entry.value);
					entry=entry.next;
				}
			}
		}
	}
	/**
	 * 测试
	 * @param args
	 */
	public static void main(String[] args) {
		MyHashMap<String, String> map=new MyHashMap<String, String>();
		for(int i=0;i<100;i++){
			map.put("stu"+i, ""+i);
		}
		for(int i=0;i<20;i++){
			map.remove("stu"+i);
		}
		map.print();
	}
}

四.MyHashSet的实现

思路:和HashMap的思路一样,我们也是先计算hash值,然后折算出位置index,然后进行插入或删除。查找元素的效率很高,不需要对所有元素进行比较。

package edu.njupt.zhb;
/*
 *@author: ZhengHaibo  
 *web:     http://blog.csdn.net/nuptboyzhb
 *mail:    zhb931706659@126.com
 *2014-3-25  Nanjing,njupt,China
 */
public class MyHashSet<T>{
	/**
	 * 节点
	 */
	public class MyEntry<T>{
		public T data;//当前数据
		public MyEntry<T> next;
	}
	
	private MyEntry<T> []tables;//数组
	private final int INIT_LEN=50;
	private int size;
	public MyHashSet(){
		tables=new MyEntry[INIT_LEN];
		size=0;
	}
	/**
	 * 哈希函数构造方法
	 * @param hashcode 哈希值
	 * @return 对应的数组下标
	 */
	private int indexFor(int hashcode){
		return Math.abs(hashcode)%tables.length;
	}
	/**
	 * 添加元素
	 * @param object
	 * @return
	 */
	public boolean add(T data){
		if(data==null){
			return false;
		}
		int hashcode=data.hashCode();//计算哈希值
		int index=indexFor(hashcode);//定位存储的位置
		MyEntry<T> head=tables[index];//取出该位置的第一个元素(链表的头)
		MyEntry<T> pNode=head;//查找链表中是否存在
		while(pNode!=null){
			if(pNode.data.hashCode()==hashcode&&(pNode.data==data||pNode.data.equals(data))){
				return false;//已经存在重复的元素
			}
			pNode=pNode.next;
		}
		//不存在重复的元素或该位置为空,将该节点添加到链表的头
		MyEntry<T> newNode=new MyEntry<T>();
		newNode.data=data;
		newNode.next=head;
		tables[index]=newNode;
		size++;
		return true;
	}
	
	public boolean remove(T data){
		if(data==null){
			return false;
		}
		int hashcode=data.hashCode();
		int index=indexFor(hashcode);
		MyEntry<T> head=tables[index];
		MyEntry<T> pNode=head;
		if(pNode.data.hashCode()==hashcode&&(pNode.data==data||pNode.data.equals(data))){
			tables[index]=pNode.next;
			size--;
			return true;
		}
		while(pNode.next!=null){
			if(pNode.next.data.hashCode()==hashcode&&(pNode.next.data==data||pNode.next.data.equals(data))){
				pNode.next=pNode.next.next;//删除该元素
				size--;
				return true;
			}
			pNode=pNode.next;
		}
		return false;
	}
	
	public void print(){
		for(int i=0;i<tables.length;i++){
			MyEntry<T> entry=tables[i];
			if(entry!=null){
				while(entry!=null){
					System.out.println(entry.data);
					entry=entry.next;
				}
			}
		}
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		MyHashSet<String> set=new MyHashSet<String>();
		for(int i=0;i<100;i++){
			set.add(i+"");
		}
		for(int i=0;i<10;i++){
			set.remove(i+"");
		}
		System.out.println(set.size);
		set.print();
	}

}

附录:

用链地址法处理冲突时的Hash表结构示意图如下:



相关博客:

[1].HashMap的实现原理:http://zhangshixi.iteye.com/blog/672697

[2].LinkedHashMap的实现原理: http://zhangshixi.iteye.com/blog/673789

抱歉!评论已关闭.