树是否平衡取决于单词的读入顺序。如果按排序后的顺序插入,则生成方式最不平衡。单词的读入顺序对于创建平衡的三叉搜索树很重要,但对于二叉搜索树就不太重要。通过选择一个排序后数据单元集合的中间值,并把它作为开始节点,我们可以创建一个平衡的三叉树。可以写一个专门的过程来生成平衡的三叉树词典。
/** * 在调用此方法前,先把词典数组k排好序 * @param fp 写入的平衡序的词典 * @param k 排好序的词典数组 * @param offset 偏移量 * @param n 长度 * @throws Exception */ void outputBalanced(BufferedWriter fp,ArrayListk,int offset, int n){ int m; if (n < 1) { return; } m = n >> 1; //m=n/2 String item= k.get(m + offset); fp.write(item);//把词条写入到文件 fp.write('\n'); outputBalanced(fp,k,offset, m); //输出左半部分 outputBalanced(fp,k, offset + m + 1, n - m - 1); //输出右半部分 }
取得平衡的单词排序类似于洗扑克牌。假想有若干张扑克牌,每张牌对应一个单词,先把牌排好序,然后取最中间的一张牌,单独放着。剩下的牌分成了两摞,左边一摞牌中也取最中间的一张放在取出来的那张牌后面。右边一摞牌中也取最中间的一张放在取出来的牌后面,以此类推。
我们再次以有序的数据单元(as at be by he in is it of on or to)为例。 首先我们把关键字"is"作为中间值并且构建一个包含字母"i"的根节点。它的直接后继节点包含字母"s"并且可以存储任何与"is"有关联的数据。对于"i"的左树,我们选择"be"作为中间值并且创建一个包含字母"b"的节点,字母"b"的直接后继节点包含"e"。该数据存储在"e"节点。对于"i"的右树,按照逻辑,选择"on"作为中间值,并且创建"o"节点以及它的直接后继节点"n"。
TernarySearchTrie本身存储关键字到值的对应关系,可以当做HashMap对象来使用。关键字按照字符拆分成许多节点,以TSTNode的实例存在。值存储在TSTNode的data属性中。TSTNode的实现代码如下所示:
public final class TSTNode { /** 节点的值 */ public Data data=null;// data属性可以存储 词原文和词性、词频等相关的信息 protected TSTNode loNode; //左边节点 protected TSTNode eqNode; //中间节点 protected TSTNode hiNode; //右边节点 protected char splitchar; // 本节点表示的字符 /** * 构造方法 * *@param splitchar 该节点表示的字符 */ protected TSTNode(char splitchar) { this.splitchar = splitchar; } public String toString() { return "splitchar:"+ splitchar; } }
查找词典的基本过程是:输入一个词,返回这个词对应的TSTNode对象,如果该词不在词典中则返回空。在查找词典的过程中,从树的根节点匹配Key,按Char从前往后匹配Key。charIndex表示Key当前要比较的Char的位置。匹配过程如下所示:
protected TSTNode getNode(String key, TSTNode startNode) { if (key == null ) { return null; } int len = key.length(); if (len ==0) return null; TSTNode currentNode = startNode; //匹配过程中当前节点的位置 int charIndex = 0; char cmpChar = key.charAt(charIndex); int charComp; while (true) { if (currentNode == null) {//没找到 return null; } charComp = cmpChar - currentNode.splitchar; if (charComp == 0) {//相等 charIndex++; if (charIndex == len) {//找到了 return currentNode; } else { cmpChar = key.charAt(charIndex); } currentNodecurrentNode = currentNode.eqNode; } else if (charComp < 0) {//小于 currentNodecurrentNode = currentNode.loNode; } else {//大于 currentNodecurrentNode = currentNode.hiNode; } } }
三叉树的创建过程也就是在Trie树上创建和单词对应的节点。实现代码如下所示:
//向词典树中加入一个单词的过程 private TSTNode addWord(String key) { TSTNode currentNode = root; //从树的根节点开始查找 int charIndex = 0; //从词的开头匹配 while (true) { //比较词的当前字符与节点的当前字符 int charComp =key.charAt(charIndex) - currentNode.splitchar; if (charComp == 0) {//相等 charIndex++; if (charIndex == key.length()) { return currentNode; } if (currentNode.eqNode == null) { currentNode.eqNode = new TSTNode(key.charAt (charIndex)); } currentNodecurrentNode = currentNode.eqNode; } else if (charComp < 0) {//小于 if (currentNode.loNode == null) { currentNode.loNode = new TSTNode(key.charAt (charIndex)); } currentNodecurrentNode = currentNode.loNode; } else {//大于 if (currentNode.hiNode == null) { currentNode.hiNode = new TSTNode(key.charAt (charIndex)); } currentNodecurrentNode = currentNode.hiNode; } } }
相对于查找过程,创建过程在搜索过程中判断出链接的空值后创建相关的节点,而不是碰到空值后结束搜索过程并返回空值。
同一个词可以有不同的词性,例如"朝阳"既可能是一个"区",也可能是一个"市"。可以把这些和某个词的词性相关的信息放在同一个链表中。这个链表可以存储在TSTNode 的Data属性中。