场景:现在有一个错词库,维护的是错词和正确词对应关系。比如:错词“我门”对应的正确词“我们”。然后在用户输入的文字进行错词校验,需要判断输入的文字是否有错词,并找出错词以便提醒用户,并且可以显示出正确词以便用户确认,如果是错词就进行替换。
首先想到的就是取出错词List放在内存中,当用户输入完成后用错词List来foreach每个错词,然后查找输入的字符串中是否包含错词。这是一种有效的方法,并且能够实现。问题是错词的数量比较多,目前有10多万条,将来也会不断更新扩展。所以pass了这种方案,为了让错词查找提高速度就用了字典树来存储错词。
字典树Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较。
Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
通常字典树的查询时间复杂度是O(logL),L是字符串的长度。所以效率还是比较高的。而我们上面说的foreach循环则时间复杂度为O(n),根据时间复杂度来看,字典树效率应该是可行方案。
字典树原理
根节点不包含字符,除根节点外每一个节点都只包含一个字符; 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串; 每个节点的所有子节点包含的字符都不相同。
比如现在有错词:“我门”、“旱睡”、“旱起”。那么字典树如下图
其中红色的点就表示词结束节点,也就是从根节点往下连接成我们的词。
实现字典树:
1 public class Trie 2 { 3 private class Node 4 { 5 /// <summary> 6 /// 是否单词根节点 7 /// </summary> 8 public bool isTail = false; 9 10 public Dictionary<char, Node> nextNode; 11 12 public Node(bool isTail) 13 { 14 this.isTail = isTail; 15 this.nextNode = new Dictionary<char, Node>(); 16 } 17 public Node() : this(false) 18 { 19 } 20 } 21 22 /// <summary> 23 /// 根节点 24 /// </summary> 25 private Node rootNode; 26 private int size; 27 private int maxLength; 28 29 public Trie() 30 { 31 this.rootNode = new Node(); 32 this.size = 0; 33 this.maxLength = 0; 34 } 35 36 /// <summary> 37 /// 字典树中存储的单词的最大长度 38 /// </summary> 39 /// <returns></returns> 40 public int MaxLength() 41 { 42 return maxLength; 43 } 44 45 /// <summary> 46 /// 字典树中存储的单词数量 47 /// </summary> 48 public int Size() 49 { 50 return size; 51 } 52 53 /// <summary> 54 /// 获取字典树中所有的词 55 /// </summary> 56 public List<string> GetWordList() 57 { 58 return GetStrList(this.rootNode); 59 } 60 61 private List<string> GetStrList(Node node) 62 { 63 List<string> wordList = new List<string>(); 64 65 foreach (char nextChar in node.nextNode.Keys) 66 { 67 string firstWord = Convert.ToString(nextChar); 68 Node childNode = node.nextNode[nextChar]; 69 70 if (childNode == null || childNode.nextNode.Count == 0) 71 { 72 wordList.Add(firstWord); 73 } 74 else 75 { 76 77 if (childNode.isTail) 78 { 79 wordList.Add(firstWord); 80 } 81 82 List<string> subWordList = GetStrList(childNode); 83 foreach (string subWord in subWordList) 84 { 85 wordList.Add(firstWord + subWord); 86 } 87 } 88 } 89 90 return wordList; 91 } 92 93 /// <summary> 94 /// 向字典中添加新的单词 95 /// </summary> 96 /// <param></param> 97 public void Add(string word) 98 { 99 //从根节点开始 100 Node cur = this.rootNode; 101 //循环遍历单词 102 foreach (char c in word.ToCharArray()) 103 { 104 //如果字典树节点中没有这个字母,则添加 105 if (!cur.nextNode.ContainsKey(c)) 106 { 107 cur.nextNode.Add(c, new Node()); 108 } 109 cur = cur.nextNode[c]; 110 } 111 cur.isTail = true; 112 113 if (word.Length > this.maxLength) 114 { 115 this.maxLength = word.Length; 116 } 117 size++; 118 } 119 120 /// <summary> 121 /// 查询字典中某单词是否存在 122 /// </summary> 123 /// <param></param> 124 /// <returns></returns> 125 public bool Contains(string word) 126 { 127 return Match(rootNode, word); 128 } 129 130 /// <summary> 131 /// 查找匹配 132 /// </summary> 133 /// <param></param> 134 /// <param></param> 135 /// <returns></returns> 136 private bool Match(Node node, string word) 137 { 138 if (word.Length == 0) 139 { 140 if (node.isTail) 141 { 142 return true; 143 } 144 else 145 { 146 return false; 147 } 148 } 149 else 150 { 151 char firstChar = word.ElementAt(0); 152 if (!node.nextNode.ContainsKey(firstChar)) 153 { 154 return false; 155 } 156 else 157 { 158 Node childNode = node.nextNode[firstChar]; 159 return Match(childNode, word.Substring(1, word.Length - 1)); 160 } 161 } 162 } 163 }