04 Javascript資料結構與演算法 之 字典和雜湊表

zhaoyezi發表於2018-08-27

1. 定義

字典:使用[鍵,值]的形式儲存資料。儲存不重複的值。字典也稱作對映

2. 字典例項(Map)

在Es6中,已經包含了一個Map類的實現,也就是所說的字典。這裡是自己實現一個Map類。

  • 使用一個Object例項,而不是陣列來儲存元素
  • set(key, value): 像字典中新增元素
  • remove(key): 通過鍵值一處鍵值對應的資料值
  • has(key): 是否包含該key,存在返回true,否則返回false
  • get(key): 根據鍵,獲取對於的值並返回
  • clear(): 將字典中的元素全部刪除
  • size(): 獲取字典元素數量
  • keys(): 將字典包含的所有鍵以陣列的形式返回
  • values(): 將字典包含的所有值以陣列的形式返回
function Map() {
    let items = {};
    
    // 是否存在key
    this.has = (key) => (key in items);

    // 新增或更新:key,value
    this.set = (key, value) => {
        items[key] = value;
    };

    // 刪除值
    this.remove =(key) => {
        if (this.has(key))  {
            delete items[key];
            return true;
        }
        return false;
    };

    // 獲取值
    this.get = (key) => {
        return this.has(key) ? items[key] : undefined;
    };

    // 清除所有
    this.clear = () => {
        items = {};
    }

    // size(): 獲取字典元素數量
    this.size = () => {
        let length = 0;
        for(let item in items) {
            if (items.hasOwnProperty(item)) {
                length++;
            }
        }
        return length;
    };

    // 將字典包含的所有鍵以陣列的形式返回
    this.keys = () => {
        let ret = [];
        for(let item in items) {
            if (items.hasOwnProperty(item)) {
                ret.push(item);
            }
        }
        return ret;
    }

    // 將字典包含的所有值以陣列的形式返回
     this.values = () => {
        let ret = [];
        for(let item in items) {
            if (items.hasOwnProperty(item)) {
                ret.push(items[item]);
            }
        }
        return ret;
    }
} 
複製程式碼

3. HashMap 定義

HashMap 也是一種Dictionary類的一種雜湊表實現方式。
雜湊演算法: 是為了儘可能快地在資料結構中找到一個值。

3.1 雜湊函式

使用雜湊函式,可以知道值的具體位置,能夠快速地檢索到該值,雜湊函式的作用是:給定一個鍵值,然後返回值在表中的地址。
我們有一組資料,某個人的郵箱地址:

{
    Gandalf: 'gandalf@email.com',
    John: 'johnsnow@emal.com',
    Tyrion: 'tyrion@email.com'
}
複製程式碼
  • 我們使用雜湊函式,將每個鍵中的字母相加,計算出雜湊值(可以想象為地址索引)
  • 在雜湊表中:以雜湊值為儲存索引,並儲存相應的value。那麼我們在通過key進行查詢value時,就不再需要一個一個迴圈,而直接根據雜湊值獲取。

04 Javascript資料結構與演算法 之 字典和雜湊表

4. 雜湊表例項

和之前一樣,我們需要準備資料儲存物件,新增相應的方法:

  • 我們將使用陣列來表示資料結構。
  • getHashCodeKey(key): 根據key生成雜湊表的key。該方法不會暴露到外部
  • put(key, value): 向雜湊表增加一個新的項
  • remove(key): 根據鍵從雜湊表中移除值
  • get(key): 返回根據鍵值檢索的特定的值
function HashTable() {
    // 使用陣列儲存資料
    let table = [];

    // 根據key生成雜湊表的key
    function getHashCodeKey(key) {
        let hash = 0;
        for (let i = 0; i < key.length; i++) {
            hash += key.charCodeAt(i);
        }
        return hash % 37;
    }

    // 新增雜湊資料
    this.put = (key, value) => {
        let position = getHashCodeKey(key);
        console.log(position + ' - ' + key);
        table[position] = value;
    };

    // 獲取資料
    this.get = function (key) {
        return table[getHashCodeKey(key)];
    };

    // 移除
    this.remove = function(key) {
        table[getHashCodeKey(key)] = undefined;
    };
}
複製程式碼

5. 雜湊集合

雜湊集合是由一個集合構成,但是插入、移除、獲取元素時,使用的是雜湊函式。我們可以重用上面所有的程式碼,區別在於:我們新增的key值就是插入的值:[value轉化為key, value]。和集合相似,雜湊集合之儲存唯一不重複的值。

6. 雜湊表衝突

按照上面的順序,會出現相同key的情況,不同的值在雜湊表中對應相同位置的時候表示衝突了。解決該問題可用以下方法:

  • 分離連結(需要重寫get,remove,put方法):

04 Javascript資料結構與演算法 之 字典和雜湊表

function HashTable() {
    let items = [];

    // 連結串列每一個物件對應的item類
    function ValuePair(key, value) {
        this.key = key;
        this.value = value;
        this.toString = () => {
            console.log(`[${this.key} - ${this.value}]`)
        };
    }

    this.put = (key, value) => {
        let position = getHashCodeKey(key);
        if (table[position] === undefined) {
            table[position] = new LinkedList();
        }
        table[position].append(new ValuePair(key, value));
    };

    this.get = function(key) {
        var position = getHashCodeKey(key);
        if (table[position] !== undefined){ 
            //遍歷連結串列來尋找鍵/值
            var current = table[position].getHead();
            while(current.next){ 
                if (current.element.key === key){ 
                    return current.element.value; 
                }
                current = current.next; 
                }
                //檢查元素在連結串列第一個或最後一個節點的情況
                if (current.element.key === key){ 
                    return current.element.value;
                }
            }
        return undefined; 
    };
    this.remove = function(key){
        var position = getHashCodeKey(key);
        // 如果位置為空,那麼不存在該元素,返回false
        if (table[position] !== undefined){
            // 獲取該位置的連結串列的head
            var current = table[position].getHead();
            // 連結串列有不只一個item, 通過迴圈遍歷查詢後進行移除
            while(current.next){
                if (current.element.key === key){
                    table[position].remove(current.element);
                    // 刪除後,如果連結串列長度為0,則從雜湊表中移除該key
                    if (table[position].isEmpty()){
                        table[position] = undefined;
                    }
                    return true; 
                }
                current = current.next;
            }
            // 連結串列只有一個item: 檢查是否為第一個或最後一個元素
            if (current.element.key === key){ 
                table[position].remove(current.element);
                if (table[position].isEmpty()){
                    table[position] = undefined;
                }
                return true;
            }
            }
        return false; 
    };
}
複製程式碼
  • 線性探查(需要重寫get,remove,put方法)
    當想向表中某個位置加入一個新元素的時候,如果索引為index的位置已經被佔據了,就嘗試index+1的位置。如果index+1的位置也被佔據了,就嘗試index+2的位置,以此類推。

04 Javascript資料結構與演算法 之 字典和雜湊表

7. 建立更好的雜湊函式

上面的雜湊函式會產太多的衝突,應該儘量避免衝突。

var djb2HashCode = function (key) {
    // 初始化一個hash值,並賦值為一個質數
    var hash = 5381; 
    // 迭代key,將hash與33相乘,並和當前迭代到的字元的ASCII碼值相加
    for (var i = 0; i < key.length; i++) {
        hash = hash * 33 + key.charCodeAt(i);
    }
    // 最後將hash與另一個隨機質數相除的餘數
    return hash % 1013; 
    };
複製程式碼

相關文章