1. 程式人生 > >三叉搜尋樹(Ternary Search Trie)和中文分詞原理分析

三叉搜尋樹(Ternary Search Trie)和中文分詞原理分析

三叉搜尋樹(Ternary Search Trie)

三叉搜尋樹是二叉搜尋樹和數字搜尋樹的混合體。它有和數字搜尋樹差不多的速度但是隻需要和二叉搜尋樹一樣相對較少的記憶體空間。在一個三叉搜尋樹中,每一個節點包含一個字元,和數字搜尋樹不同,三叉搜尋樹只有三個指標:一個指向左邊的樹;一個指向右邊的樹;還有一個向下,指向單詞的下一個資料單元。

三叉樹是否平衡取決於單詞的讀入順序。單詞的讀入順序對於建立平衡的三叉搜尋樹很重要,但對於二叉搜尋樹就不太重要。通過選擇一個排序後資料單元集合的中間值,並把它作為開始節點,我們可以建立一個平衡的三叉樹。可以寫一個專門的過程來生成平衡的三叉樹詞典。

取得平衡的單詞排序類似於洗撲克牌。假想有若干張撲克牌,每張牌對應一個單詞,先把牌排好序,然後取最中間的一張牌,單獨放著。剩下的牌分成了兩摞,左邊一摞牌中也取最中間的一張放在取出來的那張牌後面。右邊一摞牌中也取最中間的一張放在取出來的牌後面,以此類推。

/**
	 * 在呼叫此方法前,先把詞典陣列dict排好序
	 * 
	 * @param fp
	 *            寫入平衡序的詞典
	 * @param dict
	 *            排好序的詞典陣列
	 * @param offset
	 *            偏移量
	 * @param len
	 *            長度
	 * @throws IOException
	 */
	public void outputBalanced(BufferedWriter fp, ArrayList<String> dict, int offset, int len) throws IOException {
		int temp;
		if ( len < 1 ) return;
			
		temp = len >> 1;   // temp=len/2

		String item = dict.get( temp + offset );
		fp.write(item);// 把詞條寫入到檔案
		fp.write("\n");

		outputBalanced(fp, dict, offset, temp); // 輸出左半部分
		outputBalanced(fp, dict, offset + temp + 1, len - temp - 1); // 輸出右半部分
	}

以有序的資料單元(as  at  be  by  he  in  is it  of  on  or  to)為例。 首先我們把關鍵字"is"作為中間值並且構建一個包含字母"i"的根節點。它的直接後繼節點包含字母"s"並且可以儲存任何與"is"有關聯的資料。對於"i"的左樹,我們選擇"be"作為中間值並且建立一個包含字母"b"的節點,字母"b"的直接後繼節點包含"e"。該資料儲存在"e"節點。對於"i"的右樹,按照邏輯,選擇"on"作為中間值,並且建立"o"節點以及它的直接後繼節點"n"。最終的三叉樹如圖:


垂直的虛線代表一個父節點下的直接後繼節點。只有父節點和它的直接後繼節點才能形成一個數據單元的關鍵字

;"i"和"s"形成關鍵字"is",但是"i"和"b"不能形成關鍵字,因為它們之間僅用一條斜線相連,不具有直接後繼關係。圖中帶圈的節點為終止節點,如果查詢一個詞以終止節點結束,則說明三叉樹包含這個詞。從根節點開始查詢單詞,以搜尋單詞"is"為例,向下到相等的孩子節點"s",在兩次比較後找到"is"。查詢"ax"時,執行三次比較達到首字元"a",然後經過兩次比較到達第二個字元"x",返回結果是"ax"不在樹中。

TernarySearchTrie本身儲存關鍵字到值的對應關係,可以當做HashMap物件來使用。關鍵字按照字元拆分成許多節點,以TSTNode的例項存在。值儲存在TSTNode的data屬性中。TSTNode的實現程式碼如下:
public final class TSTNode {
	/** 節點的值 */
	public Data			data	= null;	// data屬性可以儲存 詞原文和詞性、詞頻等相關的資訊

	protected TSTNode	loNode;		// 左邊節點
	protected TSTNode	eqNode;		// 中間節點
	protected TSTNode	hiNode;		// 右邊節點

	protected char		splitchar;		// 本節點表示的字元

	/**
	 * 構造方法
	 * 
	 * @param splitchar
	 *            該節點表示的字元
	 */
	protected TSTNode(char splitchar) {
		this.splitchar = splitchar;
	}

