淺談哈希表

  最近剛剛看到了阿爾法狗的大爺——阿爾法元把阿爾法狗打的不知所措。感嘆於AI的迅速發展的同時也愈加看到技術的魅力。值得咱們深刻思考的是新一代的阿爾法元,徹底靠着無師自通的左右雙手互搏術,通過屢次的訓練,而後完爆了阿爾法狗。DeepMind團隊發現了一個使人窒息的事實那就是:人類把阿爾法狗教壞了!要知道阿爾法狗是學習了人類的三千萬棋局的機器,然而它被無師自通的阿爾法元戰勝了。這帶給咱們一個更深的思考,人類活動所產生的龐大無比的數據是否還具備那麼重要的做用。在將來的發展中,咱們究竟應該如何看待人類經驗的做用。
儘管阿爾法元的能力讓人無比震撼,可是目前來講咱們所產生的大數據依舊是一筆巨大的財富。或許將來AI的全局最優能力會是主流,當下來講數據依舊是咱們的生命。(好吧好像和標題扯遠了,只是感受阿爾法元有點厲害。)那麼如此龐大的數據,你們有沒有想過它們是如何保存的呢?大數據的時代,數據結構的能量也變得愈加的驚人了。說到數據結構咱們首先可以想到就是數組和鏈表。這兩個經典的數據結構依舊有着不可動搖的地位。

數組

這應該是最最經常使用的一個結構了,無論在何時數組都有着巨大的魅力。可是數組的特色咱們也都清楚:結構簡單、操做方便。通常來講若是可以知道下標就可以很快的找到所需的元素。可是咱們想象一下這樣的場景,若是數組裏面儲存的數據是無序的並且數據量很大的話,那麼最糟糕的狀況下查找須要的時間複雜度是O(n)。查找的效率就很低。並且數組的擴展也很麻煩,須要開闢的新數組並複製一份原來的數據,資源的耗費很是大。
總的來講若是數據量小,數據存儲比較順序的話數組是很不錯的選擇。

鏈表

鏈表最大的特色就是能夠動態拓展大小。若是常常要增減數據的話,鏈表固然是不二之選。可是鏈表一樣有一個致命的缺點,那就是鏈表的查找很麻煩。極端狀況下單鏈表須要從表頭查找到末尾。
 
以上的兩種存儲方式是連續數據結構和離散數據結構的兩種表明方式。可是他們又有各自適用的場景。然而對於數據量十分龐大的數據來講,這兩種結構都是效率很低的。(想象一下QQ就知道了,假設有一億的QQ號的須要查找和添加,這兩種數據結構的效率都會很低)既然如此有沒有一種數據結構可以兼容以上兩種的優缺點,使得數據的查找和添加的時間複雜度和空間複雜度都趨於一個常數呢?答案固然是確定的,那就是——哈希表

哈希表

哈希表是一種以key和value也就是鍵值對做爲基本結構的一種存儲結構。實際上哈希表可說是一種由key到value的映射。

  這種映射的好處就是能夠把查找的時間複雜度下降到一個常數的水平。經常使用的一種哈希的實現方式就是使用一個鏈表的數組。由key對錶長取模獲得數組的下標。固然下標極可能會產生衝突。解決衝突的辦法有不少。這裏咱們使用的是鏈地址法。也就是若是下標是相同的,咱們就加到數組下標對應的鏈表中。node

咱們來設想一下極端的狀況,若是數據選的「很好」,每個key對應一個下標,那麼整個哈希表可能會變成一個數組。若是數據選的很「很差」,那麼這個哈希表就可能會變成一個單鏈表。所以咱們要注意的是:key值須要是一個比表長大得多的數字,另外表長應該爲一個質數或者說哈希函數index=key%m,中的m值須要是一個質數。這樣才能更好的發揮哈希表的特性。
爲了不因哈希表後面的鏈表過長從而下降哈希表的效率(查找的時間複雜度),所以哈希表一般會設定一個閾值,當表中的key值數量超過這個閾值的時候須要擴大數組的長度(對應上圖就是把數組長度變成18),從新計算每一個元素的index值。這個過程就叫作rehash。能夠想象rehash也是極其耗費資源的,因此咱們應該儘可能避免太屢次的rehash過程。
下面是我用Java寫的一個自定義哈希表(參考了Java Hashtable的源碼):
package com.cbs.hash;
/**
 * 定義哈希表的每個節點
 * @author CBS
 *
 */
