字典
字典數據結構
- 在字典(或映射)中,我們用[鍵,值]對的形式來存儲數據,其中鍵用來查詢特定的元素
- 在字典中的每個鍵只能有一個值
幫助方法或類
- 判斷元素是否存在
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; } }