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

首先在文章的开头声明一下哈,本文只是介绍一种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());
        }
    }
}

--------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- --------------------- 

 

 

 

java字典树(Trie)实现中文模糊匹配

2018年12月16日 03:15:50 Wj要努力 阅读数:72

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dreamzuora/article/details/85024533

原理解释:
java实现:https://blog.csdn.net/yuhk231/article/details/51539840
c实现:https://blog.csdn.net/qq_31175231/article/details/77827324
代码模板:缺点,只能检索出在一个分支中的前缀匹配内容

package com.xq.algorithm;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
 
/*
 * 无数据结构设计下的蛮力中文键树
 */
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());
		}
	}
	public static void main(String[] args) {
		TrieTree_1 chinese = new TrieTree_1();
		chinese.insert("中");
		chinese.insert("中国人");
		chinese.insert("中国");
		chinese.insert("中华人民");
		chinese.insert("中华人崛起");
		chinese.insert("中华上下五千年");
		ArrayList<String> list = chinese.search("中华");
		for (String string : list) {
			System.out.println(string);
		}
	}
}

猜你喜欢

转载自blog.csdn.net/java_2017_csdn/article/details/85270133
今日推荐