转载

哈希哈希

前言:

哈希表(Hash Table)也叫散列表,是一种用于快速存取的数据结构。其内部实现是通过把键(key)码映射到表中的一个位置来访问记录,其中的“映射”也就是哈希函数,而“表”即哈希表。本文将重点介绍实现哈希表的2种方法:拉链法和线性探测法。

1.实验数据

A 2

C 1

B 6

B 11

H 1

J 3

数据说明:为了跟前面博文保持一致,本文的数据依然采用键值对(Key-Value)的形式。哈希表中主要有"存"和"取"操作。前者为:void put(Key key,Value);后者为: Value get(Key key);执行put操作时,如果数据已经存在则更新为新数据。

2.HashMap实现

实现哈希表主要分以下两步:

step1:定义哈希函数

哈希函数的实现不唯一,在此我们以java自带的hashCode()为基础进行修改。(对hashCode()底层实现感兴趣的朋友可另找资料进行了解~)

public int hash(Key key){         return (key.hashCode() & 0x7fffffff)%M;     }

step2:解决哈希冲突

当出现 hash(k1)== hash(k2)时,即产生冲突。

解决方法一(拉链法):因为哈希值相等,我们可以将k1,k2利用链表 st 进行存储。即,凡是hash(x)相等的x都存入同一链表。当要进行查找操作时,先利用hash(x)定位到链表st[hash(x)],再通过顺序链表遍历进行查找。

解决方法二(线性探测法):利用两张足够大的哈希表 keys[M]和Values[M](其中,M>=2*N),当出现冲突之后,顺势循环向下遍历查找还没有存放 key 的keys[hash(key)],直到找到keys[hash(key)]==null为止。

------------

拉链法与线性探测法的存储比较:

哈希哈希

3.Java代码实现

a.拉链法code:

 1 package com.gdufe.hash;  2   3 import java.io.File;  4 import java.util.Scanner;  5   6 public class SequenceChain<Key,Value>{  7   8     private int N;  9     private int M; 10     private SequentialSearch<Key, Value> []st; 11      12     public SequenceChain() { 13         this(997); 14     } 15     String s; 16     public SequenceChain(int M){ 17         this.M = M;        //赋值M 18         st = (SequentialSearch<Key,Value>[]) new SequentialSearch[M]; 19         for(int i=0;i<M;i++) 20             st[i] = new SequentialSearch();    //初始化 21     } 22      23     public int hash(Key key){    //自定义哈希,防止哈希值为负 24         return (key.hashCode() & 0x7fffffff) % M;    25     } 26     public void put(Key key,Value value){ 27         st[hash(key)].put(key, value); 28     } 29     public Value get(Key key){ 30         return st[hash(key)].get(key); 31     } 32      33     public static void main(String[] args) throws Exception{ 34         Scanner input = new Scanner(new File("data_hash.txt")); 35         SequenceChain<String, Integer> sequence = new SequenceChain<String, Integer>(); 36         while(input.hasNext()){ 37             String key = input.next(); 38             int value = input.nextInt(); 39             sequence.put(key, value); 40         } 41         input.close();    //关闭控制台输入 42         System.out.println("-----output----"); 43         System.out.println(sequence.get("H")); 44         System.out.println(sequence.get("B")); 45          46     } 47  48 }

辅助数据结构---顺序链表code:

 1 package com.gdufe.hash;  2   3 import java.io.File;  4 import java.util.Scanner;  5   6 public class SequentialSearch<Key,Value> {  7       8     private Node root;    //顺序链表的根root  9     class Node{ 10         Key key; 11         Value value; 12         Node next; 13         public Node(Key key,Value value){ 14             this.key=key; 15             this.value=value; 16         } 17     } 18     //在表头新增结点 19     public void put(Key key,Value value){ 20         if(get(key)!=null){            //更新即可 21             for(Node x=root;x!=null;x=x.next){ 22                 if(x.key.equals(key)){ 23                     x.value=value; 24                     return; 25                 } 26             } 27         } 28         Node h = new Node(key,value); 29         h.next=root; 30         root = h; 31     } 32      33     public Value get(Key key){ 34         for(Node x=root;x!=null;x=x.next){ 35             if(x.key.equals(key)){ 36                 return x.value; 37             } 38         } 39         return null; 40     } 41      42 }    

b.线性探测法code:

 1 package com.gdufe.hash;  2   3 import java.io.File;  4 import java.util.Scanner;  5   6 public class LinearIndex<Key,Value> {  7       8     int N=0;  9     int M=16;    //初始大小 10     Key[] keys; 11     Value[] values; 12      13     public LinearIndex() {  14         keys = (Key[]) new Object[M];   //强制转型 15         values = (Value[]) new Object[M]; 16     } 17     public void put(Key key,Value value){ 18         if(N*2>=M) resize();  //扩大哈希表 19         int i; 20         for(i=hash(key);keys[i]!=null;i=(i+1)%M){ 21             if(keys[i].equals(key)){  //    更新value 22                 values[i]=value; 23                 return;         24             } 25         } 26         keys[i]=key; 27         values[i]=value; 28         N++; 29     } 30      31     private void resize() {        //扩大一倍 32         Key[] keyTemp = (Key[]) new Object[2*keys.length]; 33         System.arraycopy(keys, 0, keyTemp, 0, keys.length); 34         keys = keyTemp; 35         Value[] valueTemp = (Value[]) new Object[2*values.length]; 36         System.arraycopy(values, 0, valueTemp, 0, values.length); 37         values = valueTemp; 38          39         M = M*2;    //shouldn't be neglected! 40     } 41     public Value get(Key key){ 42         for(int i=hash(key);keys[i]!=null;i=(i+1)%M){ 43             if(keys[i].equals(key)) 44                 return values[i]; 45         } 46         return null; 47     } 48     public int hash(Key key){ 49         return (key.hashCode() & 0x7fffffff)%M; 50     } 51      52     public static void main(String[] args) throws Exception { 53         Scanner input = new Scanner(new File("data_BST.txt")); 54         LinearIndex<String, Integer> li = new LinearIndex<String, Integer>(); 55         while(input.hasNext()){ 56             String key = input.next(); 57             int value = input.nextInt(); 58             li.put(key, value); 59         } 60         input.close(); 61         System.out.println("-----output----"); 62         System.out.println(li.get("H")); 63         System.out.println(li.get("B")); 64     } 65 }

4.测试结果

-----output---- 1 11

注意:

(1)Java底层实现HashMap采用的是上述介绍的拉链法。关于HashMap的底层实现在Java面试中是面试官喜欢问的问题之一。

(2)关于拉链法采用的辅助结构为什么选择顺序链表而不采用高效的“二叉查找树“是因为,当哈希表较大而每张链表存储的数据不多时,顺序链表的效率反而更高一些。

结语:

同之前介绍的红黑树一样,哈希表也是一种高效的存储于查找的数据结构,特别适用于大数据的场合。至于在何时使用哈希表何时使用红黑树这个不一而论。因为,存储的效率还更数据本身相关。不过,由于哈希一向擅长处理跟字符串相关的存储,所以对于大量的字符串存储与查找可以优先考虑哈希表。

正文到此结束
Loading...