Trie Tree 實現中文分詞器

前言

繼上一篇HashMap實現中文分詞器後,對Trie Tree的好奇,又使用Trie Tree實現了下中文分詞器。效率比HashMap實現的分詞器更高。

Trie Tree 簡介

Trie Tree,又稱單詞字典樹、查找樹,是一種樹形結構,是一種哈希樹的變種。典型應用是用於統計和排序大量的字符串(但不僅限於字符串),所以經常被搜索引擎系統用於文本詞頻統計。它的優點是:最大限度地減少無謂的字符串比較,查詢效率比哈希表高。

性質

它有3個基本性質:
1. 根節點不包含字符,除根節點外每一個節點都只包含一個字符。
2. 從根節點到某一節點,路徑上經過的字符連接起來,爲該節點對應的字符串。
3. 每個節點的所有子節點包含的字符都不相同。

Trie Tree 結構

Trie Tree

Trie Tree分詞原理:

(1) 從根結點開始一次搜索,比如搜索【北京】;
(2) 取得要查找關鍵詞的第一個字符【北】,並根據該字符選擇對應的子樹並轉到該子樹繼續進行檢索;
(3) 在相應的子樹上,取得要查找關鍵詞的第二個字符【京】,並進一步選擇對應的子樹進行檢索。
(4) 迭代過程……
(5) 在直到判斷樹節點的isEnd節點爲true則查找結束(最小匹配原則),然後發現【京】isEnd=true,則結束查找。

示例

下面用java簡單實現

package cn.com.infcn.algorithm;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * jijs
 * 正向最大匹配
 */
public class TrieTreeDemo {
    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 Map<String, Integer> tokenizer(String words){
            char[] arr = words.toCharArray();
            Node currentNode = root;
            Map<String, Integer> map = new HashMap<String, Integer>();
            //記錄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){
                        Integer num = map.get(word);
                        if(num==null){
                            map.put(word, 1);
                        }else{
                            map.put(word, num+1);
                        }
                        //i回退到最後匹配的座標
                        i=idx;
                        //從root的開始匹配
                        currentNode = root;
                        //清空匹配到的詞
                        word = null;
                        //清空當前路徑匹配到的所有字
                        sb = new StringBuilder();
                    }
                }
                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);
                        }
                    }
                }
            }

            return map;
        }
    }

    public static void main(String[] args) {
        TrieTree tree = new TrieTree();
        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+"】");
        }

        //分詞
        Map<String, Integer> map = tree.tokenizer("中國人民,中國首都是北京,中關村在海淀區,中國北京天安門。中國人");
        for (Entry<String, Integer> entry : map.entrySet()) {
            System.out.println(entry.getKey()+":"+entry.getValue());
        }

    }
}

想了解更多精彩內容請關注我的公衆號

本人簡書blog地址:http://www.jianshu.com/u/1f0067e24ff8    
點擊這裏快速進入簡書

GIT地址:http://git.oschina.net/brucekankan/
點擊這裏快速進入GIT

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章