怎樣實現基於Trie樹和字典的分詞功能

超人汪小建發表於2018-06-06

前言

目前做分詞比較流行的是用深度學習來做,比如用迴圈神經網路和條件隨機場,也有直接用條件隨機場或隱馬爾科夫模型的。前面也實現過上面幾種,效果挺不錯,基於隱馬爾科夫模型的差一點,條件隨機場的效果較好,而加入深度學習的效果最好。

而最最傳統的分詞做法很多都是基於字典的,然後通過最大匹配法匹配,效果比較一般。效果雖然一般,但我們還是看下怎麼實現的吧。

Trie樹結構

Trie 是一種搜尋樹,它的 key 都為字串,通過 key 可以找到 value。能做到高效查詢和插入,時間複雜度為O(k),缺點是耗記憶體。它的核心思想就是減少沒必要的字元比較,使查詢高效率,即用空間換時間,再利用共同字首來提高查詢效率。

Trie樹的根節點不包含字元,根節點到某節點的路徑連起來的字串為該節點對應的字串,每個節點只包含一個字元,此外,任意節點的所有子節點的字元都不相同。

比如如下,將五個詞語新增到Trie樹中,最後的結構如圖所示。

TrieTree tree = new TrieTree();
tree.put("美利堅");
tree.put("美麗");
tree.put("金幣");
tree.put("金子");
tree.put("帝王");
複製程式碼

這裡寫圖片描述

Github

https://github.com/sea-boat/TextAnalyzer/blob/master/src/main/java/com/seaboat/text/analyzer/segment/

效果

可以看到基於字典的分詞效果是存在缺點的,需要用機器學習進一步優化。

DictSegment segment = new DictSegment();
System.out.println(segment.seg("我是中國人"));
System.out.println(segment.seg("人工智慧是什麼"));
System.out.println(segment.seg("北京網際網路違法和不良資訊舉報中心"));
複製程式碼
[我, 是, 中國人]
[人工智慧, 是, 什麼]
[北京, 網際網路, 違法, 和不, 良, 資訊, 舉報中心]
複製程式碼

簡易實現

定義一個節點類代表Trie樹節點,包含若干子節點、值和刪除標記。getChild方法用於遍歷該節點下的指定字元的子節點,allChildrenDeleted方法用於檢測節點下的子節點是否已被刪除了,setChild方法用於將子節點設定到某個節點上。

public class TrieNode {

	private TrieNode[] children;
	private String value;
	private boolean deleted = false;

	public TrieNode(String value) {
		this.value = value == null ? null : value.intern();
	}

	public boolean isEmpty() {
		return this.value == null && this.children == null;
	}

	public TrieNode[] getChildren() {
		return children;
	}

	public TrieNode getChild(String word) {
		if (children == null)
			return null;
		for (TrieNode c : children) {
			if (c.getValue() == word.intern() && !c.deleted)
				return c;
		}
		return null;
	}

	public boolean allChildrenDeleted() {
		if (children == null)
			return true;
		for (TrieNode c : children) {
			if (!c.deleted)
				return false;
		}
		return true;
	}

	public void setChild(TrieNode child) {
		if (children == null) {
			children = new TrieNode[1];
			children[0] = child;
		} else {
			TrieNode[] temp = children;
			children = new TrieNode[temp.length + 1];
			System.arraycopy(temp, 0, children, 0, temp.length);
			children[children.length - 1] = child;
		}
	}

}
複製程式碼

定義一個 TrieTree 類代表樹物件,包含了樹的根節點。put方法用於將字串放到樹結構中,需要先遍歷檢測是否已經有字串字首,沒有則要建立對應的節點,然後新增到對應節點的子節點中。getremove操作都需要針對樹結構做處理,最終完成查詢和刪除,刪除操作為了方便僅僅是設定下指定節點的刪除標識。

public class TrieTree {

	protected TrieNode root;

	public TrieTree() {
		this.root = new TrieNode(null);
	}

	public void put(String word) throws IllegalArgumentException {
		if (word == null) {
			throw new IllegalArgumentException();
		}
		TrieNode current = this.root;
		for (String s : word.split("")) {
			TrieNode child = current.getChild(s);
			if (child == null) {
				child = new TrieNode(s);
				current.setChild(child);
			}
			current = child;
		}
	}

	public TrieNode get(String word) throws IllegalArgumentException {
		if (word == null) {
			throw new IllegalArgumentException();
		}
		TrieNode current = this.root;
		for (String s : word.split("")) {
			TrieNode child = current.getChild(s);
			if (child == null)
				return null;
			current = child;
		}
		return current;
	}

	public void remove(String word) {
		if (word == null || word.length() <= 0) {
			return;
		}
		for (int i = 0; i < word.length(); i++) {
			String sub_word = word.substring(0, word.length() - i);
			TrieNode current = this.root;
			for (String s : sub_word.split("")) {
				TrieNode child = current.getChild(s);
				if (child != null && (child.getChildren() == null || child.allChildrenDeleted()))
					child.setDeleted(true);
				current = child;
			}
		}
	}

}
複製程式碼

seg為分詞方法,它主要就是嘗試進行最大字串匹配,儘量匹配字典中最長詞,其中查詢是否存在字串在teri樹中查詢。

public List<String> seg(String text) {
		int flag = 0;
		int delta = 1;
		List<String> words = new ArrayList<String>();
		while (flag + delta <= text.length()) {
			String temp = text.substring(flag, flag + delta);
			if (tree.get(temp) != null) {
				if ((flag + delta) == text.length()) {
					words.add(temp);
					break;
				}
				delta++;
				continue;
			}
			words.add(temp.substring(0, temp.length() - 1));
			flag = flag + delta - 1;
			delta = 1;
		}
		return words;
	}
複製程式碼

-------------推薦閱讀------------

我的2017文章彙總——機器學習篇

我的2017文章彙總——Java及中介軟體

我的2017文章彙總——深度學習篇

我的2017文章彙總——JDK原始碼篇

我的2017文章彙總——自然語言處理篇

我的2017文章彙總——Java併發篇


跟我交流,向我提問:

這裡寫圖片描述

公眾號的選單已分為“讀書總結”、“分散式”、“機器學習”、“深度學習”、“NLP”、“Java深度”、“Java併發核心”、“JDK原始碼”、“Tomcat核心”等,可能有一款適合你的胃口。

為什麼寫《Tomcat核心設計剖析》

歡迎關注:

這裡寫圖片描述

相關文章