字典树

字典树如图所示:

TrieNode.js:在hashTable的基础上实现的

import HashTable from '../hash-table/HashTable';

export default class TrieNode {//字典树节点类
  /**
   * @param {string} character
   * @param {boolean} isCompleteWord
   */
  constructor(character, isCompleteWord = false) {
    this.character = character;//当前字典树节点的字母
    this.isCompleteWord = isCompleteWord;//是否是一个完整的单词
    this.children = new HashTable();//children子节点,新的哈希表
  }

  /**
   * @param {string} character
   * @return {TrieNode}
   */
  getChild(character) {//从children哈希表中获取指定字母
    return this.children.get(character);
  }

  /**
   * @param {string} character
   * @param {boolean} isCompleteWord
   * @return {TrieNode}
   */
  addChild(character, isCompleteWord = false) {//给当前节点添加新的子节点
    if (!this.children.has(character)) {
      //如果children哈希表属性里没有这个字母,就用哈希表的set方法添加
      //key是字母,value是这个字母新建的字典树节点
      this.children.set(character, new TrieNode(character, isCompleteWord));
    }

    const childNode = this.children.get(character);
    //如果children哈希表中已经有这个字母了,获取到这个字典树节点

    // In cases similar to adding "car" after "carpet" we need to mark "r" character as complete.
    childNode.isCompleteWord = childNode.isCompleteWord || isCompleteWord;
    //更新它的isCompleteWord属性

    return childNode;//返回这个新字典树节点
  }

  /**
   * @param {string} character
   * @return {TrieNode}
   */
  removeChild(character) {//删除当前节点指定字母的子节点
    const childNode = this.getChild(character);//从children哈希表中获取指定字母的子节点

    // Delete childNode only if:
    // - childNode has NO children,
    // - childNode.isCompleteWord === false.
    if (
      childNode
      && !childNode.isCompleteWord
      && !childNode.hasChildren()
    ) {//如果存在childNode,并且childNode不是完整单词,并且childNode没有子节点
      this.children.delete(character);//满足上述条件就可以删除
    }

    return this;
  }

  /**
   * @param {string} character
   * @return {boolean}
   */
  hasChild(character) {//判断当前字典表节点有没有指定字母的子节点
    return this.children.has(character);
  }

  /**
   * Check whether current TrieNode has children or not.
   * @return {boolean}
   */
  hasChildren() {//判断当前字典表节点有没有子节点
    return this.children.getKeys().length !== 0;
  }

  /**
   * @return {string[]}
   */
  suggestChildren() {//返回当前字典表节点的所有子节点的字母组成的数组
    return [...this.children.getKeys()];
  }

  /**
   * @return {string}
   */
  toString() {
    let childrenAsString = this.suggestChildren().toString();//当前字典表节点的所有子节点的字母的数组的字符串形式
    childrenAsString = childrenAsString ? `:${childrenAsString}` : '';
    const isCompleteString = this.isCompleteWord ? '*' : '';//当前字典表节点是否是完整单词属性的字符串值

    return `${this.character}${isCompleteString}${childrenAsString}`;
    //返回当前字典表节点的字母,是否是完整单词字符串值,和所有子节点字母值连起来的字符串
  }
}

note:

1.对于toString的理解:找到改结点所有孩子的关键字,并且转为字符,通过isCompleteString判断是否为完整的字符,然后输出显示。

const trieNode = new TrieNode('c');

    trieNode.addChild('a', true);
    trieNode.addChild('o');

    expect(trieNode.toString()).toBe('c:a,o');

 Trie:

import TrieNode from './TrieNode';

// Character that we will use for trie tree root.
const HEAD_CHARACTER = '*';

export default class Trie {
  constructor() {
    this.head = new TrieNode(HEAD_CHARACTER);
  }

  /**
   * @param {string} word
   * @return {Trie}
   */
  addWord(word) {
    const characters = Array.from(word);
    let currentNode = this.head;

    for (let charIndex = 0; charIndex < characters.length; charIndex += 1) {
      const isComplete = charIndex === characters.length - 1;
      currentNode = currentNode.addChild(characters[charIndex], isComplete);
    }

    return this;
  }

  /**
   * @param {string} word
   * @return {Trie}
   */
  deleteWord(word) {
    const depthFirstDelete = (currentNode, charIndex = 0) => {
      if (charIndex >= word.length) {
        // Return if we're trying to delete the character that is out of word's scope.
        return;
      }

      const character = word[charIndex];
      const nextNode = currentNode.getChild(character);

      if (nextNode == null) {
        // Return if we're trying to delete a word that has not been added to the Trie.
        return;
      }

      // Go deeper.
      depthFirstDelete(nextNode, charIndex + 1);

      // Since we're going to delete a word let's un-mark its last character isCompleteWord flag.
      if (charIndex === (word.length - 1)) {
        nextNode.isCompleteWord = false;
      }

      // childNode is deleted only if:
      // - childNode has NO children
      // - childNode.isCompleteWord === false
      currentNode.removeChild(character);
    };

    // Start depth-first deletion from the head node.
    depthFirstDelete(this.head);

    return this;
  }

  /**
   * @param {string} word
   * @return {string[]}
   */
  suggestNextCharacters(word) {
    const lastCharacter = this.getLastCharacterNode(word);

    if (!lastCharacter) {
      return null;
    }

    return lastCharacter.suggestChildren();
  }

  /**
   * Check if complete word exists in Trie.
   *
   * @param {string} word
   * @return {boolean}
   */
  doesWordExist(word) {
    const lastCharacter = this.getLastCharacterNode(word);

    return !!lastCharacter && lastCharacter.isCompleteWord;
  }

  /**
   * @param {string} word
   * @return {TrieNode}
   */
  getLastCharacterNode(word) {
    const characters = Array.from(word);
    let currentNode = this.head;

    for (let charIndex = 0; charIndex < characters.length; charIndex += 1) {
      if (!currentNode.hasChild(characters[charIndex])) {
        return null;
      }

      currentNode = currentNode.getChild(characters[charIndex]);
    }

    return currentNode;
  }
}

总结:有点难,因为没有接触过,总的来说就是对着代码,看懂youtube上截过来的那张图。

posted @ 2019-01-09 19:23  Archer-Fang  阅读(127)  评论(0编辑  收藏  举报