1. 程式人生 > >Java中文鍵樹的一種實現(附帶模糊查詢功能)

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