哈希詳解

一、導引

考慮下面這樣一個例子:如果數據中存在重復的元素,請給出輸出第一個重復字符的算法。最簡單最直接的辦法當然是:在給定的字符串中,檢查每個字符是否重復。這種方法的時間復雜度為O(n^2)。

現在給出一個更好的解決方案:我們知道可能的字符集合大小是256(為了簡單期起見,假設只有ASCII字符)。由此創建一個長度為256的數組,并將其所有元素初始化為0。將每個輸入字符放到該數組對應的位置,并增加其計數。由于使用的是數組,所以它僅需要常數時間就可以到達任意指定的位置。當掃描輸入字符時,若得到了一個計數器值已經是1的字符,則可以認為該字符就是第一個重復的字符。

char FirstRepeatedChar(char[] str){
        int count[256];
        for(int i=0;i<256;++i)
             count[i] = 0;
        for(int i=0;i<str.length;++i){
            if(count[str[i]]==1){
                System.out.println(str[i]);
                break;
            }
        }
        if(i==len)
          System.out.println("No Repeated Characters");
        return 0;
}

但是如果我們給定的數組是數字而不是字符,那么關鍵字的取值范圍將為無窮大(字符的個數是256是已知的,但是數字的范圍是未知的)。使用簡單的數組來解決那些關鍵字取值范圍巨大的問題并不是一個正確的選擇。所以要想解決這個問題,則需要以某種方式將所有這些可能的關鍵字映射到可能的內存位置,將關鍵字映射到存儲位置的過程成為散列

二、散列表的簡介

Java中最底層的數據存儲方式有兩種,一種是數組,另一種就是鏈表。數組查詢速度快,但是增加和刪除元素速度慢;鏈表則正好相反。有沒有一種數據結構能夠綜合一下數組和鏈表,以便發揮它們各自的優勢呢?答案就是散列表散列表也叫作哈希表,哈希表有較快的查詢速度,以及較快的增刪速度,所以很適合在海量數據的環境中使用

一般實現哈希表的方法采用“拉鏈法”,我們可以理解為“鏈表的數組”,如下圖:

散列表

從上圖我們可以發現哈希表是由數組+鏈表組成的。當我們面臨較少的存儲位置和較多可能的關鍵字時,僅利用簡單數組是沒有足夠的內存空間的。一種解決方案就是使用散列表。散列表是一種數據結構,利用散列函數將關鍵字映射到其關聯的值。

三、散列函數

一個好的散列函數應該具有以下特點:

  • 最大限度地減少沖突
  • 簡單并快速計算
  • 將鍵值在散列表中均勻分布
  • 能使用關鍵字提供的所有信息
  • 對一組給定關鍵字具有一個高負載因子

其中負載因子=散列表中元素的個數 / 散列表的長度,此參數指出了散列函數是否將關鍵字均勻分布

四、沖突

沖突是指兩個記錄存儲在相同位置的情況。目前解決沖突最常用的是直接鏈接法和開放定址法。

  • 直接鏈接法:鏈表數組的應用
    ??分離鏈接法
  • 開放定址法:基于數組實現
    ??線性探測法(線性搜索)
    ??二次探測法(非線性搜索)
    ??雙重散列法(使用兩個散列函數)

(1)分離鏈接法

基于鏈接法的沖突解決方案是將散列表與鏈表形式結合起來實現的。當兩個或多個記錄散列到相同的位置時,這些記錄將構成一個單項鏈表。

分離鏈接法

(2)開放定址法

這種方法是通過探測來解決沖突的。

1.線性探測法

探測間隔為固定值1。在線性探測中,從發生沖突的原始位置按順序搜索散列表,如果表中的某個位置被占據,則查找下一個位置。必要時,還可以從表的最后一個位置循環到表的第一個位置進行搜索。用于再次散列的函數如下:

rehash(key) = (n+1)%tablesize

