温习之数据结构--哈希表

    哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表

    在这里会简单模拟哈希表,并说明几个解决冲突的方法。先来模拟一下哈希表,然后一步一步完善它。    先定义一个类,姑且叫信息类吧  

package hashTable;

public class Info {
	private int key;
	private String name;
	
	public Info(int key,String name){
		this.key = key;
		this.name = name;
	}
	
	public int getKey() {
		return key;
	}
	public void setKey(int key) {
		this.key = key;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

    很简单的一个类,不多说。请注意这里的key,接下来就要对这个key来做文章了。
    接下来定义一个HashTable类

package hashTable;

public class HashTable {
	private Info[] arr;
	
	public HashTable(){
		this.arr = new Info[100];
	}
	
	public HashTable(int maxSize){
		this.arr = new Info[maxSize];
	}
	
	/**
	 * 插入
	 * @param info
	 */
	public void insert(Info info){
		arr[info.getKey()] = info; 
	}
	
	public Info find(int key){
		return arr[key];
	}
}

    大家看到了,其实就是将Info对象的key作为HashTable对象数据的key,而整个info对象作为HashTable对象的value。

    测试类  

package hashTable;

public class TestHashTable {
	public static void main(String[] args) {
		HashTable ht = new HashTable();
		ht.insert(new Info(12,"张三"));
		ht.insert(new Info(20,"李四"));
		System.out.println(ht.find(20).getName());
	}
}
    最后输出的是李四。但是如果key有需求并不是数字,而是字符串呢?所以我们应该Info这个类的key的类型都改为String,但是我们就没有办法根据索引进行插入和查找了,所以需要定义一个方法public int hashCode(String key),根据key计算出一个数字,然后将这个数字作为HashTable数据中的key。
    如何根据一个字符串计算一个int类型的key呢?这里简单有三种:
    1、将字母转换成ASCII码,然后进行相加
    2、幂的连乘
    3、压缩可选值

    首先来看第一个

public int hashCode(String key){
		int hashVal = 0;
		for(int i=key.length()-1;i>=0;i--){
			int letter = key.charAt(i) - 96;
			hashVal += letter;
		}
		return hashVal;
	}
       上面就是用了 将字母转换成ASCII码,然后进行相加的办法,但是这里有个问题,如果测试数据是
    ht.insert(new Info("abc","张三"));
    ht.insert(new Info("bca","李四"));
    Sysout.out.println(ht.find("abc").getName());
    Sysout.out.println(ht.find("bca").getName());

    输出的两个值都是李四,这说明hashCode返回了一样的数值,而很明显abc和cba是两个不相同的字符串,为了解决这个问题,可以使用幂的连乘
    设字符串的长度为n,则幂的连乘公式 1*27的n-1次方+2*27的n-2次方+....+n*27的0次方,这样就肯定不会出现上述重复的现象。
    

public int hashCode(String key){
		int hashVal = 0;
		int pow27 = 1;
		for(int i=key.length()-1;i>=0;i--){
			int letter = key.charAt(i) - 96;
			hashVal += letter * pow27;
			pow27 *= 27;
		}
		return hashVal;
	}
    但是考虑到连乘会导致返回的hashVal可能是一个很大很大数字,既占用空间又容易出现问题,所以我们考虑第三种方式,就是 压缩可选值,其实就是给 hashVal取模,用数组的长度取模。将此方法的最后一句话话改为:
    
return hashVal % arr.length;
     ok,运行报错,原来是hashVal是int类型,当key的长度很长的时候,连乘是一个很大的数字会超过int的范围,所以我们这里应该使用BigInteger

public int hashCode(String key){
		BigInteger hashVal = new BigInteger("0");
		BigInteger pow27 = new BigInteger("1");
		for(int i=key.length()-1;i>=0;i--){
			int letter = key.charAt(i) - 96;
			BigInteger letterB = new BigInteger(String.valueOf(letter));
			hashVal = hashVal.add(letterB.multiply(pow27));
			pow27 = pow27.multiply(new BigInteger(String.valueOf(27)));
		}
		return hashVal.mod(new BigInteger(String.valueOf(arr.length))).intValue();
}

    做完这些,又发现在压缩后还是可能出现上面提到过的重复的情况,这里有几种办法。
    1、开放地址法:当发生冲突时,通过查找数组的一个空位,并将数据填入,而不在用哈希函数得到的数组下标,这个方法就叫做开放地址法,说的简单点就是,假设字符串a和ct经过哈希函数产生的hashCode一样,那么相当于在存ct所对应的info对象时把a所对应的info对象给挤掉了,也就是a和ct所对应的地址其实是一个,开发地址法的意思就是在计算a的hashcode后计算ct的hashCode,发现ct和a的hashCode冲突,那么就找个数组的空的位置来存放ct对应的info对象,而不是替换掉原来的。具体看代码吧。

public void insert(Info info){
		String key = info.getKey();
		int hashVal = hashCode(key);
		//加上arr[hashVal].getName() != null 是因为删除的时候会把name属性置为空
		//这里需要未被删除的数据
		while(arr[hashVal] != null && arr[hashVal].getName() != null){
			++hashVal;
			//为了防止数组长度-1 的数字+1后超过数组界限,所以再进行取余操作
			hashVal %= arr.length;
		}
		arr[hashVal] = info; 
	}
public Info find(String key){
		int hashVal = hashCode(key);
		while(arr[hashVal] != null){
			if(arr[hashVal].getKey().equals(key)){
				return arr[hashVal];
			}
			++hashVal;
			hashVal %= arr.length;
		}
		return null;
	}
	
	/**
	 * 删除,为了方便调试看对象的内容,这里删除方法返回Info对象
	 * @param key
	 * @return
	 */
	public Info delete(String key){
		int hashVal = hashCode(key);
		while(arr[hashVal] != null){
			if(arr[hashVal].getKey().equals(key)){
				Info tmp = arr[hashVal];
				tmp.setName(null);
				return tmp;
			}
			++hashVal;
			hashVal %= arr.length;
		}
		return null;
	}
    2、链地址法

        上个方法思想是当发现数组里面有与本次计算的hashCode相同的key,那么就在下一个空的位置插入进去,而在这里介绍的链地址法的思想是,在数组中每一个元素是一个链表,当发现有与本次计算的hashCode相同的key,则存放在对应元素的链表中。举个例子,a和ct计算的hashCode相同都为1,那么插入a的时候,在数组中的第二个元素(对应一个链表)中插入a对应的info对象。再细说一点就是每个数组中每个元素是链表,链表中每个元素是info对象。这样我们就可以通过链表的查找,删除,插入才进行hashTable的查找,删除,插入。请看代码:
    链表的模拟请看前几篇博客,以下与前几篇介绍的类似,只是更改了很小一部分。
    节点类:

package hashTable1;
/**
 * 节点类
 * @author Administrator
 */
public class Node {
	Info info;
	Node next;
	
	public Node(Info info){
		this.info = info;
	}
}



    链表类:
package hashTable1;

public class LinkList {
	private Node first;
	
	/**
	 * 插入一个节点
	 */
	public void insertNode(Info info){
		Node node = new Node(info);
		node.next = first;
		first = node;
	}
	
	/**
	 * 删除一个节点
	 */
	public void deleteFirst(){
		Node tmp = first;
		first = tmp.next;
	}
	
	/**
	 * 查找节点
	 */
	public Node find(String key){
		Node current = first;
		while(!key.equals(current.info.getKey())){
			if(current.next == null){
				return null;
			}
			current = current.next;
		}
		return current;
	}
	
	/**
	 * 根据值删除节点
	 */
	public Node delete(String key){
		Node current = first;
		Node tmp = first;
		while(!key.equals(current.info.getKey())){
			if(current.next == null){
				return null;
			}
			tmp = current;
			current = current.next;
		}
		if(current == first){
			first = current.next;
		}else{
			tmp.next = current.next;
		}
		return current;
	}
}



    HashTable类
    
package hashTable1;

import java.math.BigInteger;

public class HashTable {
	private LinkList[] arr;
	
	public HashTable(){
		this.arr = new LinkList[100];
	}
	
	public HashTable(int maxSize){
		this.arr = new LinkList[maxSize];
	}
	
	/**
	 * 插入
	 * @param info
	 */
	public void insert(Info info){
		String key = info.getKey();
		int hashVal = hashCode(key);
		if(arr[hashVal] == null){
			arr[hashVal] = new LinkList();
		}
		arr[hashVal].insertNode(info);
	}
	
	/*public Info find(String key){
		return arr[hashCode(key)];
	}*/
	public Info find(String key){
		int hashVal = hashCode(key);
		Node node = null;
		if(arr[hashVal] != null){
			node = arr[hashVal].find(key);
		}
		if(node != null){
			return node.info;
		}
		return null;
	}
	
	/**
	 * 删除,为了方便调试看对象的内容,这里删除方法返回Info对象
	 * @param key
	 * @return
	 */
	public Info delete(String key){
		int hashVal = hashCode(key);
		arr[hashVal].delete(key);
		return null;
	}
	
	public int hashCode(String key){
		BigInteger hashVal = new BigInteger("0");
		BigInteger pow27 = new BigInteger("1");
		for(int i=key.length()-1;i>=0;i--){
			int letter = key.charAt(i) - 96;
			BigInteger letterB = new BigInteger(String.valueOf(letter));
			hashVal = hashVal.add(letterB.multiply(pow27));
			pow27 = pow27.multiply(new BigInteger(String.valueOf(27)));
		}
		return hashVal.mod(new BigInteger(String.valueOf(arr.length))).intValue();
	}
		
}



    如此这般便能够实现链地址法了。


    




    

转载于:https://my.oschina.net/U74F1zkKW/blog/369975

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值