數(shù)據(jù)結(jié)構(gòu)之字典和散列表

學(xué)習(xí)github地址

今天來(lái)學(xué)習(xí)使用字典和散列表來(lái)存儲(chǔ)唯一值(不重復(fù)的值)的數(shù)據(jù)結(jié)構(gòu)

字典

在字典中申屹,存儲(chǔ)的是[鍵衡查,值]對(duì)兆旬,其中鍵名是用來(lái)查詢特定元素的, 字典也稱作映射、符號(hào)表或關(guān)聯(lián)數(shù)組, 在計(jì)算機(jī)科學(xué)中, 字典經(jīng)常用來(lái)保存對(duì)象的引用地址

  1. 創(chuàng)建字典類

與 Set 類相似朵栖,ECMAScript6 同樣包含了一個(gè) Map 類的實(shí)現(xiàn),即我們所說(shuō)的字典

今天要實(shí)現(xiàn)的類就是以 ES6 中 Map 類的實(shí)現(xiàn)為基礎(chǔ)的柴梆。你會(huì)發(fā)現(xiàn)它和 Set 類很相似陨溅,但不同于存儲(chǔ)[值,值]對(duì)的形式绍在,我們將要存儲(chǔ)的是[鍵门扇,值]對(duì)

在字典中,理想的情況是用字符串作為鍵名揣苏,值可以是任何類型, 但是悯嗓,由于 JavaScript 不是強(qiáng)類型的語(yǔ)言,我們不能保證鍵一定是字符串卸察。我們需要把所有作為鍵名傳入的對(duì)象轉(zhuǎn)化為字符串

// 轉(zhuǎn)化為字符串
const defaultToString = (item) => {
  if (item === null) {
    return "NULL";
  } else if (item === undefined) {
    return "UNDEFINED";
  } else if (typeof item === "string" || item instanceof String) {
    return `${item}`;
  }

  return item.toString;
};
// 打一個(gè)骨架
class Dictionary {
  constructor(toStrFn = defaultToString) {
    this.toStrFn = toStrFn;
    this.table = {};
  }
}
// 不是只將 value 保存在字典中脯厨,而是要保存兩個(gè)值:原始的key 和 value
class ValuePair {
  constructor(key, value) {
    this.key = key;
    this.value = value;
  }
  toString() {
    return `[#${this.key}: ${this.value}]`;
  }
}
  1. 聲明一些字典所使用的方法

    • set(key,value):向字典中添加新元素。如果 key 已經(jīng)存在坑质,那么已存在的 value 會(huì)被新的值覆蓋
    • remove(key):通過(guò)使用鍵值作為參數(shù)來(lái)從字典中移除鍵值對(duì)應(yīng)的數(shù)據(jù)值
    • hasKey(key):如果某個(gè)鍵存在與字典中, 返回 true, 否則返回 false
    • get(key):通過(guò)鍵值為參數(shù)查找特定的值返回
    • clear():刪除字典中的所有值
    • size():返回字典包含值的數(shù)量
    • isEmpty():在 size 等于 0 的時(shí)候?yàn)?true
    • keys():將字典所包含的所有鍵名以數(shù)組形式返回
    • values():將字典所包含的所有數(shù)值以數(shù)組形式返回
    • keyValues():將字典中所有[鍵,值]以數(shù)組形式返回
    • forEach(callbackFn):迭代字典所有的鍵值對(duì), callbackFn有兩個(gè)參數(shù): key 和 value. 該方法可以在回調(diào)函數(shù)返回 false 時(shí)被中止
  2. 實(shí)現(xiàn)上面的方法

// 字典的實(shí)現(xiàn)
class Dictionary {
  constructor(toStrFn = defaultToString) {
    this.toStrFn = toStrFn;
    this.table = {};
  }

  // 已經(jīng)存在一個(gè)給定鍵名的鍵值對(duì)(表中的位置不是 null 或 undefined)合武,那么返回 true
  hasKey(key) {
    return this.table[this.toStrFn(key)] != null;
  }

  set(key, value) {
    if (key != null && value != null) {
      const tableKey = this.toStrFn(key); // 獲取 key 的字符串
      this.table[tableKey] = new ValuePair(key, value); // 創(chuàng)建一個(gè)新的鍵值對(duì)并將其賦值給 table 對(duì)象上的 key屬性(tableKey)
      return true;
    }
    return false;
  }

  // 檢索一個(gè)值
  get(key) {
    // 首先檢索存儲(chǔ)在給定key 屬性中的對(duì)象,
    const valuePair = this.table[this.toStrFn(key)];
    return valuePair == null ? undefined : valuePair.value;
  }

  // 這個(gè)也可以檢索一個(gè)值: 獲取兩次key的字符串以及訪問(wèn)兩次 table 對(duì)象
  get2(key) {
    if (this.hasKey(key)) {
      return this.table[this.toStrFn(key)].value;
    }
    return undefined;
  }

