拉鏈法和線性探測法


散列函數

  1. 正整數
    除留余數法,選擇大小為素數M的數組,對於任意正整數k ,計算k除以M的余數。
    如果M不是素數,我們可能無法利用鍵中包含的所有信息,這可能導致我們無法均勻地散列散列值

  2. 浮點數
    第一,如果鍵是0-1的實數,我們可以將它乘 M 並四舍五入得到一個0~M-1 之間的索引,有缺陷,高位起的作用更大,最低位對散列值得結果沒影響。
    第二,將鍵表示為二進制,然后試用版除留余數法。

  3. 字符串
    基本原理也是除留余數法,Horner方法
    int hash = 0;
    for(int i = 0 ; i<s.length(); i++){
    hash = (R * hash +s.charAt(i)) % M;
    }
    //R代表進制

  4. 組合鍵
    eg ,年月日
    int hash = (((day * R +month) % M) * R +year) % M;

  5. Java約定
    第一,所有數據類型都集成了一個能返回一個32bit整數的hashCode()方法。
    第二,每一種數據類型的hashCode()都必須和equals()一致
    第三,a.equals(b) 為true a.hashCode()= b.hashCode(); a,b的hashCode相同,並不一樣是一個對象,需要用equal判斷是否為同一對象。

  6. hashCode()的返回值轉化為索引值
    hashCode()返回32位整數,而我們需要的是 0~M-1索引。轉化方法如下

private int hash(Key x){
    return (x.hashCode() & 0x7fffffff) % M;
}
//x.hashCode() & 0xfffffff將符號位屏蔽,然后除留余數法計算。

  1. hashCode()
public class Transaction{
    private final String who;
    private final Date when;
    private final double amount;
    
    public int hashCode(){
        int hash  = 17;
        hash = 31 * hash + who.hashCode();
        hash = 31 * hash + when.hashCode();
       hash = 31 * hash + ammount.hashCode();
       return hash;
    }
}
  1. 軟緩存
    如果散列值得計算很耗時,那么我們或許可以將每個鍵的散列值保存起來,即在每個鍵中使用一個hash變量保存它的hashCode()返回值
  2. 優秀的散列方法的三個條件
    第一,一致性 等價的鍵必然產生相等的散列值
    第二,高效性,計算簡便
    第三,均勻地散列所有鍵

基於拉鏈法的散列表

定義:碰撞處理是指處理兩個或多個鍵的散列值相同的情況。一種直接的辦法是將大小為M的數組中的每個元素指向一條鏈表,鏈表中每個結點都存儲了
散列值為該元素索引的鍵值對。這種方法稱為拉鏈法。
讓M足夠大,這樣每個鏈表的長度就越短。
查找分兩步:根據散列值找到鏈表,然后沿着鏈表查找相應的鍵。
在一張含有M條鏈表 和N個鍵的散列表中,未命中查找和插入操作所需比較的次數為 ~N/M

基於線性探測法的散列表

另外一種,用大小M的數組保存N個鍵值對,其中 M > N。我們需要依靠數組中的空位解決碰撞沖突。基於這種策略的所有方法統稱為 開放地址散列表
開放地址散列表最簡單的方法叫做 線性探測法
定義:當碰撞發生時,我們直接檢查散列表中的下一個位置。這樣的線性探測有三種結果
第一,命中,該位置的鍵和被查找的鍵相同
第二,未命中,鍵為空(該位置沒有鍵)
第三,繼續查找,該位置的鍵和被查找的鍵不同

基於線性探測法的散列表:
插入思路:
第一:計算key的散列表,找到相應的位置
第二:查看這個位置的值是否為要插入的key。不是的話比較下一個位置 ,直到當前位置的鍵值為空。
第三:如果在遇到空鍵值前遇到相等的鍵,則進行更新操作;如果直到遇到空值也沒遇到相同的鍵,在空鍵值位置插入相應鍵值。

