天天看點

查找算法—散清單

散清單

散清單在查找當中使用非常廣泛(本文當然也還會說明删除等其他操作)

本文主要說明散清單的兩種解決沖突的辦法

散清單查找

散清單:對于很多複雜類型的鍵值,我們可以用一個數組來實作無序的符号表,将鍵值通過散列函數轉化作為數組的索引,查找即對數組進行查找(這裡就需要解決沖突)。

散清單的實作分為兩步:

1. 散列函數:

将資料的鍵值轉化為數組的一個索引的一個函數。理想情況下,當然是所有的鍵值,均對應不同的數組索引。但是更多的是,多個不同的鍵值,通過散列函數之後,有了相同的數組索引,這時候就需要解決這些同意數組索引的不同鍵值的确定問題。

2. 解決沖突

這是對于上一步驟産生相同的數組索引問題的解決,主要有兩種解決沖突的辦法。

1>拉鍊法

2>線性探測法

散列函數

對于不同類型的資料,構造不同的散列函數,當然是最好的。

下面主要是三種常用的散列函數的構造:

  1. 平方取中法

    ——先通過求關鍵字的平方值擴大相近數的差别,然後根據表長度取中間的幾位數作為散列函數值。

    ——這種方式中間的幾位數都和關鍵字的每一位都有關,産生的散列位址較為的均勻。

  2. 折疊法

    ——将關鍵字分割成相同的幾位數(最後一位可不同),然後去這幾部分的疊加和。

    ——折疊法一般是和除留餘法一起使用的。

  3. 除留餘數法

    ——取關鍵字被某個不大于散清單表長 m 的數 p 除後所得的餘數為散列位址。即 hash(k) = k mod p, p < m。不僅可以對關鍵字直接取模,也可在折疊法、平方取中法等運算之後 取模。

    ——對 p 的選擇很重要,一般取素數或 m ,若 p 選擇不好,容易産生碰撞。

對于一些常用的資料類型:如正整數,浮點數,字元串,組合鍵等。

1>正整數:最常用的就是除留餘數法。

2>浮點數:通常是将鍵值表示為二進制,再使用除留餘數法。(Java就是這樣處理的)

3>字元串:除留餘數法,将字元串的每個字元使用charAt()轉化為char值,即是一個非負的16位整數。這種辦法確定了字元串的每個字元都能起到作用。

4>組合鍵:與處理String類似,将每個鍵值都混合起來,在進行散列函數的處理。

拉鍊法

  1. 拉鍊法原理

    将大小為M的數組中的每個索引都指向一個連結清單,這樣對于産生沖突的元素,就都存儲在了數組對應索引的連結清單中。這樣查找時,就需要先找到數組對應的索引中,再對索引中的連結清單進行順序查找,就能找到目标元素。

    查找算法—散清單
  2. Java實作(隻貼關鍵代碼)
//put(Key,Value)
        // double table size if average length of list >= 
        if (n >= *m) resize(*m);

        int i = hash(key);

        //judege key is not null
        if (!st[i].contains(key))
            n++;
        st[i].put(key, val);
           
//delete(Key)
        int i = hash(key);
        if (st[i].contains(key)) n--;
        st[i].delete(key);

        // halve table size if average length of list <= 
        if (m > INIT_CAPACITY && n <= *m) resize(m/2);
           

線性探測法

  1. 線性探測法原理

    使用大小為M的數組儲存N個鍵值對,用數組中的空位來解決沖突的辦法。

    當發生碰撞時(數組的索引已經被前面的鍵值所占有),我們直接檢查散清單的下一個位置(将索引值+K),這樣的線性探測,隻會有三種結果:

    1>命中,該位置的鍵與查找鍵相同;

    2>未命中,鍵空;

    3>未命中,鍵值不相同繼續查找,索引+K。

    如圖:

    查找算法—散清單

    其中K值不同,也分為不同的線性探測法:

    ( 1 ) d i = 1 , 2 , 3 ,——線性探測再散列;

    ( 2 ) d i = 1^2 ,- 1^2 , 2^2 ,- 2^2 , k^2, -k^2,——二次探測再散列;

    ( 3 ) d i = 僞随機序列 ,——僞随機再散列;

  2. Java實作
//put(Key,Value)
        // double table size if 50% full
        if (n >= m/) resize(*m);

        int i;
        for (i = hash(key); keys[i] != null; i = (i + ) % m) {
            if (keys[i].equals(key)) {
                vals[i] = val;
                return;
            }
        }
        keys[i] = key;
        vals[i] = val;
        n++;
           
//delete(Key)
        // find position i of key
        int i = hash(key);
        while (!key.equals(keys[i])) {
            i = (i + ) % m;
        }

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

        // rehash all keys in same cluster
        i = (i + ) % 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 + ) % m;
        }

        n--;

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