  remove(key) {
    if (this.hasKey(key)) {
      delete this.table[this.toStrFn(key)];
      return true;
    }
    return false;
  }

  keyValues() {
    return Object.values(this.table);
  }

  // 兼容版
  keyValues2() {
    const valuePairs = [];
    for (const key in this.table) {
      if (this.hasKey(key)) {
        valuePairs.push(this.table[key]);
      }
    }
    return valuePairs;
  }

  keys() {
    return this.keyValues().map((i) => i.key);
  }

  values() {
    return this.keyValues().map((i) => i.value);
  }

  // 迭代字典中的每個(gè)鍵值對(duì)
  forEach(callbackFn) {
    const valuePairs = this.keyValues();
    for (let i = 0; i < valuePairs.length; i++) {
      // 執(zhí)行以參數(shù)形式傳入 forEach 方法的 callbackFn 函數(shù), 保存結(jié)果,
      const result = callbackFn(valuePairs[i].key, valuePairs[i].value);
      // 回調(diào)函數(shù)返回了 false,我們會(huì)中斷 forEach 方法的執(zhí)行
      if (result === false) {
        break;
      }
    }
  }

  size() {
    return Object.keys(this.table).length;
  }

  isEmpty() {
    return this.size() === 0;
  }

  clear() {
    this.table = {};
  }

  toString() {
    if (this.isEmpty()) {
      return "";
    }
    const valuePairs = this.keyValues();
    let objString = `${valuePairs[0].toString()}`;
    for (let i = 1; i < valuePairs.length; i++) {
      objString = `${objString},${valuePairs[i].toString()}`;
    }
    return objString;
  }
}

散列表

HashTable 類涡扼,也叫 HashMap 類稼跳,它是 Dictionary 類的一種散列表實(shí)現(xiàn)方式, 散列算法的作用是盡可能快地在數(shù)據(jù)結(jié)構(gòu)中找到一個(gè)值, 散列函數(shù)的作用是給定一個(gè)鍵值,然后返回值在表中的地址

  1. 應(yīng)用

    • 它是字典的一種實(shí)現(xiàn)吃沪,所以可以用作關(guān)聯(lián)數(shù)組
    • 也可以用來(lái)對(duì)數(shù)據(jù)庫(kù)進(jìn)行索引, 創(chuàng)建一個(gè)索引來(lái)更快地查詢到記錄的 key
    • 使用散列表來(lái)表示對(duì)象, JavaScript 語(yǔ)言內(nèi)部就是使用散列表來(lái)表示每個(gè)對(duì)象
  2. 創(chuàng)建散列表

使用一個(gè)關(guān)聯(lián)數(shù)組(對(duì)象)來(lái)表示我們的數(shù)據(jù)結(jié)構(gòu)汤善,和我們?cè)?Dictionary 類中所做的一樣

// 打一個(gè)骨架, 和字典差不多
class HashTable {
  constructor(toStrFn = defaultToString) {
    this.toStrFn = toStrFn;
    this.table = {};
  }
}
  1. 給類添加方法

    • put(key, value):向散列表增加一個(gè)新的項(xiàng)(也能更新散列表)
    • remove(key):根據(jù)鍵值從散列表中移除值
    • get(key):返回根據(jù)鍵值檢索到的特定的值
class HashTable {
  constructor(toStrFn = defaultToString) {
    this.toStrFn = toStrFn;
    this.table = {};
  }

  // 散列函數(shù)
  loseloseHashCode(key) {
    if (typeof key === "number") {
      return key;
    }
    const tableKey = this.toStrFn(key);

    let hash = 0; // 防止 key 是一個(gè)對(duì)象而不是字符串。我們需要一個(gè) hash 變量來(lái)存儲(chǔ)這個(gè)總和

    // charCodeAt() 方法可返回指定位置的字符的 Unicode 編碼票彪,返回值是 0 - 65535 之間的整數(shù)
    // 遍歷 key 并將從 ASCII表中查到的每個(gè)字符對(duì)應(yīng)的 ASCII 值加到 hash 變量中
    for (let i = 0; i < tableKey.length; i++) {
      hash += tableKey.charCodeAt(i);
    }

    // 為了得到比較小的數(shù)值红淡,我們會(huì)使用 hash 值和一個(gè)任意數(shù)做除法的余數(shù)(%)——這可以規(guī)避操作數(shù)超過(guò)數(shù)值變量最大表示范圍的風(fēng)險(xiǎn)。
    return hash % 37;
  }

  hashCode(key) {
    return this.loseloseHashCode(key);
  }

  // 將鍵和值加入散列表
  put(key, value) {
    if (key != null && value != null) {
      //對(duì)于給出的 key 參數(shù)降铸,我們需要用所創(chuàng)建的 hashCode 函數(shù)在表中找到一個(gè)位置
      const position = this.hashCode(key);
      // 為了信息備份將原始的 key 保存下來(lái)
      this.table[position] = new ValuePair(key, value);
    }
  }

