Trie樹 又稱單詞查找樹,前綴樹或鍵樹,是一種哈希樹的變種。典型應用是用於統計和排序大量的字符串(但不只限於字符串),因此常常被搜索引擎系統用於文本詞頻統計。它的優勢是:最大限度地減小無謂的字符串比較,查詢效率比哈希表高。這是百度百科作的概述。 html
Trie樹是一個很是實用的索引結構,它能夠穩定的以O(c)的時間複雜度,它的插入和查詢時間複雜度都爲 O(c),這是他的優勢。 Trie的核心思想是空間換時間。利用字符串的公共前綴來下降查詢時間的開銷以達到提升效率的目的。因此Trie 的最大缺點是空間消耗很高。 java
它有3個基本特性: apache
1)根節點不包含字符,除根節點外每個節點都只包含一個字符。 數組
2)從根節點到某一節點,路徑上通過的字符鏈接起來,爲該節點對應的字符串。 ui
3)每一個節點的全部子節點包含的字符都不相同。 搜索引擎
引入維基百科給的圖: spa
這是一個字符Trie樹,每一個節點通常來講包含倆部分:值域、指針列表。值域能夠任意對象,指針列表可使用數組、集合均可以。每一個節點都固定的表示一個字符串,即從根節點到該節點,路徑上通過的字符鏈接起來。如節點"to",所表明的字符就是從根節點到t節點的路徑字符"t"鏈接上t節點與to節點的路徑字符"o"。to節點的值是7。注意:節點內並不包含字符,這個字符是經過路徑表示的,通常是採用某個默認的規則,如將指針列表中的下標轉爲對應的ascll碼對應的字符,這樣就將下標與某個字符對應起來,這個字符就是路徑字符。 指針
struts2中也實現了個簡單Trie樹org.apache.struts2.util.PrefixTrie,是用於實現前綴檢索的。即當經過某個字符串檢索值時,優先採用該字符的最小前綴所對應的值。源碼以下: code
package org.apache.struts2.util; /** * Quickly matches a prefix to an object. * */ public class PrefixTrie { // supports 7-bit chars. private static final int SIZE = 128; Node root = new Node(); public void put(String prefix, Object value) { Node current = root; for (int i = 0; i < prefix.length(); i++) { char c = prefix.charAt(i); if (c > SIZE) throw new IllegalArgumentException("'" + c + "' is too big."); if (current.next[c] == null) current.next[c] = new Node(); current = current.next[c]; } current.value = value; } public Object get(String key) { Node current = root; for (int i = 0; i < key.length(); i++) { char c = key.charAt(i); if (c > SIZE) return null; current = current.next[c]; if (current == null) return null; if (current.value != null) return current.value; } return null; } static class Node { Object value; Node[] next = new Node[SIZE]; } }節點類型是經過內部類Node描述的。Node的值域是Object 實例(Object value;),指針列表是Node數組(Node[] next = new Node[SIZE];),數組大小是128,即128個ascll字符。數組的下標值與ascll碼值一一對應。 PrefixTrie內部預先實例化了根節點root(Node root = new Node();)。 PrefixTrie內部只有倆個方法:put、get。這讓咱們想起了java.util.Map,其實 PrefixTrie與Map功能很是類似,都是經過put賦值,經過get取值。二者的區別總結起來有三點:
1)兩者的內部實現原理不一樣。 orm
2)PrefixTrie的key值只能是字符串。
3)PrefixTrie的get是經過最小前綴檢索的。
下面分別看下get、put方法是怎樣實現的:
public void put(String prefix, Object value) { Node current = root; for (int i = 0; i < prefix.length(); i++) { char c = prefix.charAt(i); if (c > SIZE) throw new IllegalArgumentException("'" + c + "' is too big."); if (current.next[c] == null) current.next[c] = new Node(); current = current.next[c]; } current.value = value; }方法參數prefix是某個特定的字符串,對應java.util.Map 中的key。value是該字符串對應的值,對應java.util.Map 中的value。
put方法的工做過程實際上就是遍歷多叉樹(最多128叉)的過程,經過遍歷找到該字符串對應的節點,將值放入該節點中。規則是依次遍歷參數prefix的每一個字符,而後以該字符的ascll碼值爲下標,訪問節點的指針列表,從而找到子節點的路徑,在沿着這個路徑繼續找下去,直到最後一個字符。
第1句定義個臨時指針current初始指向根節點root,第2句遍歷prefix,char c = prefix.charAt(i)句取當前字符,if (c > SIZE)判斷字符是否超出範圍以避免指針列表數組越界。if (current.next[c] == null)以當前字符的ascll碼值爲下標判斷子節點是否爲空,爲空則生成個節點。current = current.next[c]將工做指針指向該子節點,繼續遍歷。當遍歷prefix結束時工做指針current指向的節點就是字符prefix對應的節點,將value放入該節點的值域中。而整個遍歷樹的路徑其實就是prefix表明的字符串。
public Object get(String key) { Node current = root; for (int i = 0; i < key.length(); i++) { char c = key.charAt(i); if (c > SIZE) return null; current = current.next[c]; if (current == null) return null; if (current.value != null) return current.value; } return null; }get方法的遍歷樹的過程和put方法是同樣的,只不過當發現某個節點的值域不爲空時當即返回這個值做爲get方法的返回,由此來實現最小前綴的匹配。
好比我經過PrefixTrie的put方法分別對字符串"abcde"、"abc"進行了賦值,"abcde"的值爲7,"abc"的值爲5,則當我經過get方法檢索值"abcd"、"abcde"時返回的都是5。由於"abcd"、"abcde"的最小前綴"abc"存在值,遍歷到"abc"時就會當即返回。
PrefixTrie在struts2中主要是實現經過form表單域來設置動態方法調用的。
好比有以下表單:
<form action="http://localhost:8080/mystruts2/common/user"> <input type="text" name="method:hello" /> <input type="text" name="myAge"/> <input type="submit" value="提交"> </form>action屬性定義了訪問的命名空間爲"/common",訪問的action爲"user", 訪問action的哪一個方法那 ?,這個由文本框<input type="text" name="method:hello" />設置,訪問"hello"方法。struts2內部維護一個PrefixTrie對象,並經過PrefixTrie的put方法put("method:", handle)往前綴"method:"存入一個對象handle,每一個前綴的handle都是不一樣的對象,這個handle對象是ParameterAction接口的一個實現類的實例,該對象主要負責從相似"method:hello"的字符串中截取出action的方法名(此處爲hello)。而在檢索某個handle時,是經過表單域提交上來的字符串"method:hello"調用PrefixTrie的get方法,從而檢索出前綴"method:"對應的handle對象,在經過這個handle對象截取出action的方法名,從而實現動態方法調用設置的。