public class LinearProbingHashST<Key, Value> {
    private static final int INIT_CAPACITY = 4;

    private int n;           // number of key-value pairs in the symbol table
    private int m;           // size of linear probing table
    private Key[] keys;      // the keys
    private Value[] vals;    // the values

    public LinearProbingHashST() {
        this(INIT_CAPACITY);
    }
    public LinearProbingHashST(int capacity) {
        m = capacity;
        n = 0;
        keys = (Key[])   new Object[m];
        vals = (Value[]) new Object[m];
    }

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

    // resizes the hash table to the given capacity by re-hashing all of the keys
    private void resize(int capacity) {
        LinearProbingHashST<Key, Value> temp = new LinearProbingHashST<Key, Value>(capacity);
        for (int i = 0; i < m; i++) {
            if (keys[i] != null) {
                temp.put(keys[i], vals[i]);
            }
        }
        keys = temp.keys;
        vals = temp.vals;
        m    = temp.m;
    }
    public void put(Key key, Value val) {
        if (key == null) throw new IllegalArgumentException("first argument to put() is null");
        if (val == null) {
            delete(key);
            return;
        }
        // double table size if 50% full
        if (n >= m/2) resize(2*m);

        int i;
        for (i = hash(key); keys[i] != null; i = (i + 1) % m) {
            if (keys[i].equals(key)) {
                vals[i] = val;
                return;
            }
        }
        keys[i] = key;
        vals[i] = val;
        n++;
    }
    public Value get(Key key) {
        if (key == null) throw new IllegalArgumentException("argument to get() is null");
        for (int i = hash(key); keys[i] != null; i = (i + 1) % m)
            if (keys[i].equals(key))
                return vals[i];
        return null;
    }
}

刪除操作
鍵簇: 元素在插入數組后集聚而成的一組連續的條目。
我們需要將簇中被刪除鍵的右側的所有鍵重新插入到散列表

    public void delete(Key key) {
        if (key == null) throw new IllegalArgumentException("argument to delete() is null");
        if (!contains(key)) return;

        // find position i of key
        int i = hash(key);
        while (!key.equals(keys[i])) {
            i = (i + 1) % m;
        }

        // delete key and associated value
        keys[i] = null;
        vals[i] = null;

        // rehash all keys in same cluster
        i = (i + 1) % m;
        while (keys[i] != null) {
            // delete keys[i] an vals[i] and reinsert
            Key   keyToRehash = keys[i];
            Value valToRehash = vals[i];
            keys[i] = null;
            vals[i] = null;
            n--;
            put(keyToRehash, valToRehash);
            i = (i + 1) % m;
        }

        n--;

        // halves size of array if it's 12.5% full or less
        if (n > 0 && n <= m/8) resize(m/2);

        assert check();
    }

當 散列表快滿的時候查找所需要的探測次數是巨大的,但當使用率 a小於 1/2 是探測的預計次數在1.5~2.5之間。

調整數組的大小
第一,基於線性探測法的數組大小調整
把原表中所有的鍵重新散列並插入到新表

    private void resize(int capacity) {
        LinearProbingHashST<Key, Value> temp = new LinearProbingHashST<Key, Value>(capacity);
        for (int i = 0; i < m; i++) {
            if (keys[i] != null) {
                temp.put(keys[i], vals[i]);
            }
        }
        keys = temp.keys;
        vals = temp.vals;
        m    = temp.m;
    }

第二,拉鏈法的數組大小調整

內存使用
除了存儲鍵和值所需要的空間外,我們實現拉鏈法 SeparateChainingHashST 保存了M個SeparateChainingHashST
對象和它們的引用。每個對象需要16字節,每個引用需要8字節。另外還有N個對象,每個對象需要24字節以及三個引用。
在使用率 1/81/2情況下,線性探測使用4N16N個引用。
方法 N個元素所需的內存
拉鏈法 48N + 32M
線性探測法 32N ~ 128N
二叉查找樹 56N


免責聲明!

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



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