关闭

Java中文键树的一种实现(附带模糊查询功能)

标签: 数据结构中文键树模糊查询性能测试java
1944人阅读 评论(5) 收藏 举报
分类:

首先在文章的开头声明一下哈,本文只是介绍一种Java蛮力键树的实现,并没有什么高深的数据结构,所以数据量不超过百万字符的可以参考,数据量太大的另请高明吧。另外,后面的键树代码实际上不仅适用于中文存储和查找,只要是字符串形式的数据都可以存储。比如:“锄禾日当午”、“a+你好啊234#jfjf”这样形式的数据都可以放进去(韩文柬埔寨文怎么混搭都可以,只要编码方式别搞混)。

键树是一种非常简单的数据结构,相信学过的人都知道,没学过的人一看就明白:


图1 一棵键树

好了,既然它这么简单,那我就不介绍了,想要完整阅读键树定义的读者可以随便百度,下面开始设计分析。传统键树拥有两种可选的存储结构,分别是双链树和多重链表(又称为Trie树)。多重链表表示法适用于键树中结点的度较大的情况,因此本文在实现中文键树时考虑使用多重链表结构。键树将一条完整的信息串分割成一层一层的结点结构,对应到中文,即每一个结点上存储了一个汉字信息。典型的键树通常采用数组来实现结点后代的存储,这是源于英文字母只有固定26个。鉴于中文无法同样考虑,在实现时采用ArrayList来实现后代的存储。这既保证了查询速度,又避免数组越界的问题。因此,一个结点的结构就是:

class TrieNode {
	public String value;
	public ArrayList<TrieNode> ptr = null;
	public TrieNode(String value) {
		this.value=value;
		ptr =new ArrayList<TrieNode>();
	}
}
    向这棵键树中插入新节点是很简单的,比如插入的新内容是一个"apple"的单词,那么将这个字符串依次拆开,逐个在键树中向下寻找(在ArrayList中遍历比较),并最终决定放不放就行(放就add,不放就下一层或者结束),具体实现就是后文中的insert(String key)方法。同样的道理,查找也很简单。

    好了,键树就实现完成了,很简单。这里加了一个内容:因为正常人实现这种数据结构都会想要提供模糊查询的功能,比如我查找:"ap",就希望这棵树能给我一个"apple",满足你。实现这个功能的基本功就在于最简单的树的先序遍历,不过由于这是贱树,所以又不太一样。

    因为前面说了百万以上不要看本文,所以我这里的先序遍历用了递归(百万以下就不要叫会栈溢出,随心所欲的插就行)。原理很简单,往遍历方法里传一个树结点,比如前面查询了"ap",那么"ap"的p结点就传了进来。然后用一个StringBuffer来装进后面的"ple"。如果还有类似于"application"这样的单词,就倒回去,再在StringBuffer里装一遍"plication"。有多个关键词的就会将每次查询的StringBuffer装进一个ArrayList<String>,最后这个集合searchResult就存储了模糊查询的结果。

ArrayList<String> searchResult=new ArrayList<String>();
StringBuffer tempWord=new StringBuffer();
int start=0;
private void traverseTree(TrieNode p){
	if(!(p.ptr.isEmpty())){
		for(TrieNode tn:p.ptr){
			tempWord.append(tn.value);
			start++;
			traverseTree(tn);
			start--;
			tempWord.delete(start,tempWord.length());
		}
	}else{
		searchResult.add(tempWord.toString());
	}
}

    最后说一下性能和改进:

    性能:

        20万字符(约60000条古诗)模糊查询平均耗时为1毫秒。插入的时间非常短,短到我忘了测(以上性能什么概念呢,就是如果你要做一个简易的搜索提示框的话,后台用这个键树来实现是非常合适的,搜索提示的反应零卡顿非常快。那如果是点一个按钮然后查询那种功能就更不在话下了)。

    改进(这里的改进如果完成的话,那么和市面上一线的搜索引擎相应功能比,也就输在没有商标):

    (1)对于要满足中拼双搜的搜索框提示功能,需要维护中文、拼音两棵键树(中文拼音转化可使用pinyin4j开源库,处理时注意拼音时涉及多音字),在设计算法时会复杂很多(复杂4倍左右)。

    (2)为了提升中文键树的效率,可以考虑按照偏旁拆分中文来组织键树结点结构(就像按照偏旁部首查字典一样),将会使键树的效率提升非常多。实现这样的算法需要中文偏旁api的支持,至于有不有这样的api我就不知道了。

代码贴:中文键树的蛮力实现(可处理任意字符串)

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

import com.yhk.filewriter.MyReader;

/*
 * 无数据结构设计下的蛮力中文键树
 */
class TrieNode {
	public String value;
	public ArrayList<TrieNode> ptr = null;
	public TrieNode(String value) {
		this.value=value;
		ptr =new ArrayList<TrieNode>();
	}
}

public class TrieTree_1 {
	private static TrieNode root = null;
	ArrayList<String> searchResult=new ArrayList<String>();
	StringBuffer tempWord=new StringBuffer();
	int start=0;
	
	public TrieTree_1() {
		root = new TrieNode(null);
	}
	
	public void insert(String key) {
		TrieNode p = root;
		String tempWord;
		boolean contains;
		TrieNode tempNode;
		for (int i = 0; i < key.length(); i++) {
			tempWord=String.valueOf(key.charAt(i));
			contains=false;
			for(TrieNode tn:p.ptr){
				if(tn.value.equals(tempWord)){
					p=tn;
					contains=true;
					break;
				}
			}
			if(!contains){
				tempNode=new TrieNode(tempWord);
				p.ptr.add(tempNode);
				p=tempNode;
			}
		}
	}
	
	public ArrayList<String> search(String key) {  //模糊查询就是这个方法,打个比方比如key是"ap",那么ArrayList里就有{"apple","application"}
		TrieNode p = root;
		String temp;
		boolean contains=false;
		for (int i = 0; i < key.length(); i++) {
			temp=String.valueOf(key.charAt(i));
			contains=false;
			for(TrieNode tn:p.ptr){
				if(tn.value.equals(temp)){
					p=tn;
					contains=true;
					break;
				}
			}
			if(contains){
				continue;
			}else{
				break;
			}
		}
		if(contains){
			if(!(p.ptr.isEmpty())){
				//查找到关键字
				searchResult.clear();
				tempWord.delete(0, tempWord.length());
				tempWord.append(key);
				start=key.length();
				traverseTree(p);
			}else{
				//已经查找到键树的底部
				return null;
			}
		}else{
			//没有查找到相应关键字
			return null;
		}
		return searchResult;
	}
	
	private void traverseTree(TrieNode p){
		if(!(p.ptr.isEmpty())){
			for(TrieNode tn:p.ptr){
				tempWord.append(tn.value);
				start++;
				traverseTree(tn);
				start--;
				tempWord.delete(start,tempWord.length());
			}
		}else{
			searchResult.add(tempWord.toString());
		}
	}
}



1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:34281次
    • 积分:749
    • 等级:
    • 排名:千里之外
    • 原创:32篇
    • 转载:0篇
    • 译文:8篇
    • 评论:17条
    博客专栏
    文章分类