字典树
字典树如图所示:
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上截过来的那张图。