Java中實現hash算法


Hash

  Hash,一般翻譯做“散列”,也有直接音譯為“哈希”的,就是把任意長度的輸入,通過散列算法,變換成固定長度的輸出,該輸出就是散列值。根據散列值作為地址存放數據,這種轉換是一種壓縮映射,簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數。查找關鍵字數據(如K)的時候,若結構中存在和關鍵字相等的記錄,則必定在f(K)的存儲位置上。由此,不需比較便可直接取得所查記錄。我們稱這個對應關系f為散列函數(Hash function),按這個事件建立的表為散列表。

  綜上所述,根據散列函數f(key)和處理沖突的方法將一組關鍵字映象到一個有限的連續的地址集(區間)上,並以關鍵字在地址集中的“象” 作為記錄在表中的存儲位置,這種表便稱為散列表,這一映象過程稱為散列造表或散列,所得的存儲位置稱散列地址。

Hash沖突 

  對不同的關鍵字可能得到同一散列地址,即key1≠key2,而f(key1)=f(key2),這種現象稱hash沖突。即:key1通過f(key1)得到散列地址去存儲key1,同理,key2發現自己對應的散列地址已經被key1占據了。

解決辦法(總共有四種):

1.開放尋址法

  所謂的開放定址法就是一旦發生了沖突,就去尋找下一個空的散列地址,只要散列表足夠大,空的散列地址總能找到,並將記錄存入 。

  開放尋址法:Hi=(H(key) + di) MOD m,i=1,2,…,k(k<=m-1),其中H(key)為散列函數,m為散列表長,di為增量序列,可有下列三種取法:

    1). di=1,2,3,…,m-1,稱線性探測再散列;

    2). di=1^2,(-1)^2,2^2,(-2)^2,(3)^2,…,±(k)^2,(k<=m/2)稱二次探測再散列;

    3). di=偽隨機數序列,稱偽隨機探測再散列。

  用開放定址法解決沖突的做法是:當沖突發生時,使用某種探測技術(線性探測法、二次探測法(解決線性探測的堆積問題)、隨機探測法(和二次探測原理一致,不一樣的是:二次探測以定值跳躍,而隨機探測的散列地址跳躍長度是不定值))在散列表中形成一個探測序列。沿此序列逐個單元地查找,直到找到給定的關鍵字,或者碰到一個開放的地址(即該地址單元為空)為止插入即可。

  比如說,我們的關鍵字集合為{12,67,56,16,25,37,22,29,15,47,48,34},表長為12。 我們用散列函數f(key) = key mod l2

  當計算前S個數{12,67,56,16,25}時,都是沒有沖突的散列地址,直接存入:

    

  計算key = 37時,發現f(37) = 1,此時就與25所在的位置沖突。

  於是我們應用上面的公式f(37) = (f(37)+1) mod 12 = 2。於是將37存入下標為2的位置:

  

2.再哈希

  再哈希法又叫雙哈希法,有多個不同的Hash函數,當發生沖突時,使用第二個,第三個,….,等哈希函數去計算地址,直到無沖突。雖然不易發生聚集,但是增加了計算時間。

3.鏈地址法(Java hashmap就是這么做的)

  鏈地址法的基本思想是:每個哈希表節點都有一個next指針,多個哈希表節點可以用next指針構成一個單向鏈表,將所有關鍵字為同義詞的結點鏈接在同一個單鏈表中,如:

  設有 m = 5 , H(K) = K mod 5 ,關鍵字值序例 5 , 21 , 17 , 9 , 15 , 36 , 41 , 24 ,按外鏈地址法所建立的哈希表如下圖所示:  

4.建立一個公共溢出區

  這種方法的基本思想是:將哈希表分為基本表和溢出表兩部分,凡是和基本表發生沖突的元素,一律填入溢出表。

代碼實現:

  Person類:

    關於Java中的hashCode方法,詳情請見:

    https://www.cnblogs.com/dolphin0520/p/3681042.html

 

 1 package my.hash;
 2 
 3 /**
 4  * Person類
 5  * 重寫hashCode方法和equals方法
 6  * hashCode方法計算該對象的散列碼
 7  * Java中每個對象都有一個散列碼
 8  * @author ASUS
 9  *
10  */
11 
12 public class Person {
13     private String name;
14     private int age;
15     
16     //set和get方法
17     public String getName() {
18         return name;
19     }
20     public void setName(String name) {
21         this.name = name;
22     }
23     public int getAge() {
24         return age;
25     }
26     public void setAge(int age) {
27         this.age = age;
28     }
29     
30     //構造器
31     public Person(String name,int age){
32         super();
33         this.age=age;
34         this.name=name;
35     }
36     
37     //輸出方法
38     public String toString() {
39         return "Person [name="+name+",age="+age+"]";
40     }
41     
42     //重寫hashcode方法
43     public int hashCode() {
44         final int prime=31;
45         int result=1;
46         result=prime*result+age;
47         result=prime*result+((name==null)?0:name.hashCode());
48         return result;
49     }
50     
51     /*重寫equals方法
52      * 重寫該方法時必須重寫hashCode方法,因為要確保一個對象映射到同一個存儲地址
53      */
54     public boolean equals(Object object) {
55         if(this==object){
56             return true;
57         }
58         if(object==null){
59             return false;
60         }
61         if(getClass()!=object.getClass()){
62             return false;
63         }
64         Person other=(Person)object;
65         if(age!=other.age){
66             if(other.name!=null){
67                 return false;
68             }
69         }else if (!name.equals(other.name)) {
70             return false;
71         }
72         return true;
73     }
74 }

  關於該Java代碼中的getClass方法,詳情請見:

  https://blog.csdn.net/mark_to_win/article/details/38757259

  測試類:

 1 package my.hash;
 2 
 3 import java.util.HashSet;
 4 
 5 public class Test {
 6     public static void main(String[] args) {
 7         //構造6個person對象
 8         Person p1=new Person("sam", 10);
 9         Person p2=new Person("amy", 13);
10         Person p3=new Person("lili", 22);
11         Person p4=new Person("daming", 34);
12         Person p5=new Person("a", 2);
13         Person p6=new Person("b", 2);
14         
15         //輸出a和b的hashcode值
16         System.out.println("a的hashcode值:"+p5.hashCode()+"  b的hashcode值:"+p6.hashCode());
17         
18         //定義一個HashSet,將Person對象存儲在該集合中
19         HashSet<Person> set=new HashSet<Person>();
20         
21         //將Person對象添加進HashSet集合中
22         set.add(p6);
23         set.add(p5);
24         set.add(p4);
25         set.add(p3);
26         set.add(p2);
27         set.add(p1);
28         
29         //遍歷HashSet,若p5和p6的hashCode一致,但是卻添加進了集合set,說明hashset底層解決了hash沖突的問題。
30         for(Person person :set){
31             System.out.println(person);
32         }
33     }
34 }

  運行結果:

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM