數據結構與算法之散列(分離鏈接法)

Hash,一般翻譯做“散列”,也有直接音譯爲“哈希”的,就是把任意長度的輸入(又叫做預映射, pre-image),通過散列算法,變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,所以不可能從散列值來唯一的確定輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數

爲什麼用散列
散列對插入,刪除和檢索等操作的時間複雜度可以達到O(1) 儘管散列在最差情況下仍然是O(n)

散列的組成部分

1.散列表
   散列表是一種數據結構,用以儲存關鍵字以及其關聯的值,散列表利用散列函數將關鍵字映射到其關聯的值 

2.散列函數
   散列函數用於將關鍵字裝換爲索引,理想情況下,刪了函數應該將每個可能的關鍵字映射到唯一的槽索引,但在實踐中難以實現。
   散列函數的方法
      1.直接定址法
      2.數字分析法
      3.平方取中法
       取關鍵字平方後的中間幾位爲哈希地址。 

      4.摺疊法
          將關鍵字分割成位數相同的幾部分(最後一部分的位數可以不同),然後取這幾部分的疊加和(捨去進位)作爲哈希地址,這方法稱爲摺疊法。 

      5.除留餘數法
      取關鍵字被某個不大於哈希表表長m的數p除後所得餘數爲哈希地址。
      H(key)=key MOD p (p<=m) 

      6.等等 

3.衝突
散列數組用於將每個關鍵字映射到不同的地址空間,但當無法創建一個將每個關鍵字映射到不同地址的散列函數稱爲發生了衝突,衝突是指兩個記錄儲存在相同的位置的情況

4.衝突解決的常用技術
    分離鏈式法
    平方探測法
    雙平方探測法

這裏先介紹分離鏈式法
分離鏈式法 就是將散列表結合鏈表來實現
當兩個或多個記錄散列到相同位置時,這些記錄將構成鏈表

這裏寫圖片描述

實現代碼


//分離鏈接法
/*
 * 爲執行一次查找,我們使用散列函數來確定究竟遍歷哪個鏈表
 * 然後我們在被確定的鏈表中執行一次查找。爲執行insert 我們檢查相應的鏈表看看該元素是否已經處在合適位置。
 * 
 */
public class SeparateChainingHashTable<T> {
    // 構造函數
    public SeparateChainingHashTable() {
        // 默認大小
        this(DEFAULT_TABLE_SIZE);
    }

    @SuppressWarnings("unchecked")
    public SeparateChainingHashTable(int size) {
        // 初始化鏈表數組
        linkList = new LinkedList[nextPrime(size)];
        for (int i = 0; i < linkList.length; i++) {
            linkList[i] = new LinkedList<>();
        }
    }

    // 清空哈希表
    public void makeEmpty() {
        for (int i = 0; i < linkList.length; i++) {
            linkList[i].clear();
        }
        currentSize = 0;
    }

    // 判斷哈希表中是否包含t
    public boolean contain(T t) {
        // 利用哈希計算公式計算出其哈希值,在根據哈希值找到對應的鏈表
        List<T> list = linkList[myhash(t)];
        return list.contains(t);
    }

    // 在哈希表中插入數據
    public void insert(T t) {
        List<T> list = linkList[myhash(t)];
        if (!list.contains(t)) {
            list.add(t);
            if (++currentSize > linkList.length) {
                // 超出範圍
                rehash();
            }
        }
    }

    // 在哈希表中移除數據
    public void remove(T t) {
        List<T> list = linkList[myhash(t)];
        if (list.contains(t)) {
            list.remove(t);
            currentSize--;
        }
    }

    private static final int DEFAULT_TABLE_SIZE = 11;

    private List<T>[] linkList; // 數據數組
    private int currentSize; // 數據佔據數組的大小
    // 超出數組範圍 擴大爲原來的兩倍
    //再哈希
    @SuppressWarnings("unchecked")
    private void rehash() {
        List<T>[] oldlinkList = linkList;
        linkList = new List[nextPrime(linkList.length * 2)];
        for (List<T> list : oldlinkList) {
            if (list != null && !list.isEmpty())
                for (T t : list) {
                    insert(t);
                }
        }
    }
    //哈希函數
    private int myhash(T t) {
        int hashval = t.hashCode();
        hashval %= linkList.length;
        if (hashval < 0)
            hashval += linkList.length;
        return hashval;
    }

    // 下一個素數
    private static int nextPrime(int n) {
        // 如果是偶數則加1變奇數(所有的偶數除2以外都不是素數)
        if (n % 2 == 0)
            n++;
        // 如果不是素數則加2
        for (; !isPrime(n); n += 2)
            ;

        return n;
    }

    /**
     * 是否爲素數 素數,有無限個。素數定義爲在大於1的自然數中, 除了1和它本身以外不再有其他因數的數稱爲素數。
     */
    private static boolean isPrime(int n) {
        // 2,3是素數
        if (n == 2 || n == 3)
            return true;
        // 1是特例不是素數
        // 若被偶數整除不是素數
        if (n == 1 || n % 2 == 0)
            return false;
        // 若被奇數整除則不是素數 (3,5,7,9,11,13,15,17.......)
        // 若i*i>=n 此時i已經是遍歷n的所有可能公因數
        for (int i = 3; i * i <= n; i += 2)
            if (n % i == 0)
                return false;
        // 若通過上述的判斷則爲素數。
        return true;
    }
}

下一篇 數據結構與算法之散列(平方/雙平方探測法)<八>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章