	public String toString() {
		return "splitchar:" + splitchar;
	}
}

查詢詞典的基本過程是:輸入一個詞,返回這個詞對應的TSTNode物件,如果該詞不在詞典中則返回空。在查詢詞典的過程中,從樹的根節點匹配Key,按Char從前往後匹配Key。charIndex表示Key當前要比較的Char的位置。

protected TSTNode getNode(String key, TSTNode startNode) {
		if (key == null) {
			return null;
		}
		int len = key.length();
		if (len == 0)
			return null;
		TSTNode currentNode = startNode; // 匹配過程中當前節點的位置
		int charIndex = 0;
		char cmpChar = key.charAt(charIndex);
		int charComp;
		while (true) {
			if (currentNode == null) {// 沒找到
				return null;
			}
			charComp = cmpChar - currentNode.splitchar;
			if (charComp == 0) {// 相等
				charIndex++;
				if (charIndex == len) {// 找到了
					return currentNode;
				} else {
					cmpChar = key.charAt(charIndex);
				}
				currentNodecurrentNode = currentNode.eqNode;
			} else if (charComp < 0) {// 小於
				currentNodecurrentNode = currentNode.loNode;
			} else {// 大於
				currentNodecurrentNode = currentNode.hiNode;
			}
		}
	}

三叉樹的建立過程也就是在Trie樹上建立和單詞對應的節點。實現程式碼如下:
// 向詞典樹中加入一個單詞的過程
	private TSTNode addWord(String key) {
		TSTNode currentNode = root; // 從樹的根節點開始查詢
		int charIndex = 0; // 從詞的開頭匹配
		while (true) {
			// 比較詞的當前字元與節點的當前字元
			int charComp = key.charAt(charIndex) - currentNode.splitchar;
			if (charComp == 0) {// 相等
				charIndex++;
				if (charIndex == key.length()) {
					return currentNode;
				}
				if (currentNode.eqNode == null) {
					currentNode.eqNode = new TSTNode(key.charAt(charIndex));
				}
				currentNodecurrentNode = currentNode.eqNode;
			} else if (charComp < 0) {// 小於
				if (currentNode.loNode == null) {
					currentNode.loNode = new TSTNode(key.charAt(charIndex));
				}
				currentNodecurrentNode = currentNode.loNode;
			} else {// 大於
				if (currentNode.hiNode == null) {
					currentNode.hiNode = new TSTNode(key.charAt(charIndex));
				}
				currentNodecurrentNode = currentNode.hiNode;
			}
		}
	}

相對於查詢過程,建立過程在搜尋過程中判斷出連結的空值後建立相關的節點,而不是碰到空值後結束搜尋過程並返回空值。

同一個詞可以有不同的詞性,例如"朝陽"既可能是一個"區",也可能是一個"市"。可以把這些和某個詞的詞性相關的資訊放在同一個連結串列中。這個連結串列可以儲存在TSTNode 的Data屬性中。

中文分詞的原理

中文分詞有以下兩類方法。

機械匹配的方法:例如正向最大長度匹配(ForwardMaximum Match)的方法和逆向最大長度匹配(Reverse Maximum Matching)的方法。

統計的方法:例如最大概率分詞方法和最大熵分詞方法等。

正向最大長度匹配的分詞方法實現起來很簡單。每次從詞典中查詢和待匹配串字首最長匹配的詞,如果找到匹配詞,則把這個詞作為切分詞,待匹配串減去該詞;如果詞典中沒有詞與其匹配,則按單字切分。

例如:"有意見分歧"這句話,正向最大長度切分的結果是"有意/見/分歧",逆向最大長度切分的結果是"有/意見/分歧"。因為漢語的主幹成分後置,所以逆向最大長度切分的精確度稍高

例如,Trie樹結構的詞典中包括如下的詞語:

 大  大學   大學生   活動   生活   中   中心  心

為了形成平衡的Trie樹,把詞先排序,結果為:

 中   中心   大   大學  大學生   心   活動  生活

按平衡方式生成的詞典Trie樹如圖所示,其中粗黑顯示的節點可以作為匹配終止節點。


例如:要搜尋“大學生活動中心”。 按照正向最大長度匹配原則,將字串“大學生活動中心”拆分為:大學生、活動、中心。

從Trie樹搜尋最長匹配單詞的方法如下所示:

