1. 程式人生 > 實用技巧 >Trie(prefix tree)

Trie(prefix tree)

Trie

Trie 是一種特殊的資料結構,與二叉樹類似,只是 Trie 不限子孩子數量。

Trie 又名字典樹,單詞查詢樹,字首樹。我們可以使用 Trie 來構造工作中使用到的 紅點系統

下面以 LeetCode 的第208題 Implement Trie (Prefix Tree) 來討論實現 Trie 這種特殊的資料結構。

題目描述

Implement a trie with insert, search, and startsWith methods.

Trie trie = new Trie();

trie.insert("apple");
trie.search("apple");   // returns true
trie.search("app");     // returns false
trie.startsWith("app"); // returns true
trie.insert("app");
trie.search("app");     // returns true

Note: You may assume that all inputs are consist of lowercase letters a-z.All inputs are guaranteed to be non-empty strings.

實現 Trie

假設我們要往 Trie 中加入 ['apple', 'app', 'about', 'bed']。

在建立 Trie 時,我們以物件 Object 為儲存型別,對一個單詞字母都是物件的一個屬性,然後在每個單詞末尾時,我們加多一個單詞結尾標識 isEnd

把對應的單詞插入 Trie 後,我們將得到下圖所示的 Trie 結構:

1. 插入單詞

插入單詞時,每一個字母對應一個結點物件,如果沒有對應的結點則建立;如果有就在此結點下繼續查詢。在單詞的結尾時,我們需加入單詞結束標識 isEnd,以便查詢。

let obj = this.root;
for (let i = 0; i < word.length; i++) {
  let c = word[i];
  // 沒有這個字母結點,建立
  if (!obj[c]) {
    obj[c] = {};
  }
  obj = obj[c];
}
// 標識單詞結尾
obj.isEnd = true;

2. 查詢單詞或字首

在查詢單詞或字首的時候,需要一步一步的往下查詢,如果中途沒有任一個結點,則直接返回即可。如果是查詢單詞,找到了對應的最後一個字母結點後,我們仍需判斷結點是否有 isEnd

屬性;如果是字首,則判斷是否是一個物件即可。

// 查詢函式
Trie.prototype.commonSearch = function (word) {
  let obj = this.root;
  for (let char of word.split('')) {
    if (!obj[char]) {
      // 對應對應字母的結點,返回null
      return null;
    } else {
      obj = obj[char];
    }
  }
  // 找到最後一個單詞結點,返回結點物件
  return obj;
};

完整程式碼附下


/**
 * Initialize your data structure here.
 */
const Trie = function () {
  this.root = {};
};

/**
 * Inserts a word into the trie.
 * @param {string} word
 * @return {void}
 */
Trie.prototype.insert = function (word) {
  let obj = this.root;
  for (let i = 0; i < word.length; i++) {
    let c = word[i];
    if (!obj[c]) {
      obj[c] = {};
    }
    obj = obj[c];
  }
  obj.isEnd = true;
};

/**
 * Returns if the word is in the trie.
 * @param {string} word
 * @return {boolean}
 */
Trie.prototype.search = function (word) {
  let obj = this.commonSearch(word);
  return obj && obj.isEnd ? true : false;
};

/**
 * Returns if there is any word in the trie that starts with the given prefix.
 * @param {string} prefix
 * @return {boolean}
 */
Trie.prototype.startsWith = function (prefix) {
  let obj = this.commonSearch(prefix);
  return !!obj;
};

/**
 * @param {string} prefix
 * @return {any}
 */
Trie.prototype.commonSearch = function (word) {
  let obj = this.root;
  for (let char of word.split('')) {
    if (!obj[char]) {
      return null;
    } else {
      obj = obj[char];
    }
  }
  return obj;
};