JavaScript數據結構與算法之 "字典和散列表"

字典

字典數據結構

  • 在字典(或映射)中,我們用[鍵,值]對的形式來存儲數據,其中鍵用來查詢特定的元素
  • 在字典中的每個鍵只能有一個值

幫助方法或類

  • 判斷元素是否存在
    const isExist = (element) => {
        return element !== undefined && element !== null;
    };
    
  • 字符串轉換函數
    /*將傳入的參數轉換爲字符串*/
    const toStr = (param) => {
        if (param === null) {
            return 'NULL';
        } else if (param === undefined) {
            return 'UNDEFINED';
        } else if (typeof param === 'number' || param instanceof Number) {
            return `${param}`;
        } else if (typeof param === 'string' || param instanceof String) {
            return `${param}`;
        } else if (typeof param === 'symbol') {
            return param;
        } else {
            return JSON.stringify(param);
        }
    };
    
    
  • 創建存儲鍵值對對象的類
    /*創建存儲鍵值對對象的類*/
    class KeyValuePair {
        constructor(key, value) {
            this.key = key;
            this.value = value;
        }
    
        toString() {
            return `[${this.key}:${this.value}]`;
        }
    }
    

創建字典數據結構

  • 字典的方法
    • set(key,value):向字典中添加新元素,如果key已經存在那麼新的值會覆蓋舊的值
    • hasKey(key):判斷某個key是否存在於字典中,存在返回true,否則返回false
    • get(key):通過特定的key查找值並返回
    • clear():刪除字典中的所有的值
    • size():返回字典中所包含值的數量。與數組的length屬性類似
    • isEmpty():判斷字典中是否有值,有返回true,否則返回false
    • keys():將字典中所有的鍵以數組的形式返回
    • values():將字典中所有的值以數組的形式返回
    • keyValues():將字典中所有的[鍵,值]以數組的形式返回
    • forEach(callback):將函數callback用於字典中的每個鍵值對。callback有兩個參數:key和value
    • toString()
  • 代碼
    /*創建字典的類*/
    class Dictionary {
        constructor() {
            this.table = {}; //存儲字典中元素的table對象
        }
    
        //  set(key,value):向字典中添加新元素,如果key已經存在那麼新的值會覆蓋舊的值
        set(key, value) {
            if (isExist(key) && isExist(value)) {
                const name = toStr(key);
                this.table[name] = new KeyValuePair(key, value);
                return true;
            }
    
            return false;
        }
    
        //  hasKey(key):判斷某個key是否存在於字典中,存在返回true,否則返回false
        hasKey(key) {
            const name = toStr(key);
            return Object.prototype.hasOwnProperty.call(this.table, name);
        }
    
        //  get(key):通過特定的key查找值並返回
        get(key) {
            const name = toStr(key);
            const result = this.table[name];
            return isExist(result) ? result.value : undefined;
        }
    
        //  clear():刪除字典中的所有的值
        clear() {
            this.table = {};
        }
    
        //  size():返回字典中所包含值的數量。與數組的length屬性類似
        size() {
            return Object.keys(this.table).length;
        }
    
        //  isEmpty():判斷字典中是否有值,有返回true,否則返回false
        isEmpty() {
            return this.size() === 0;
        }
    
        //  keys():將字典中所有的鍵以數組的形式返回
        keys() {
            return this.keyValues().map(v => v.key);
        }
    
        //  values():將字典中所有的值以數組的形式返回
        values() {
            return this.keyValues().map(v => v.value);
        }
    
        //  keyValues():將字典中所有的[鍵,值]以數組的形式返回
        keyValues() {
            return Object.values(this.table);
        }
    
        //  forEach(callback):將函數callback用於字典中的每個鍵值對。callback有兩個參數:key和value
        forEach(callback) {
            const kvs = this.keyValues();
            const len = kvs.length;
            for (let i = 0; i < len; i += 1) {
                callback(kvs[i].key, kvs[i].value);
            }
        }
    
        // toString()
        toString() {
            if (this.isEmpty()) {
                return '';
            }
    
            const kvs = this.keyValues();
            const len = kvs.length;
            let str = `${kvs[0].toString()}`;
    
            for (let i = 1; i < len; i += 1) {
                str = `${str},${kvs[i].toString()}`;
            }
    
            return str;
        }
    }
    
    

散列表

  • 散列表是字典的一種實現。
  • 散列列表可以用作關聯數組,也可以用來對數據庫進行索引。另一個很常見的應用是使用散列表來表示對象,JavaScript內部就是使用散列表來表示每個對象。
  • 散列表算法的作用是儘可能快的在數據結構中找到一個值。散列函數的作用是給定一個鍵值,然後返回值在表中的地址。

