小橙書閱讀指南(十一)——散列表

算法描述:散列表是一種在時間和空間上作出權衡的查找算法。使用查找算法分爲兩步。第一步是經過散列函數將被查找的鍵轉化未數組的一個索引。理想狀況下,不一樣的鍵都能轉爲不一樣的索引值。固然,這只是理想狀況,因此咱們須要面對兩個或多個鍵都被散列到相同索引值的狀況。所以,散列查找的第二部就是處理碰撞衝突的過程。java

一個比較使人滿意的散列函數可以均勻並獨立地將全部鍵散佈於0到M-1之間。git

1、基於拉鍊法的散列表算法

算法圖示:數組

拉鍊散列表算法的本質是將哈希值相同的鍵保存在一個普通鏈表中,當咱們須要調整數組長度的時候,須要將全部鍵在新的數組中從新散列。函數

代碼示例:this

import java.util.ArrayList;
import java.util.List;

public class SeparateChainingHashSymbolTable<Key, Value> {
    private int initCapacity; // 初始散列數組的長度
    private int size; // 鍵值總數
    private int len; // 散列數組的長度
    private SequentialSearchSymbolTable<Key, Value>[] st; // 散列數組

    public SeparateChainingHashSymbolTable(int len) {
        this.len = len;
        this.initCapacity = len;
        st = (SequentialSearchSymbolTable<Key, Value>[]) new SequentialSearchSymbolTable[len];

        for (int i = 0; i < len; i++) {
            st[i] = new SequentialSearchSymbolTable<>();
        }
    }

    public Value get(Key key) {
        int h = hash(key);
        return st[h].get(key);
    }

    public boolean contains(Key key) {
        return get(key) != null;
    }

    public void put(Key key, Value val) {
        // 當包含元素的數量大於散列數組長度10倍時,擴展容量
        if (size > 10 * len) {
            resize(2 * len);
        }
        int h = hash(key);
        if (!contains(key)) {
            size++;
        }
        st[h].put(key, val);
    }

    public void delete(Key key) {
        int h = hash(key);
        if (contains(key)) {
            st[h].delete(key);
            size--;
        }

        if (size > initCapacity && size <= 2 * len) {
            resize(len / 2);
        }
    }

    public Iterable<Key> keys() {
        List<Key> keys = new ArrayList<>();
        for (int i = 0; i < len; i++) {
            for (Key key : st[i].keys()) {
                keys.add(key);
            }
        }
        return keys;
    }


    private void resize(int capacity) {
        SeparateChainingHashSymbolTable<Key, Value> nst = new SeparateChainingHashSymbolTable<>(capacity);
        // 遍歷原先散列表中保存的元素,並從新散列進新的散列表
        for (int i = 0; i < len; i++) {
            for (Key key : st[i].keys()) {
                nst.put(key, st[i].get(key));
            }
        }

        this.size = nst.size;
        this.len = nst.len;
        this.st = nst.st;
    }

    /**
     * 散列算法
     *
     * @param key
     * @return
     */
    private int hash(Key key) {
        return (key.hashCode() & 0x7fffffff) % len;
    }
}

2、基於探測法的散列表spa

這種算法在處理碰撞的時候並不是將全部相同哈希鍵的對象保存在一條鏈表中而是沿數組向後查找並插入到一個空槽中。3d

算法圖示:code

探測法哈希算法的一個比較重要的特徵是:當咱們須要刪除一個鍵的時候,不能僅僅將數組中對應的位置設置未null,由於這會使得在此位置以後的元素沒法被查找。所以,咱們須要將簇中被刪除的鍵的右側的全部鍵從新散列計算並插入散列表。這個過程會比較複雜。對象

代碼示例:

import java.util.ArrayList;
import java.util.List;

public class LinearProbingHashSymbolTable<Key, Value> {
    private int size;
    private int len;
    private Key[] keys;
    private Value[] vals;

    public LinearProbingHashSymbolTable(int capacity) {
        len = capacity;
        size = 0;
        keys = (Key[]) new Object[capacity];
        vals = (Value[]) new Object[capacity];
    }

    public void put(Key key, Value val) {
        // 始終保證元素數量只佔數組長度的50%
        if (size > len / 2) {
            resize(2 * len);
        }
        // 線性碰撞檢測
        int h;
        for (h = hash(key); keys[h] != null; h = (h + 1) % len) {
            if (key.equals(keys[h])) {
                vals[h] = val;
                return;
            }
        }
        keys[h] = key;
        vals[h] = val;
        size++;
    }

    public Value get(Key key) {
        for (int h = hash(key); keys[h] != null; h = (h + 1) % len) {
            if (key.equals(keys[h])) {
                return vals[h];
            }
        }
        return null;
    }

    public boolean contains(Key key) {
        return get(key) != null;
    }

    public void delete(Key key) {
        if (!contains(key)) {
            return;
        }

        int h = hash(key);
        while (!keys[h++].equals(key)) {
            h = h % len;
        }
        keys[h] = null;
        vals[h] = null;

        // 因爲在刪除了一個鍵以後可能形成查詢的不連續,所以須要對一些鍵從新散列
        h = (h + 1) % len;
        while (keys[h] != null) { // 在被刪除的鍵後至空鍵前的全部鍵從新散列保存
            Key nkey = keys[h];
            Value nval = vals[h];
            keys[h] = null;
            vals[h] = null;
            put(nkey, nval);
            h = (h + 1) % len;
            // 每次循環size--的目的時抵消put中的size++
            size--;
        }
        size--;
        // 當包含的元素數量小於數組長度的12.5%時,按照1/2的比例收縮數組
        if (size > 0 && size < len / 8) {
            resize(len / 2);
        }
    }

    public Iterable<Key> keys() {
        List<Key> keyList = new ArrayList<>();
        for (int i = 0; i < len; i++) {
            if (keys[i] != null) {
                keyList.add(keys[i]);
            }
        }
        return keyList;
    }

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

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

        this.size = nst.size;
        this.keys = nst.keys;
        this.vals = nst.vals;
    }
}

 

相關連接:

Algorithms for Java

Algorithms for Qt

相關文章
相關標籤/搜索