public class Node<K,V> {
    K key;//key值
    V value;//value值
    Node<K,V> next;//下一個節點
    
    public Node(){
        
    }
    
    public Node(K k,V v){
        this.key=k;
        this.value=v;
    }
}

package com.cbs.hash;
/**
 * 自定義哈希表
 * @author CBS
 *
 */
public class MyHashTable<K,V> {
    private Node<K,V>[] table;
    private int count=0;//哈希表長度
    private final float f=0.75f;//裝載因子
    private int index;//數組下標
    private int hash;//哈希值
    
    //初始化數組長度
    @SuppressWarnings("unchecked")
    public MyHashTable(){
        table=new Node[9];
    }
    /**
     * 添加方法
     * @param key
     * @param value
     */
    public void put(K key,V value){
        hash=key.hashCode();//獲取哈希值
        index = (hash & 0x7FFFFFFF) % table.length;//獲取下標值
        
        Node<K,V> node=table[index];
        Node<K,V> tem=node;
        //若是節點已經存在,更新value值
        for(;tem!=null;tem=tem.next){
            if(hash==tem.key.hashCode() && tem.key.equals(key)){
                tem.value=value;
                return;
            }
        }
        Node<K,V> newNode=new Node<K, V>(key,value);
        //在鏈表的表頭插入
        table[index]=newNode;
        newNode.next=node;
        count++;
    }
    /**
     * 查找指定key值下的節點
     * @param k
     * @return key值對應的節點對象
     */
    public Node<K, V> find(K k){
        hash=k.hashCode();
        int index = (hash & 0x7FFFFFFF) % table.length;
        
        Node<K,V> node=table[index];
        Node<K,V> tem=node;
        for(;tem!=null;tem=tem.next){
            if(hash==tem.key.hashCode() && tem.key.equals(k)){
                return tem;
            }
        }
        return null;
    }
    /**
     * 更新key值對應的value值
     * @param k
     * @param v
     */
    public void update(K k,V v){
        Node<K,V> node=find(k);
        node.value=v;
    }
    /**
     * 刪除指定key值的節點
     * @param k
     */
    public void delete(K k){
        hash=k.hashCode();
        int index = (hash & 0x7FFFFFFF) % table.length;
        
        Node<K,V> node=table[index];
        Node<K,V> tem=node;
        for(;tem!=null;tem=tem.next){
            if(hash==tem.next.key.hashCode() && tem.next.key.equals(k)){
                break;
            }
        }
        
        Node<K,V> nextNode=find(k);
        tem.next=nextNode.next;
        count--;
    }
    /**
     * 獲取key值節點下面的value值
     * @param k
     * @return    V類型的值
     */
    public V get(K k){
        Node<K,V> Node=find(k);
        if(Node!=null){
            return (V)Node.value;
        }
        return null;
    }
    
    public int size(){
        return count;
    }
    
}
View Code
我的能力問題,尚未想出來rehash應該怎麼寫。另外解釋一下hashCode方法。這個方法是Object類中的一個方法,也就是Java中全部的類都有這個方法。它是返回一個int值,這個int值惟一標識了一個對象。它一方面是爲了便於對象的equals方法的重寫,一方面是便於Java中Hashtable的使用。簡單的來講就是:在一個程序中,一個對象只有一個惟一的hashCode。
 
PS:以上純屬我的理解,若有錯誤,歡迎批評指正。
相關文章
相關標籤/搜索