public String matchLong(String key, int offset) { // 輸入字串和匹配的開始位置
		String ret = null;
		if (key == null || rootNode == null || "".equals(key)) {
			return ret;
		}
		TSTNode currentNode = rootNode;
		int charIndex = offset;
		while (true) {
			if (currentNode == null) {
				return ret;
			}
			int charComp = key.charAt(charIndex) - currentNode.spliter;

			if (charComp == 0) {
				charIndex++;

				if (currentNode.data != null) {
					ret = currentNode.data; // 候選最長匹配詞
				}
				if (charIndex == key.length()) {
					return ret; // 已經匹配完
				}
				currentNodecurrentNode = currentNode.eqNode;
			} else if (charComp < 0) {
			} else {
			}
		}
	}

測試matchLong方法如下所示:

 String sentence = "大學生活動中心";//輸入字串  
 int offset = 0;//匹配的開始位置  
 String ret = dic.matchLong(sentence,offset); 
 System.out.println(sentence+" match:"+ret);  

返回結果如下所示:

大學生活動中心match:大學生 

正向最大長度分詞的實現程式碼如下所示:

	public void wordSegment(String sentence) {// 傳入一個字串作為要處理的物件
		int senLen = sentence.length();// 首先計算出傳入的字串的字元長度
		int i = 0;// 控制匹配的起始位置

		while (i < senLen) {// 如果i小於此字串的長度就繼續匹配
			String word = dic.matchLong(sentence, i);// 正向最大長度匹配
			if (word != null) {// 已經匹配上
				// 下次匹配點在這個詞之後
				i += word.length();
				// 如果這個詞是詞庫中的那麼就打印出來
				System.out.print(word + " ");
			} else {// 如果在詞典中沒有找到匹配上的詞,就按單字切分
				word = sentence.substring(i, i + 1);
				// 列印一個字
				System.out.print(word + " ");
				++i;// 下次匹配點在這個字元之後
			}
		}
	}

因為採用了Trie樹結構查詢單詞,所以和用HashMap查詢單詞的方式比較起來,這種實現方法程式碼更簡單,而且切分速度更快。








相關推薦

三叉搜尋Ternary Search Trie中文原理分析

三叉搜尋樹(Ternary Search Trie) 三叉搜尋樹是二叉搜尋樹和數字搜尋樹的混合體。它有和數字搜尋樹差不多的速度但是隻需要和二叉搜尋樹一樣相對較少的記憶體空間。在一個三叉搜尋樹中,每一個節點包含一個字元,和數字搜尋樹不同,三叉搜尋樹只有三個指標:一個指向左邊

二叉搜尋binary search tree

性質 設 x 是二叉搜尋樹中的一個結點。如果 y 是 x 左子樹中的一個結點,那麼 y.key ≤ x.key。如果 y 是 x 右子樹中的一個結點,那麼 y.key ≥ x.key。 特性 期望高度:height = O(lgn) 基本操作平均時間複雜度

自定義2:二分搜尋Binary Search Tree

二分搜尋樹也是一種二叉樹。       二分搜尋樹的遍歷:   層序遍歷圖解:   刪除任意元素圖解:       程式碼實現: packa

【資料結構05】紅-黑基礎----二叉搜尋Binary Search Tree

目錄 1、二分法引言 2、二叉搜尋樹定義 3、二叉搜尋樹的CRUD 4、二叉搜尋樹的兩種極端情況 5、二叉搜尋樹總結 前言 在【演算法04】樹與二叉樹中,已經介紹

二分搜尋Binary Search Tree

[TOC] ###什麼是二叉樹?   在實現二分搜尋樹之前,我們先思考一下,為什麼要有樹這種資料結構呢?我們通過企業的組織機構、檔案儲存、資料庫索引等這些常見的應用會發現,將資料使用樹結構儲存後,會出奇的高效,樹結構本身是一種天然的組織結構。常見的樹結構有:二分搜尋樹、平衡二叉樹(常見的平

”據結構一:二叉搜尋Binary Search Tree, BST

前言 定義 來源 演算法 資料結構 查 遍歷 增 刪 總結 參閱 前言 想寫兩篇關於AVL樹和B樹的較為詳細的介紹,發現需要先介紹二叉搜尋樹作為先導。 定義 二叉搜尋樹(Binary Search Thee, BST),也被稱為二

資料機構與演算法:二叉查詢Binary Search TreeJava實現