散列表數據結構的實現

  • 散列函數

    • 作用:散列函數的作用是將傳入的鍵轉換爲數值的方法
    • 一個良好的散列函數由幾個方面構成:
      • 插入和檢索元素的時間(即性能)
      • 較低的衝突性
    • djb2散列函數
      • djb2散列函數是社區最受推崇的散列函數之一
      • 實現
            _djb2HashCode(key) {
                const tableKey = toStr(key);
                let hash = 5381;
        
                for (let i = 0, len = tableKey.length; i < len; i += 1) {
                    hash = (hash * 33) + tableKey.charCodeAt(i)
                }
                
                //tableSize:散列表的大小,是一個整數,默認是 1013
                return hash % this.tableSize;
            }
        
  • 處理散列表中的衝突

    • 有時一些鍵會有相同的散列值。不同的值在散列表中對應相同位置的時候,我們稱其爲衝突。
    • 解決散列衝突的辦法:
      • 分離鏈接
      • 線性探查
      • 雙散列法
      • 我們採用線性探查來解決散列衝突的問題
    • 線性探查
      • 之所以稱做線性,是因爲它處理衝突的方法是將元素直接存儲到表中,而不是在單獨的數據結構中
      • 如果向表中某個位置添加一個新元素,如果當前位置被佔據了就嘗試使用下一個位置,如果下一個位置也被佔據了就嘗試使用下下個位置,直到在散列表中找到一個空閒位置。
  • 方法

    • _djb2HashCode(key):將傳入的key轉換爲哈希值
    • _clearDeleteSideEffect(key, removedPosition):清除刪除元素的副作用
    • put(key,value):向散列表中增加一個新的項,該方法也能用於更新散列表
    • remove(key):根據key從散列表中移除值
    • get(key):根據key檢索到特定的值
    • isEmpty():判斷散列表是否爲空,爲空返回true,否則返回false
    • size():返回散列表的大小,類似數組的length屬性
    • clear():清空散列表
    • getTable():獲取散列表中的表
    • toString():
  • 代碼

    class HashTable {
        /**
         * @param tableSize {number} 散列表的大小,默認是 1013
         */
        constructor(tableSize) {
            this.table = {};  // 存儲散列表元素的對象
            this.tableSize = (tableSize && typeof tableSize === 'number') ? tableSize : 1013;
        }
    
        // _djb2HashCode(key):將傳入的key轉換爲哈希值
        _djb2HashCode(key) {
            const tableKey = toStr(key);
            let hash = 5381;
    
            for (let i = 0, len = tableKey.length; i < len; i += 1) {
                hash = (hash * 33) + tableKey.charCodeAt(i)
            }
    
            return hash % this.tableSize;
        }
    
        // 清除刪除元素的副作用
        _clearDeleteSideEffect(key, removedPosition) {
            const hash = this._djb2HashCode(key);
            let index = removedPosition + 1;  // 散列表中被刪除元素的哈希位置的下一個位置
    
            while (isExist(this.table[index])) {
                const currentHash = this._djb2HashCode(this.table[index].key);  // 當前元素key的hash
                // 如果當前元素key的hash小於等於原始的hash值或小於等於上一個被移除元素的hash值(removePosition)
                if (currentHash <= hash || currentHash < removedPosition) {
                    this.table[removedPosition] = this.table[index];
                    delete this.table[index];
                    removedPosition = index;
                }
    
                // index移到下一個位置
                index += 1;
            }
        }
    
        // put(key,value):向散列表中增加一個新的項,該方法也能用於更新散列表
        put(key, value) {
            if (isExist(key) && isExist(value)) { // key 和value都存在
                const position = this._djb2HashCode(key); // 獲取key在散列表中的哈希位置
    
                if (!isExist(this.table[position])) { // 當前位置沒有元素
                    this.table[position] = new KeyValuePair(key, value);
                } else { // 當前位置沒有元素
                    let index = position + 1;
                    while (isExist(this.table[index])) {
                        index += 1;
                    }
                    this.table[index] = new KeyValuePair(key, value);
                }
                return true;
            }
    
            // key 或value不存在
            return false;
        }
    
        // remove(key):根據key從散列表中移除值
        remove(key) {
            const position = this._djb2HashCode(key);
            console.log(position);
            // 散列表 position位置不存在元素
            if (!isExist(this.table[position])) {
                return false;
            }
    
            // 散列表 position位置存在元素且key等於傳入的key
            if (this.table[position].key === key) {
                delete this.table[position];  // 移除元素
                this._clearDeleteSideEffect(key, position);  // 清除刪除元素的副作用
                return true;
            }
    
            let index = position + 1;
            while (isExist(this.table[index]) && this.table[index].key !== key) {
                index += 1;
            }
    
            if (isExist(this.table[index]) && this.table[index].key === key) {
                delete this.table[index];  // 移除元素
                this._clearDeleteSideEffect(key, index);  // 清除刪除元素的副作用
                return true;
            }
        }
    
        // get(key):根據key檢索到特定的值
        get(key) {
            const position = this._djb2HashCode(key);
            if (!isExist(this.table[position])) { // 散列表 position位置不存在元素
                return undefined;
            }
    
            if (this.table[position].key === key) { // 散列表 position位置存在元素且key等於傳入的key
                return this.table[position].value;
            }
    
            let index = position + 1;
            while (isExist(this.table[index]) && this.table[index].key !== key) {
                index += 1;
            }
    
            if (isExist(this.table[index]) && this.table[index].key === key) {
                return this.table[index].value;
            }
        }
    
        // isEmpty():判斷散列表是否爲空,爲空返回true,否則返回false
        isEmpty() {
            return this.size() === 0;
        }
    
        // size():返回散列表的大小,類似數組的length屬性
        size() {
            return Object.keys(this.table).length;
        }
    
        // clear():清空散列表
        clear() {
            this.table = {};
        }
    
        // getTable():獲取散列表中的表
        getTable() {
            return this.table;
        }
    
        // toString():
        toString() {
            if (this.isEmpty()) {
                return '';
            }
    
            const positions = Object.keys(this.table);
            let str = `${positions[0]} -> ${this.table[positions[0]].toString()}`;
            for (let i = 1, len = positions.length; i < len; i += 1) {
                str = `${str} , ${positions[i]} -> ${this.table[positions[i]].toString()}`;
            }
            return str;
        }
    }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章