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