個人總結,如有錯誤,感謝指正 二叉查詢樹(Binary Search Tree) 一、簡介 二叉樹(Binary Tree):每個節點最多有兩個子節點的樹。 二叉查詢樹(binary srarch tree):具有如下性質的二叉樹稱為二叉查詢樹

二叉查詢binary search tree——python實現

二叉查詢樹(binary search tree) 顧名思義二叉查詢樹中每個節點至多有兩個子節點,並且還對儲存於每個節點中的關鍵字值有個小小的要求, 即只要這個節點有左節點或右節點,那麼其關鍵字值總的大於其左節點的關鍵字值,小於其右節點的關鍵字值,如下圖: 因為樹的結

二叉查詢Binary Search Tree

private void mirror(Node rootNode) { if (rootNode != null) { // do the sub-trees mirror(rootNode.leftChild); mirror(rootNod

Lucene——中文

實現 ext cse ron -a tag 大小 -c .com 1. 什麽是中文分詞器 對於英文,是安裝空格、標點符號進行分詞 對於中文,應該安裝具體的詞來分,中文分詞就是將詞,切分成一個個有意義的詞。 比如:“我的中國人”,分詞:我、的、中

隱馬爾可夫模型HMM jieba原始碼的理解

在理解隱馬爾可夫模型(HMM)時,看到的很好的部落格,記錄一下: 1. 隱馬爾可夫模型(HMM) - 1 - 基本概念:http://blog.csdn.net/xueyingxue001/article/details/51435728 2.隱馬爾可夫模型(HMM) - 2 -

NLP詞法分析中文

##1.中文分詞介紹 中文分詞相較於英文分詞要難許多,因為英文字身就是由單詞與空格組成的,而中文則是由獨立的字組成的,但同時語義卻是有詞來表達的。因此對於中文的分析與研究,首先應尋找合適的方法進行分詞。現有的中文分詞技術主要分為規則分詞,統計分詞與規則加統計相結

solr 6.2.0系列教程IK中文器配置及新增擴充套件、停止、同義詞

前言 2、solr的不同版本,對應不同版本的IK分詞器。由於IK 2012年停止更新了。所以以前的版本不適合新版的solr。 有幸在網上扒到了IK原始碼自己稍微做了調整,用來相容solr6.2.0版本。IK原始碼下載地址 步驟 1、解壓下載的src.rar壓縮包,這是我建

python自然語言處理中文預處理、統計詞頻

一個小的嘗試。。資料來源資料集 一共200條關於手機的中文評論,以XML格式儲存。分詞工具 python-jieba預處理包括去停用詞、去標點符號和數字去停用詞:使用的是他人總結的 停用詞表去標點符號和數字:用正則表示式。原本打算的是中文標點符號從網上覆制,英文標點符號用st

Python自然語言處理實戰3中文技術

3.1、中文分詞簡介       在英文中,單詞本身就是“詞”的表達,一篇英文文章就是“單詞”加分隔符(空格)來表示的,而在漢語中,詞以字為基本單位的,但是一篇文章的語義表達卻仍然是以詞來劃分的。       自中文自動分詞被提出以來,歷經將近30年的探索,提出了很多方法,可

python自然語言處理NLP1------中文1,基於規則的中文方法

python中文分詞方法之基於規則的中文分詞 目錄 常見中文分詞方法 推薦中文分詞工具 參考連結 一、四種常見的中文分詞方法: 基於規則的中文分詞 基於統計的中文分詞 深度學習中文分詞 混合分詞方法 基於規則的中

一個簡單最大正向匹配Maximum MatchingMM中文演算法的實現

1.構建詞典記憶體樹的TrieNode節點類:       package cn.wzb.segmenter.mm.bean; import java.util.HashMap; /** * 構建記憶體詞典的Trie樹結點 * */ public cla

自然語言處理入門4——中文原理工具介紹

本文首先介紹下中文分詞的基本原理,然後介紹下國內比較流行的中文分詞工具,如jieba、SnowNLP、THULAC、NLPIR,上述分詞工具都已經在github上開源,後續也會附上github連結,以供參考。 1.中文分詞原理介紹 1.1 中文分詞概述 中

HMM隱馬爾科夫用於中文

隱馬爾可夫模型(Hidden Markov Model,HMM)是用來描述一個含有隱含未知引數的馬爾可夫過程。 本文閱讀了2篇blog,理解其中的意思,附上自己的程式碼,共同學習。 一、理解隱馬爾科夫  1.1 舉例理解 來源:< http