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);
}
}
}