前言
继上一篇HashMap实现中文分词器后,对Trie Tree的好奇,又使用Trie Tree实现了下中文分词器。效率比HashMap实现的分词器更高。
Trie Tree 简介
Trie Tree,又称单词字典树、查找树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
性质
它有3个基本性质:
1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3. 每个节点的所有子节点包含的字符都不相同。
Trie Tree 结构
Trie Tree分词原理:
(1) 从根结点开始一次搜索,比如搜索【中国人民】;
(2) 取得要查找关键词的第一个字符【中】,并根据该字符选择对应的子树并转到该子树继续进行检索;
(3) 在相应的子树上,取得要查找关键词的第二个字符【国】,并进一步选择对应的子树进行检索。
(4) 迭代过程……
(5) 在直到判断树节点最下面节点isEnd=true的节点,查找结束(最大匹配原则),然后发现【民】isEnd=true,则结束查找。
示例
下面用java简单实现 没有做歧义处理
package cn.com.infcn.algorithm;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import org.omg.CosNaming.NamingContextExtPackage.StringNameHelper;
/**
* jijs
* 正向最大匹配
*/
public class TireTreeQueue {
static class Node {
//记录当前节点的字
char c;
//判断该字是否词语的末尾,如果是则为false
boolean isEnd;
//子节点
List<Node> childList;
public Node(char c) {
super();
this.c = c;
isEnd = false;
childList = new LinkedList<Node>();
}
//查找当前子节点中是否保护c的节点
public Node findNode(char c){
for(Node node : childList){
if(node.c == c){
return node;
}
}
return null;
}
}
static class TrieTree{
Node root = new Node(' ');
//构建Trie Tree
public void insert(String words){
char[] arr = words.toCharArray();
Node currentNode = root;
for (char c : arr) {
Node node = currentNode.findNode(c);
//如果不存在该节点则添加
if(node == null){
Node n = new Node(c);
currentNode.childList.add(n);
currentNode = n;
}else{
currentNode = node;
}
}
//在词的最后一个字节点标记为true
currentNode.isEnd = true;
}
//判断Trie Tree中是否包含该词
public boolean search(String word){
char[] arr = word.toCharArray();
Node currentNode = root;
for (int i=0; i<arr.length; i++) {
Node n = currentNode.findNode(arr[i]);
if(n != null){
currentNode = n;
//判断是否为词的尾节点节点
if(n.isEnd){
if(n.c == arr[arr.length-1]){
return true;
}
}
}
}
return false;
}
//最大匹配优先原则
public Queue<String> tokenizer(String words){
char[] arr = words.toCharArray();
Node currentNode = root;
//Map<String, Integer> map = new HashMap<String, Integer>();
Queue<String> queue = new LinkedList<String>();
//记录Trie Tree 从root开始匹配的所有字
StringBuilder sb = new StringBuilder();;
//最后一次匹配到的词,最大匹配原则,可能会匹配到多个字,以最长的那个为准
String word="";
//记录记录最后一次匹配坐标
int idx = 0;
for (int i=0; i<arr.length; i++) {
Node n = currentNode.findNode(arr[i]);
if(n != null){
sb.append(n.c);
currentNode = n;
//匹配到词
if(n.isEnd){
//记录最后一次匹配的词
word = sb.toString();
//记录最后一次匹配坐标
idx = i;
}
}else{
//判断word是否有值
if(word!=null && word.length()>0){
queue.offer(word);
//i回退到最后匹配的坐标
i=idx;
//从root的开始匹配
currentNode = root;
//清空匹配到的词
word = null;
//清空当前路径匹配到的所有字
sb = new StringBuilder();
}else {
int len = sb.length();
if(len > 0) {
//将sb中的字放到队列中(此时sb中的字已不能组成词)
for(int j=0;j<len;j++) {
//未匹配到的字
queue.offer(sb.charAt(j)+"");
}
//清空当前路径匹配到的所有字
sb = new StringBuilder();
//回退一个 重新匹配
i--;
//从root的开始匹配
currentNode = root;
}else {
//未匹配到的字
queue.offer(arr[i]+"");
}
}
}
/*if(i==arr.length-2){
if(word!=null && word.length()>0){
Integer num = map.get(word);
if(num==null){
map.put(word, 1);
}else{
map.put(word, num+1);
}
}
}*/
}//for
//
if(word!=null && word.length()>0){
queue.offer(word);
}else {
//
int len = sb.length();
if(len > 0) {
for(int j=0;j<len;j++) {
queue.offer(sb.charAt(j)+"");
}
}
}
return queue;
}
}
public static void main(String[] args) {
TrieTree tree = new TrieTree();
tree.insert("北京");
tree.insert("海淀区");
tree.insert("中国");
tree.insert("中国人民");
tree.insert("中关村");
tree.insert("首都");
tree.insert("周杰伦");
tree.insert("天安门");
tree.insert("马云爸爸");
tree.insert("马云");
tree.insert("刘强东");
tree.insert("美国");
tree.insert("中国人");
tree.insert("中国人寿");
tree.insert("民主");
/* String word = "中国";
//查找该词是否存在 Trid Tree 中
boolean flag = tree.search(word);
if(flag){
System.out.println("Trie Tree 中已经存在【"+word+"】");
}else{
System.out.println("Trie Tree 不包含【"+word+"】");
}*/
//分词
Queue<String> queue = tree.tokenizer("中国人 民主");
for (String word : queue) {
System.out.print(word+" | ");
}
}
}
参考: https://blog.csdn.net/jijianshuai/article/details/72455736