線性探測的一個問題是,表項往往在散列表中聚集,集散列表包含一組連續的被占據的位置,這一現象稱為聚集。因此,散列表中的某部分可能相當密集,即使另一部分元素相對較少。因此聚集會導致較長的探測搜索,從而降低整體效率。

2.二次探測法

探測間隔的增加與散列值成正比(因此間隔線性地增加,索引值由一個二次函數描述)。在二次探測中,從發生沖突的初始位置i開始,如果某個位置被占據,則探測i+1^2、i+2 ^2 、i+3^2、i+4 ^2等位置。如果有必要,將從表的最后一個位置循環到表的第一個位置進行探測。再次散列的函數如下:

rehash(key) = (n+k^2)%tablesize

【例子】

表長是11(0..10);散列函數:h(key) = key mod 11

31 mod 11 = 9

19 mod 11 = 8

2 mod 11 = 2

13 mod 11 = 2 → (2+1^2)mod 11 = 3

25 mod 11 = 3 → (3+1^2)mod 11 = 4

24 mod 11 = 2 → (2+1^2)mod 11 , (2+2^2)mod 11 = 6

聚集問題可以使用二次探測方法消除。但是還是存在出現聚集的可能。聚集是由多個關鍵字映射到同一個散列值引起的,所以與這些關鍵字相關的探測序列將隨著重復沖突的出現而被延長。

3.雙重散列法

探測間隔由另一個散列函數計算生成,雙重散列法用一種更好的方式減少了聚集。由于探測序列的增量使用第二個散列函數計算,所以第二個散列函數h2應遵循:

h2(key)≠0且h2≠h1

算法首先探測位置h1(key)。若該位置已被占據,那么繼續探測位置h1(key)+h2(key),h1(key)+2×h2(key)....。

【例子】

表長是11(0..10)

散列函數:假設h1(key) = key mod 11、h2(key) =7- (key mod 7)

插入關鍵字:

58 mod 11 = 3

14 mod 11 = 3 → (3+7) mod 11 =10

91 mod 11 = 3 → (3+7) mod 11 , (3+2×7) mod 11=6

25 mod 11 = 3 → (3+3) mod 11 , (3+2×3) mod 11 = 9

五、哈希的應用

Java的每個類都有hashCode方法,hashCode方法返回該對象的哈希碼值。

那么為什么對象都要有hashCode方法呢?考慮一種情況,當向集合中插入對象時,如何判別在集合中是否已經存在該對象了?也許大多數人都會想到調用equals方法來逐個進行比較,這個方法確實可行。但是如果集合中已經存在一萬條數據或者更多的數據,如果采用equals方法去逐一比較,效率必然是一個問題。此時hashCode方法的作用就體現出來了,當集合要添加新的對象時,先調用這個對象的hashCode方法,得到對應的hashcode值來進行重復的判斷。所以,之所以有hashCode方法,是因為在批量的對象比較中,hashCode要比equals來得快

兩個對象equals相等那么hashcode是一定相等的;equals不相等hashcode可能相等可以不相等。因為hashCode說白了是地址值經過一系列的復雜運算得到的結果,而Object中的equals方法底層比較的就是地址值,所以equals()相等,hashCode必定相等;equals()不等,在java底層進行哈希運算的時候有一定的幾率出現相等的hashCode,所以hashCode可等可不等。

如果你重寫了equals()方法,那么一定要記得重寫hashCode()方法。重寫的原則就是按照equals( )中比較兩個對象是否一致的條件用到的屬性來重寫hashCode()

  // HASH
    @Override
    public int hashCode()
    {
        int hash = 17;
        //根據id生成hashCode
        if (this.id != null)
            hash = hash * 31 + this.id.hashCode();

        return(hash);
    }

    @Override
    public boolean equals(Object o)
    {
        if (this == o)
            return(true);
        //利用getClass()獲得當前對象的類型
        if (o==null || !this.getClass().equals(o.getClass()))
            return(false);

        C c = (C)o;
        //通過id判斷兩個對象是否相等
        return(
            (this.id == c.id) ||
            (this.id != null && this.id.equals(c.id))
        );
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容