from:https://www.cnblogs.com/justinh/p/7716421.htmlphp
Trie,又常常叫前綴樹,字典樹等等。它有不少變種,如後綴樹,Radix Tree/Trie,PATRICIA tree,以及bitwise版本的crit-bit tree。固然不少名字的意義其實有交叉。html
在計算機科學中,trie,又稱前綴樹或字典樹,是一種有序樹,用於保存關聯數組,其中的鍵一般是字符串。與二叉查找樹不一樣,鍵不是直接保存在節點中,而是由節點在樹中的位置決定。一個節點的全部子孫都有相同的前綴,也就是這個節點對應的字符串,而根節點對應空字符串。通常狀況下,不是全部的節點都有對應的值,只有葉子節點和部份內部節點所對應的鍵纔有相關的值。node
trie中的鍵一般是字符串,但也能夠是其它的結構。trie的算法能夠很容易地修改成處理其它結構的有序序列,好比一串數字或者形狀的排列。好比,bitwise trie中的鍵是一串位元,能夠用於表示整數或者內存地址linux
基本性質git
1,根節點不包含字符,除根節點意外每一個節點只包含一個字符。web
2,從根節點到某一個節點,路徑上通過的字符鏈接起來,爲該節點對應的字符串。 面試
3,每一個節點的全部子節點包含的字符串不相同。算法
優勢:c#
能夠最大限度地減小無謂的字符串比較,故能夠用於詞頻統計和大量字符串排序。windows
跟哈希表比較:
1,最壞狀況時間複雜度比hash表好
2,沒有衝突,除非一個key對應多個值(除key外的其餘信息)
3,自帶排序功能(相似Radix Sort),中序遍歷trie能夠獲得排序。
1,雖然不一樣單詞共享前綴,但其實trie是一個以空間換時間的算法。其每個字符均可能包含至多字符集大小數目的指針(不包含衛星數據)。
每一個結點的子樹的根節點的組織方式有幾種。1>若是默認包含全部字符集,則查找速度快但浪費空間(特別是靠近樹底部葉子)。2>若是用連接法(如左兒子右兄弟),則節省空間但查找需順序(部分)遍歷鏈表。3>alphabet reduction: 減小字符寬度以減小字母集個數。,4>對字符集使用bitmap,再配合連接法。
2,若是數據存儲在外部存儲器等較慢位置,Trie會較hash速度慢(hash訪問O(1)次外存,Trie訪問O(樹高))。
3,長的浮點數等會讓鏈變得很長。可用bitwise trie改進。
bit-wise Trie
相似於普通的Trie,可是字符集爲一個bit位,因此孩子也只有兩個。
可用於地址分配,路由管理等。
雖然是按bit位存儲和判斷,但由於cache-local和可高度並行,因此性能很高。跟紅黑樹比,紅黑樹雖然紙面性能更高,可是由於cache不友好和串行運行多,瓶頸在存儲訪問延遲而不是CPU速度。
壓縮Trie
壓縮分支條件:
1,Trie基本不變
2,只是查詢
3,key跟結點的特定數據無關
4,分支很稀疏
若容許添加和刪除,就可能須要分裂和合並結點。此時可能須要對壓縮率和更新(裂,並)頻率進行折中。
外存Trie
某些變種如後綴樹適合存儲在外部,另外還有B-trie等。
(1) 字符串檢索
事先將已知的一些字符串(字典)的有關信息保存到trie樹裏,查找另一些未知字符串是否出現過或者出現頻率。
舉例:
1,給出N 個單詞組成的熟詞表,以及一篇全用小寫英文書寫的文章,請你按最先出現的順序寫出全部不在熟詞表中的生詞。
2,給出一個詞典,其中的單詞爲不良單詞。單詞均爲小寫字母。再給出一段文本,文本的每一行也由小寫字母構成。判斷文本中是否含有任何不良單詞。例如,若rob是不良單詞,那麼文本problem含有不良單詞。
3,1000萬字符串,其中有些是重複的,須要把重複的所有去掉,保留沒有重複的字符串。
(2)文本預測、自動完成,see also,拼寫檢查
(3)詞頻統計
1,有一個1G大小的一個文件,裏面每一行是一個詞,詞的大小不超過16字節,內存限制大小是1M。返回頻數最高的100個詞。
2,一個文本文件,大約有一萬行,每行一個詞,要求統計出其中最頻繁出現的前10個詞,請給出思想,給出時間複雜度分析。
3,尋找熱門查詢:搜索引擎會經過日誌文件把用戶每次檢索使用的全部檢索串都記錄下來,每一個查詢串的長度爲1-255字節。假設目前有一千萬個記錄,這些查詢串的重複度比較高,雖然總數是1千萬,可是若是去除重複,不超過3百萬個。一個查詢串的重複度越高,說明查詢它的用戶越多,也就越熱門。請你統計最熱門的10個查詢串,要求使用的內存不能超過1G。
(1) 請描述你解決這個問題的思路;
(2) 請給出主要的處理流程,算法,以及算法的複雜度。
==》若無內存限制:Trie + 「k-大/小根堆」(k爲要找到的數目)。
不然,先hash分段再對每個段用hash(另外一個hash函數)統計詞頻,再要麼利用歸併排序的某些特性(如partial_sort),要麼利用某使用外存的方法。參考
「海量數據處理之歸併、堆排、前K方法的應用:一道面試題」 http://www.dataguru.cn/thread-485388-1-1.html。
「算法面試題之統計詞頻前k大」 http://blog.csdn.net/u011077606/article/details/42640867
(4)排序
Trie樹是一棵多叉樹,只要先序遍歷整棵樹,輸出相應的字符串即是按字典序排序的結果。
好比給你N 個互不相同的僅由一個單詞構成的英文名,讓你將它們按字典序從小到大排序輸出。
(5)字符串最長公共前綴
Trie樹利用多個字符串的公共前綴來節省存儲空間,當咱們把大量字符串存儲到一棵trie樹上時,咱們能夠快速獲得某些字符串的公共前綴。
舉例:
給出N 個小寫英文字母串,以及Q 個詢問,即詢問某兩個串的最長公共前綴的長度是多少?
解決方案:首先對全部的串創建其對應的字母樹。此時發現,對於兩個串的最長公共前綴的長度即它們所在結點的公共祖先個數,因而,問題就轉化爲了離線(Offline)的最近公共祖先(Least Common Ancestor,簡稱LCA)問題。
而最近公共祖先問題一樣是一個經典問題,能夠用下面幾種方法:
1. 利用並查集(Disjoint Set),能夠採用採用經典的Tarjan 算法;
2. 求出字母樹的歐拉序列(Euler Sequence )後,就能夠轉爲經典的最小值查詢(Range Minimum Query,簡稱RMQ)問題了;
(6)字符串搜索的前綴匹配
trie樹經常使用於搜索提示。如當輸入一個網址,能夠自動搜索出可能的選擇。當沒有徹底匹配的搜索結果,能夠返回前綴最類似的可能。
Trie樹檢索的時間複雜度能夠作到n,n是要檢索單詞的長度,
若是使用暴力檢索,須要指數級O(n2)的時間複雜度。
(7) 做爲其餘數據結構和算法的輔助結構
如後綴樹,AC自動機等
後綴樹能夠用於全文搜索
轉一篇關於幾種Trie速度比較的文章:http://www.hankcs.com/nlp/performance-comparison-of-several-trie-tree.html
Trie樹和其它數據結構的比較 http://www.raychase.net/1783
參考:
[1] 維基百科:Trie, https://en.wikipedia.org/wiki/Trie
[2] LeetCode字典樹(Trie)總結, http://www.jianshu.com/p/bbfe4874f66f
[3] 字典樹(Trie樹)的實現及應用, http://www.cnblogs.com/binyue/p/3771040.html#undefined
[4] 6天通吃樹結構—— 第五天 Trie樹, http://www.cnblogs.com/huangxincheng/archive/2012/11/25/2788268.html
=============摘錄自維基百科============
In computer science, a trie, also called digital tree and sometimes radix tree or prefix tree (as they can be searched by prefixes), is a kind of search tree—an ordered tree data structure that is used to store a dynamic set or associative array where the keys are usually strings. Unlike a binary search tree, no node in the tree stores the key associated with that node; instead, its position in the tree defines the key with which it is associated. All the descendants of a node have a common prefix of the string associated with that node, and the root is associated with the empty string. Values are not necessarily associated with every node. Rather, values tend only to be associated with leaves, and with some inner nodes that correspond to keys of interest. For the space-optimized presentation of prefix tree, see compact prefix tree.
A trie for keys "A","to", "tea", "ted", "ten", "i", "in", and "inn".
In the example shown, keys are listed in the nodes and values below them. Each complete English word has an arbitrary integer value associated with it. A trie can be seen as a tree-shaped deterministic finite automaton. Each finite language is generated by a trie automaton, and each trie can be compressed into a deterministic acyclic finite state automaton.
Though tries are usually keyed by character strings,[not verified in body] they need not be. The same algorithms can be adapted to serve similar functions of ordered lists of any construct, e.g. permutations on a list of digits or shapes. In particular, a bitwise trie is keyed on the individual bits making up any fixed-length binary datum, such as an integer or memory address.[citation needed]
Tries were first described by René de la Briandais in 1959.[1][2]:336 The term trie was coined two years later by Edward Fredkin, who pronounces it /ˈtriː/ (as "tree"), after the middle syllable of retrieval.[3][4] However, other authors pronounce it /ˈtraɪ/ (as "try"), in an attempt to distinguish it verbally from "tree".[3][4][5]
As discussed below, a trie has a number of advantages over binary search trees.[6] A trie can also be used to replace a hash table, over which it has the following advantages:
Tries do have some drawbacks as well:
A common application of a trie is storing a predictive text or autocomplete dictionary, such as found on a mobile telephone. Such applications take advantage of a trie's ability to quickly search for, insert, and delete entries; however, if storing dictionary words is all that is required (i.e., storage of information auxiliary to each word is not required), a minimal deterministic acyclic finite state automaton (DAFSA) would use less space than a trie. This is because a DAFSA can compress identical branches from the trie which correspond to the same suffixes (or parts) of different words being stored.
Tries are also well suited for implementing approximate matching algorithms,[8] including those used in spell checking and hyphenation[4] software.
A discrimination tree term index stores its information in a trie data structure.[9]
Lookup and membership are easily described. The listing below implements a recursive trie node as a Haskell data type. It stores an optional value and a list of children tries, indexed by the next character:
import Data.Map data Trie a = Trie { value :: Maybe a, children :: Map Char (Trie a) }
We can look up a value in the trie as follows:
find :: String -> Trie a -> Maybe a find [] t = value t find (k:ks) t = do ct <- Data.Map.lookup k (children t) find ks ct
In an imperative style, and assuming an appropriate data type in place, we can describe the same algorithm in Python (here, specifically for testing membership). Note that children
is a list of a node's children; and we say that a "terminal" node is one which contains a valid word.
def find(node, key): for char in key: if char in node.children: node = node.children[char] else: return None return node.value == key
Insertion proceeds by walking the trie according to the string to be inserted, then appending new nodes for the suffix of the string that is not contained in the trie. In imperative Pascal pseudocode:
algorithm insert(root : node, s : string, value : any): node = root i = 0 n = length(s) while i < n: if node.child(s[i]) != nil: node = node.child(s[i]) i = i + 1 else: break (* append new nodes, if necessary *) while i < n: node.child(s[i]) = new node node = node.child(s[i]) i = i + 1 node.value = value
Lexicographic sorting of a set of keys can be accomplished with an inorder traversal over trie.
This algorithm is a form of radix sort.
A trie forms the fundamental data structure of Burstsort, which (in 2007) was the fastest known string sorting algorithm.[10] However, now there are faster string sorting algorithms.[11]
A special kind of trie, called a suffix tree, can be used to index all suffixes in a text in order to carry out fast full text searches.
There are several ways to represent tries, corresponding to different trade-offs between memory use and speed of the operations. The basic form is that of a linked set of nodes, where each node contains an array of child pointers, one for each symbol in the alphabet (so for the English alphabet, one would store 26 child pointers and for the alphabet of bytes, 256 pointers). This is simple but wasteful in terms of memory: using the alphabet of bytes (size 256) and four-byte pointers, each node requires a kilobyte of storage, and when there is little overlap in the strings' prefixes, the number of required nodes is roughly the combined length of the stored strings.[2]:341 Put another way, the nodes near the bottom of the tree tend to have few children and there are many of them, so the structure wastes space storing null pointers.[12]
The storage problem can be alleviated by an implementation technique called alphabet reduction, whereby the original strings are reinterpreted as longer strings over a smaller alphabet. E.g., a string of n bytes can alternatively be regarded as a string of 2n four-bit units and stored in a trie with sixteen pointers per node. Lookups need to visit twice as many nodes in the worst case, but the storage requirements go down by a factor of eight.[2]:347–352
An alternative implementation represents a node as a triple (symbol, child, next) and links the children of a node together as a singly linked list: child points to the node's first child, next to the parent node's next child.[12][13] The set of children can also be represented as a binary search tree; one instance of this idea is the ternary search tree developed by Bentley and Sedgewick.[2]:353
Another alternative in order to avoid the use of an array of 256 pointers (ASCII), as suggested before, is to store the alphabet array as a bitmap of 256 bits representing the ASCII alphabet, reducing dramatically the size of the nodes.[14]
Bitwise tries are much the same as a normal character-based trie except that individual bits are used to traverse what effectively becomes a form of binary tree. Generally, implementations use a special CPU instruction to very quickly find the first set bit in a fixed length key (e.g., GCC's __builtin_clz()
intrinsic). This value is then used to index a 32- or 64-entry table which points to the first item in the bitwise trie with that number of leading zero bits. The search then proceeds by testing each subsequent bit in the key and choosing child[0]
or child[1]
appropriately until the item is found.
Although this process might sound slow, it is very cache-local and highly parallelizable due to the lack of register dependencies and therefore in fact has excellent performance on modern out-of-order execution CPUs. A red-black tree for example performs much better on paper, but is highly cache-unfriendly and causes multiple pipeline and TLB stalls on modern CPUs which makes that algorithm bound by memory latency rather than CPU speed. In comparison, a bitwise trie rarely accesses memory, and when it does, it does so only to read, thus avoiding SMP cache coherency overhead. Hence, it is increasingly becoming the algorithm of choice for code that performs many rapid insertions and deletions, such as memory allocators (e.g., recent versions of the famous Doug Lea's allocator (dlmalloc) and its descendents).
Compressing the trie and merging the common branches can sometimes yield large performance gains. This works best under the following conditions:
For example, it may be used to represent sparse bitsets, i.e., subsets of a much larger, fixed enumerable set. In such a case, the trie is keyed by the bit element position within the full set. The key is created from the string of bits needed to encode the integral position of each element. Such tries have a very degenerate form with many missing branches. After detecting the repetition of common patterns or filling the unused gaps, the unique leaf nodes (bit strings) can be stored and compressed easily, reducing the overall size of the trie.
Such compression is also used in the implementation of the various fast lookup tables for retrieving Unicode character properties. These could include case-mapping tables (e.g. for the Greek letter pi, from ∏ to π), or lookup tables normalizing the combination of base and combining characters (like the a-umlaut in German, ä, or the dalet-patah-dagesh-ole in Biblical Hebrew, דַּ֫). For such applications, the representation is similar to transforming a very large, unidimensional, sparse table (e.g. Unicode code points) into a multidimensional matrix of their combinations, and then using the coordinates in the hyper-matrix as the string key of an uncompressed trie to represent the resulting character. The compression will then consist of detecting and merging the common columns within the hyper-matrix to compress the last dimension in the key. For example, to avoid storing the full, multibyte Unicode code point of each element forming a matrix column, the groupings of similar code points can be exploited. Each dimension of the hyper-matrix stores the start position of the next dimension, so that only the offset (typically a single byte) need be stored. The resulting vector is itself compressible when it is also sparse, so each dimension (associated to a layer level in the trie) can be compressed separately.
Some implementations do support such data compression within dynamic sparse tries and allow insertions and deletions in compressed tries. However, this usually has a significant cost when compressed segments need to be split or merged. Some tradeoff has to be made between data compression and update speed. A typical strategy is to limit the range of global lookups for comparing the common branches in the sparse trie.[citation needed]
The result of such compression may look similar to trying to transform the trie into a directed acyclic graph (DAG), because the reverse transform from a DAG to a trie is obvious and always possible. However, the shape of the DAG is determined by the form of the key chosen to index the nodes, in turn constraining the compression possible.
Another compression strategy is to "unravel" the data structure into a single byte array.[16] This approach eliminates the need for node pointers, substantially reducing the memory requirements. This in turn permits memory mapping and the use of virtual memory to efficiently load the data from disk.
One more approach is to "pack" the trie.[4] Liang describes a space-efficient implementation of a sparse packed trie applied to automatic hyphenation, in which the descendants of each node may be interleaved in memory.
Several trie variants are suitable for maintaining sets of strings in external memory, including suffix trees. A combination of trie and B-tree, called the B-trie has also been suggested for this task; compared to suffix trees, they are limited in the supported operations but also more compact, while performing update operations faster.[17]
This paper presents a method for direct building of minimal acyclic finite states automaton which recognizes a given finite list of words in lexicographical order. Our approach is to construct a minimal automaton in a single phase by adding new strings one by one and minimizing the resulting automaton on-the-fly
We present Tightly Packed Tries (TPTs), a compact implementation of read-only, compressed trie structures with fast on-demand paging and short load times. We demonstrate the benefits of TPTs for storing n-gram back-off language models and phrase tables for statistical machine translation. Encoded as TPTs, these databases require less space than flat text file representations of the same data compressed with the gzip utility. At the same time, they can be mapped into memory quickly and be searched directly in time linear in the length of the key, without the need to decompress the entire file. The overhead for local decompression during search is marginal.