散列 - Java 實現

此處使用分離鏈接法來解決衝突,即將散列到同一個桶的所有元素都保存到一個鏈表中。

在這裏插入圖片描述
對象必須實現 hashCode() 方法(計算哈希值)和 equals() 方法(判斷是否已經存在該元素)。

除了鏈表,也可以使用其他的數據結構來解決衝突,如,二叉搜索樹、另一個散列表 …

但是,我們期望如果散列表足夠大且散列函數可以將元素分佈均勻,那麼所有的鏈表都應該是短的,此時使用簡單的鏈表就足以。

裝填因子

定義裝填因子 λ=/\lambda = 元素總數 / 哈希表桶數 ,即每個衝突鏈表的平均長度爲 λ\lambda

  • 在一次失敗的查找中,要探查的節點數平均爲 λ\lambda :先根據哈希值定位到一個哈希桶,然後再遍歷對應的衝突鏈表(平均含 λ\lambda 個節點)。

  • 在一次成功的查找中,要探查的節點數平均爲 λ/2+1\lambda / 2 + 1:先根據哈希值定位到一個哈希桶,然後再遍歷對應的衝突鏈表,平均來看,有一半的“其他”節點被檢查到,接着就是目標節點。

由此來看,散列表的大小並不重要,裝填因子纔是重要的!

再散列

就是重新構建一個散列表,然後再重新插入每一個元素至新的散列表中。

其運行時間爲 O(N) 。

在 rehash 之前,必然已存在 N 次插入操作,因此,插入操作的時間複雜度在均攤意義上爲 O(1) 。

import java.util.LinkedList;
import java.util.List;

public class SeparateChainingHashTable<T> {
    private static final int DEFAULT_TABLE_SIZE = 101;	// 哈希表默認大小
    private List<T> [] lists;		// 鏈表:解決衝突
    private int size;				// 當前元素個數
    private float loadFactor;		// 裝填因子 = 元素總數 / 哈希表桶數,即,size / lists.length

    public SeparateChainingHashTable() {
        this(DEFAULT_TABLE_SIZE, 1.0f);
    }

    public SeparateChainingHashTable(int size, float loadFactor) {
        lists = new LinkedList[nextPrime(size)];
        for (int i = 0; i < lists.length; i++) {
            lists[i] = new LinkedList<>();
        }
        this.loadFactor = loadFactor;
    }

	// 計算元素的哈希值
    private int hash(T e) {
        int key = (e.hashCode() + lists.length) % lists.length;
        return key;
    }

	// 大於等於 value 的最小素數
    private int nextPrime(int value) {
        while (!isPrime(value)) {
            if (value >= Integer.MAX_VALUE) {
                break;
            }
            value++;
        }
        return value;
    }

    private boolean isPrime(int value) {
        int last = (int) Math.sqrt((double) value);
        for (int i = 2; i <= last; i++) {
            if (value % i == 0) {
                return false;
            }
        }
        return true;
    }

	// 是否包含元素 e
    public boolean contains(T e) {
        List<T> list = lists[hash(e)];
        return list.contains(e);
    }

	// 插入元素 e:成功時返回 true
    public boolean insert(T e) {
        List<T> list = lists[hash(e)];

        if (list.contains(e)) {
            return false;
        }

        list.add(0, e);
        size++;

        // 是否需要 rehash
        if ((int) (size * loadFactor) > lists.length) {
            rehash();
        }

        return true;
    }

	// 重哈希:桶數大致變爲之前的兩倍
    private void rehash() {
        List<T> [] oldLists = lists;
        lists = new LinkedList[nextPrime(lists.length << 1)];
        for (int i = 0; i < lists.length; i++) {
            lists[i] = new LinkedList<>();
        }

		// 重新插入:耗時
        for (int i = 0; i < oldLists.length; i++) {
            for (T e : oldLists[i]) {
                insert(e);
            }
        }
    }

	// 刪除元素 e
    public void remove(T e) {
        List<T> list = lists[hash(e)];
        if (list.contains(e)) {
            list.remove(e);
            size--;
        }
    }

	// 根據 e 的哈希值獲取對應的元素
    public T get(T e) {
        List<T> list = lists[hash(e)];
        for (int i = 0; i < list.size(); i++) {
            if (e.equals(list.get(i))) {
                return list.get(i);
            }
        }
        return null;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章