海量數據處理-Trie樹

http://blog.csdn.net/beiyeqingteng/article/details/6981263

http://blog.csdn.net/zmazon/article/details/8227610#

關注Trie 這種結構已經很久,Trie有一個很有趣的用途,那就是自動提示。而且,前不久在一次面試裏,也需要用Trie來解答。所以,在此對這個數據結構進行總結。

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

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

下面這個圖就是Trie的表示,每一條邊表示一個字符,如果結束,就用星號表示。在這個Trie結構裏,我們有下面字符串,比如do, dork, dorm等,但是Trie裏沒有ba, 也沒有sen,因爲在a, 和n結尾,沒有結束符號(星號)。

                                   


我們來看看Trie樹的特點:根節點爲空值,剩下每一個節點保存一個字母。知道這些就夠了!

我們再來看看這棵樹能幹什麼?如果從根節點遍歷到某一個節點把路徑節點的值連在一起就構成了一個字符串,利用這個特點很容易想到這棵樹的第一個功能能幫我們查找某一個單詞是否在樹中(需要在每一個節點設置一個標誌,表示從根節點到此節點是否構成一個單詞);如果該單詞存在,我們可以利用它實現第二個功能:去除重複單詞;同樣如果該詞存,在我們還可以看出它的第三個功能:統計單詞頻率;因爲這是一個樹形結構我們利用這個特點很容易看出它的第四個功能能幫我們查找N個單詞的最長公共前綴;如果我們按順序遍歷輸出整棵樹,發現它的第五個功能:對字符串排序。


這棵樹創建看起來比較容易,就有一個問題需要我們考慮:父節點如何保存孩子節點? 主要有兩種方式供大家參考:

1.因爲是英文字符,我們可以用Node[26]來保存孩子節點(如果是數字我們可以用Node[10]),這種方式最快,但是並不是所有節點都會有很多孩子,所以這種方式浪費的空間太多

2.用一個鏈表根據需要動態添加節點。這樣我們就可以省下不小的空間,但是缺點是搜索的時候需要遍歷這個鏈表,增加了時間複雜度。

class TrieNode{//結點類
	
	private static final int  NUMBER = 26;
	private char _value;
	private boolean _isWord;//從根節點到這個節點存不存在一個單詞
	TrieNode[] _children = new TrieNode[NUMBER];//子結點集合
	
	public TrieNode(char c) {
		this.setValue(c);
	}
	public char getValue() {
		return _value;
	}
	public void setValue(char _value) {
		this._value = _value;
	}
	public boolean isWord() {
		return _isWord;
	}
	public void setIsWord(boolean _isWord) {
		this._isWord = _isWord;
	}
	

}

public class TrieTree {
	
	static String[] _words = {"add","am","good","the","think"};//待插入單詞

	private boolean searchWord(TrieNode _root, String _word) {
	
		if(null == _root || null == _word || "".equals(_word))
			return false;
		char[] cs = _word.toCharArray();//將字符串轉化爲字符數組
		for(int i = 0; i < cs.length; i++){
			
			int index;
			if(cs[i] >= 'A' && cs[i] <= 'Z'){
				index = cs[i]-'A';
			}
			else if(cs[i] >= 'a' && cs[i] <= 'z') 
				index = cs[i] - 'a';
			else
				return false;
			
			TrieNode child_node = _root._children[index];
				
			if(null != child_node){//找到相同字符
				if(child_node.isWord())//如果找到該單詞
					return true;
			}				
			
			if(null == child_node)//如果在i層沒找到相同字符	
				return false;
			_root = child_node;//重設根節點
			
			
		}
		return false;
	}


	private void insertIntoTree(TrieNode _root, String _word) {//插入一個單詞
		
		if(null == _root || null == _word || "".equals(_word))
			return;
		char[] cs = _word.toCharArray();//將字符串轉化爲字符數組
		for(int i = 0; i < cs.length; i++){
			
			int index;//對應的索引值
			if(cs[i] >= 'A' && cs[i] <= 'Z'){
				index = cs[i]-'A';
			}
			else if(cs[i] >= 'a' && cs[i] <= 'z') 
				index = cs[i] - 'a';
			else
				return;
			
			TrieNode child_node = _root._children[index];
			if(null == child_node){//如果沒找到
				TrieNode new_node = new TrieNode(cs[i]);//創建新節點
				if(i == cs.length-1)//如果遍歷到該單詞最後一個字符
					new_node.setIsWord(true);//把該單詞存在樹中
				_root._children[index] = new_node;//連接該節點
				_root = new_node;
				
			}else
				_root = child_node;//更新樹根
			
			
		}
	}

	private void printTree(TrieNode _root,char[] _word,int index) {
		
		if(_root == null)
			return;
		if(_root.isWord()){//如果根節點到此節點構成一個單詞則輸出
			for(char c : _word){
				if(c != ' ')
					System.out.print(c);
			}
				
			System.out.println();
		}
			
		for(TrieNode node : _root._children){//遍歷樹根孩子節點
			if(node != null){//回溯法遍歷該樹
				_word[index++] = node.getValue();
				printTree(node,_word,index);
				_word[index] = ' ';
				index--;
			}
		}
			
	}
	public static void main(String[] args){
		TrieTree _tree = new TrieTree();//創建一棵樹
		TrieNode _root = new TrieNode(' ');//創建根節點
		for(String word : _words)//插入單詞
			_tree.insertIntoTree(_root,word);
		char[] _word = new char[20];
		_tree.printTree(_root,_word,0);//打印樹中單詞
		boolean status = _tree.searchWord(_root,"think");//查詢樹中是否存在某單詞
		System.out.println(status);
	}
}

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