  // 從散列表中獲取一個(gè)值
  get(key) {
    const valuePair = this.table[this.hashCode(key)];
    return valuePair == null ? undefined : valuePair.value;
  }

  // 移除一個(gè)值
  remove(key) {
    const hash = this.hashCode(key);
    const valuePair = this.table[hash];
    if (valuePair != null) {
      delete this.table[hash];
      return true;
    }
    return false;
  }

  toString() {
    if (this.isEmpty()) {
      return "";
    }
    const keys = Object.keys(this.table);
    let objString = `{${keys[0]} => ${this.table[keys[0]].toString()}}`;
    for (let i = 1; i < keys.length; i++) {
      objString = `${objString},{${keys[i]} => ${this.table[
        keys[i]
      ].toString()}}`;
    }
    return objString;
  }
}
// 使用 HashTable
const hash = new HashTable();
hash.put("Gandalf", "gandalf@email.com");
hash.put("John", "johnsnow@email.com");
hash.put("Tyrion", "tyrion@email.com");

console.log(hash.hashCode("Gandalf") + " - Gandalf"); // 19 - Gandalf
console.log(hash.hashCode("John") + " - John"); // 29 - John
console.log(hash.hashCode("Tyrion") + " - Tyrion"); // 16 - Tyrion
數(shù)據(jù)結(jié)構(gòu)
  1. 散列表和散列集合

在一些編程語(yǔ)言中在旱,還有一種叫作散列集合的實(shí)現(xiàn)。散列集合由一個(gè)集合構(gòu)成推掸,但是插入桶蝎、移除或獲取元素時(shí),使用的是 hashCode 函數(shù), 可以使用散列集合來(lái)存儲(chǔ)所有的英語(yǔ)單詞(不包括它們的定義)谅畅。和集合相似登渣,散列集合只存儲(chǔ)不重復(fù)的唯一值

  1. 處理散列表中的沖突

有時(shí)候,一些鍵會(huì)有相同的散列值毡泻。不同的值在散列表中對(duì)應(yīng)相同位置的時(shí)候胜茧,我們稱其為沖突

const hash = new HashTable();
hash.put("Ygritte", "ygritte@email.com"); // 4
hash.put("Jonathan", "jonathan@email.com"); // 5
hash.put("Jamie", "jamie@email.com"); // 5
hash.put("Jack", "jack@email.com"); // 7
hash.put("Jasmine", "jasmine@email.com"); // 8
hash.put("Jake", "jake@email.com"); // 9
hash.put("Nathan", "nathan@email.com"); // 10
hash.put("Athelstan", "athelstan@email.com"); //7
hash.put("Sue", "sue@email.com"); // 5
hash.put("Aethelwulf", "aethelwulf@email.com"); // 5
hash.put("Sargeras", "sargeras@email.com"); // 10
  • 處理沖突有幾種方法:分離鏈接、線性探查和雙散列法 (了解為主, 后續(xù)補(bǔ)充)

ES6 Map 類

ECMAScript 2015 新增了 Map 類牙捉≈褡幔可以基于 ES2015 的 Map 類開發(fā)我們的 Dictionary 類

ES6 WeakMap 和 WeakSet 類

  1. Map 和 Set 與其弱化版本之間僅有的區(qū)別是:
    • WeakSet 或 WeakMap 類沒(méi)有 entries、keys 和 values 等方法
    • 只能用對(duì)象作為鍵

ES6 的 Set Map WeakMap WeakSet 數(shù)據(jù)結(jié)構(gòu) => 阮一峰文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末邪铲,一起剝皮案震驚了整個(gè)濱河市芬位,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌带到,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異丢郊,居然都是意外死亡秸弛,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門搪搏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)狭握,“玉大人,你說(shuō)我怎么就攤上這事疯溺÷勐” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵囱嫩,是天一觀的道長(zhǎng)恃疯。 經(jīng)常有香客問(wèn)我,道長(zhǎng)墨闲,這世上最難降的妖魔是什么今妄? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮鸳碧,結(jié)果婚禮上盾鳞,老公的妹妹穿的比我還像新娘。我一直安慰自己杆兵,他們只是感情好雁仲,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著琐脏,像睡著了一般攒砖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上日裙,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天吹艇,我揣著相機(jī)與錄音,去河邊找鬼昂拂。 笑死受神,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的格侯。 我是一名探鬼主播鼻听,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼财著,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了撑碴?” 一聲冷哼從身側(cè)響起撑教,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎醉拓,沒(méi)想到半個(gè)月后伟姐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亿卤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年愤兵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片排吴。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡秆乳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钻哩,到底是詐尸還是另有隱情矫夷,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布憋槐,位于F島的核電站双藕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏阳仔。R本人自食惡果不足惜忧陪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望近范。 院中可真熱鬧嘶摊,春花似錦、人聲如沸评矩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)斥杜。三九已至虱颗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蔗喂,已是汗流浹背忘渔。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缰儿,地道東北人畦粮。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親宣赔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子预麸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容