上一篇博文介绍了字典树(Tire Tree),它的结构简单、实现也很容易,但是,它也有自己的缺点,就是空间效率低,占用内存大。英文有26个字母,每个节点保存26个指向字母的指针(其中很大部分会是空指针),全部存下要 2626 ,占用空间之大可想而知。中文的汉字有5万多,要是存储起来,内存耗用会更加严重,以至于不可行。所以需要考虑用另外一种数据结构来进行存储,三分搜索树(ternary search tree)可以很好的解决Trie Tree的内存问题。
三分搜索树的每个节点可以存储一个字符、一个对象,以及指向三个孩子节点的指针,三个孩子节点分别称作 equal kid, lo kid and hi kid ,也可以叫做 middle (child), lower (child) and higher (child)。树节点还有一个指向父节点的指针用来标识此节点是否是一个词语的末尾。lo kid 指向的是一个节点小于当前节点的值。hi kid 指向的一个节点大于当前节点的值;equal kid 指向的一个节点等于当前节点的值;下图是字符串 “as”, “at”, “cup”, “cute”, “he”, “i” and “us”构成的三元搜索树。
三分搜索树常用的操作有新增、查询操作,修改、删除的操作用的会比较少。
同样,以”as”, “at”, “cup”, “cute”, “he”, “i” and “us”字符串为示例,首先插入 cute,接着插入 cup,由于 cute 和 cup 有相同的前缀 cu,而 p在字母表中的顺序在 t 的前面,所以 p 应该插入到 t的 lo child,即 t节点的左边,接着插入 as,由于 a在字母表中的顺序在c的前面,所以 a应该插入到 c的 lo child,即c节点的左边,t 直接插入 a 的 equal kid即可,同理,插入剩下的节点,即可得到上图。
以查询 us 为例,先找 u, u 大于 c,从 c的hi kid 往下查询找到h,c 大于 h,继续从 h的hi kid 往下查询找到u,相同,继续找 s,由于 s 与 u的 mid kid s 相同,并且 s 标记为单词的末尾,到此,查找结束。
package com.feng.nlp.algorithm;
import java.util.ArrayList;
/**
* Created by lionel on 17/5/16.
*/
public class TernarySearchTree {
private TSTNode root;
private ArrayList al;
/** 构造器 **/
public TernarySearchTree()
{
root = null;
}
/** 检查是否为空 **/
public boolean isEmpty()
{
return root == null;
}
/** 清空树**/
public void makeEmpty()
{
root = null;
}
/** 插入操作**/
public void insert(String word)
{
root = insert(root, word.toCharArray(), 0);
}
/** 插入一个单词**/
public TSTNode insert(TSTNode r, char[] word, int ptr)
{
if (r == null)
r = new TSTNode(word[ptr]);
if (word[ptr] < r.data)
r.left = insert(r.left, word, ptr);
else if (word[ptr] > r.data)
r.right = insert(r.right, word, ptr);
else
{
if (ptr + 1 < word.length)
r.middle = insert(r.middle, word, ptr + 1);
else
r.isEnd = true;
}
return r;
}
/** 查找操作 **/
public boolean search(String word)
{
return search(root, word.toCharArray(), 0);
}
/** 查找单词 **/
private boolean search(TSTNode r, char[] word, int ptr)
{
if (r == null)
return false;
if (word[ptr] < r.data)
return search(r.left, word, ptr);
else if (word[ptr] > r.data)
return search(r.right, word, ptr);
else
{
if (r.isEnd && ptr == word.length - 1)
return true;
else if (ptr == word.length - 1)
return false;
else
return search(r.middle, word, ptr + 1);
}
}
/** 打印树 **/
public String toString()
{
al = new ArrayList();
traverse(root, "");
return "\nTernary Search Tree : "+ al;
}
/** 树的遍历 **/
private void traverse(TSTNode r, String str)
{
if (r != null)
{
traverse(r.left, str);
str = str + r.data;
if (r.isEnd)
al.add(str);
traverse(r.middle, str);
str = str.substring(0, str.length() - 1);
traverse(r.right, str);
}
}
}