字典树(Trie)
一、定義
Trie樹,又稱為前綴樹(Prefix Tree)、單詞查找樹或鍵樹,是一種多叉樹結(jié)構(gòu)。
二、基本性質(zhì)
- 根節(jié)點(diǎn)不包含字符,除根節(jié)點(diǎn)外的每一個(gè)子節(jié)點(diǎn)都包含一個(gè)字符。
- 從根節(jié)點(diǎn)到某一個(gè)節(jié)點(diǎn),路徑上經(jīng)過(guò)的字符連接起來(lái),為該節(jié)點(diǎn)對(duì)應(yīng)的字符串。
- 每個(gè)節(jié)點(diǎn)的所有子節(jié)點(diǎn)包含的字符互不相同。
三、代碼
#include <iostream>#include <string>using namespace std;#define ALPHABET_SIZE 26typedef struct trie_node {int count; // 記錄該節(jié)點(diǎn)代表的單詞的個(gè)數(shù)trie_node *children[ALPHABET_SIZE]; // 各個(gè)子節(jié)點(diǎn) }*trie;trie_node* create_trie_node() {trie_node* pNode = new trie_node();pNode->count = 0;for(int i=0; i<ALPHABET_SIZE; ++i)pNode->children[i] = NULL;return pNode; }void trie_insert(trie root, char* key) {trie_node* node = root;char* p = key;while(*p){if(node->children[*p-'a'] == NULL){node->children[*p-'a'] = create_trie_node();}node = node->children[*p-'a'];++p;}node->count += 1; }/*** 查詢:不存在返回0,存在返回出現(xiàn)的次數(shù)*/ int trie_search(trie root, char* key) {trie_node* node = root;char* p = key;while(*p && node!=NULL){node = node->children[*p-'a'];++p;}if(node == NULL)return 0;elsereturn node->count; }int main() {// 關(guān)鍵字集合char keys[][8] = {"the", "a", "there", "answer", "any", "by", "bye", "their"};trie root = create_trie_node();// 創(chuàng)建trie樹for(int i = 0; i < 8; i++)trie_insert(root, keys[i]);// 檢索字符串char s[][32] = {"Present in trie", "Not present in trie"};printf("%s --- %s\n", "the", trie_search(root, "the")>0?s[0]:s[1]);printf("%s --- %s\n", "these", trie_search(root, "these")>0?s[0]:s[1]);printf("%s --- %s\n", "their", trie_search(root, "their")>0?s[0]:s[1]);printf("%s --- %s\n", "thaw", trie_search(root, "thaw")>0?s[0]:s[1]);return 0; }四、優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
1、插入和查詢的效率很高,均是O(m),其中 m 是待插入/查詢的 字符串長(zhǎng)度 。
2、關(guān)于查詢,有人會(huì)說(shuō)hash表時(shí)間復(fù)雜度是O(1)不是更快?但是哈希搜索的效率取決于哈希函數(shù)的好壞,若一個(gè)壞的hash函數(shù)導(dǎo)致了很多沖突,效率不一定比Trie樹高。
3、Trie樹中不同的關(guān)鍵字不會(huì)產(chǎn)生沖突。
4、Trie樹中只有在允許一個(gè)關(guān)鍵字關(guān)聯(lián)多個(gè)值的情況下才有類似hash碰撞發(fā)生。
5、Trie樹不用求hash值,對(duì)短字符串有更快的速度。通常,求hash值也是需要遍歷字符串的(與hash函數(shù)相關(guān))。
Trie樹可以對(duì)關(guān)鍵字 按照字典序排序 (先序遍歷)。
字典排序(lexicographical order)是一種對(duì)于隨機(jī)變量形成序列的排序方法。其方法是,按照字母順序,或者數(shù)字小大順序,由小到大的形成序列。
6、每一顆Trie樹都可以被看做一個(gè)簡(jiǎn)單版的確定有限狀態(tài)的自動(dòng)機(jī)(DFA,deterministic finite automation),也就是說(shuō),對(duì)于一個(gè)任意給定屬于該自動(dòng)機(jī)的狀態(tài)(①)和一個(gè)屬于該自動(dòng)機(jī)字母表的字符(②),都可以根據(jù)給定的轉(zhuǎn)移函數(shù)(③)轉(zhuǎn)到下一個(gè)狀態(tài)。其中:
① 對(duì)于Trie樹的每一個(gè)節(jié)點(diǎn)都確定一個(gè)自動(dòng)機(jī)的狀態(tài)。
② 給定一個(gè)屬于該自動(dòng)機(jī)字母表的字符,在圖中可以看到根據(jù)不同字符形成的分支;
③ 從當(dāng)前節(jié)點(diǎn)進(jìn)入下一層次節(jié)點(diǎn)的過(guò)程進(jìn)過(guò)狀態(tài)轉(zhuǎn)移函數(shù)得出。
核心思想是:空間換時(shí)間,利用字符串的公共前綴來(lái)減少無(wú)謂的字符串比較以達(dá)到提高查詢效率的目的。
缺點(diǎn)
當(dāng)hash函數(shù)很好時(shí),Trie樹的查找效率低于哈希搜索。
空間消耗大。
五、應(yīng)用
1、字符串檢索
檢索、查詢功能是Trie樹最原始功能,思路就是從根節(jié)點(diǎn)開始一個(gè)一個(gè)字符進(jìn)行比較。
如果沿路比較,發(fā)現(xiàn)不同的字符,則表示該字符串在集合中不存在。
如果所有的字符全部比較并且完全相同,還需要判斷最后一個(gè)節(jié)點(diǎn)標(biāo)識(shí)位(標(biāo)記該節(jié)點(diǎn)是否為一個(gè)關(guān)鍵字)。
2、詞頻統(tǒng)計(jì)?
Trie樹常被搜索引擎用于文本詞頻統(tǒng)計(jì)。
思路:為了實(shí)現(xiàn)詞頻統(tǒng)計(jì),我們修改了節(jié)點(diǎn)結(jié)構(gòu),用一個(gè)整型變量count來(lái)計(jì)數(shù)。對(duì)每一個(gè)關(guān)鍵字執(zhí)行插入操作,若已存在,計(jì)數(shù)加1,若不存在,插入后count置 1。?
(1. 2. 都可以用hash table做)
3、字符串排序?
Trie樹可以對(duì)大量字符串按字典序進(jìn)行排序,思路也很簡(jiǎn)單:遍歷一次所有關(guān)鍵字,將它們?nèi)坎迦雝rie樹,樹的每個(gè)結(jié)點(diǎn)的所有兒子很顯然地按照字母表排序,然后先序遍歷輸出Trie樹中所有關(guān)鍵字即可。
4、前綴匹配?
例如:找出一個(gè)字符串集合中所有以ab開頭的字符串。我們只需要用所有字符串構(gòu)造一個(gè)trie樹,然后輸出以a->b->開頭的路徑上的關(guān)鍵字即可。 trie樹前綴匹配常用于搜索提示。如當(dāng)輸入一個(gè)網(wǎng)址,可以自動(dòng)搜索出可能的選擇。當(dāng)沒(méi)有完全匹配的搜索結(jié)果,可以返回前綴最相似的可能。
5、作為輔助結(jié)構(gòu)?
如后綴樹,AC自動(dòng)機(jī)?
有窮自動(dòng)機(jī) 參考資料:http://blog.csdn.net/yukuninfoaxiom/article/details/6057736
6、與哈希表相比?
優(yōu)點(diǎn):
缺點(diǎn):
7、與二叉搜索樹相比
二叉搜索樹,又稱二叉排序樹,它滿足:
其實(shí)二叉搜索樹的優(yōu)勢(shì)已經(jīng)在與查找、插入的時(shí)間復(fù)雜度上了,通常只有O(log n),很多集合都是通過(guò)它來(lái)實(shí)現(xiàn)的。在進(jìn)行插入的時(shí)候,實(shí)質(zhì)上是給樹添加新的葉子節(jié)點(diǎn),避免了節(jié)點(diǎn)移動(dòng),搜索、插入和刪除的復(fù)雜度等于樹的高度,屬于O(log n),最壞情況下整棵樹所有的節(jié)點(diǎn)都只有一個(gè)子節(jié)點(diǎn),完全變成一個(gè)線性表,復(fù)雜度是O(n)。
Trie樹在最壞情況下查找要快過(guò)二叉搜索樹,如果搜索字符串長(zhǎng)度用m來(lái)表示的話,它只有O(m),通常情況(樹的節(jié)點(diǎn)個(gè)數(shù)要遠(yuǎn)大于搜索字符串的長(zhǎng)度)下要遠(yuǎn)小于O(n)。
?
六、改進(jìn)
1、按位樹(Btiwise Trie):原理上和普通Trie樹差不多,只不過(guò)普通Trie樹存儲(chǔ)的最小單位是字符,但是Bitwise Trie存放的是位而已。位數(shù)據(jù)的存取由CPU指令一次直接實(shí)現(xiàn),對(duì)于二進(jìn)制數(shù)據(jù),它理論上要比普通Trie樹快。
2、節(jié)點(diǎn)壓縮
①分支壓縮: 對(duì)于穩(wěn)定的Trie樹,基本上都是查找和讀取的操作,完全可以把一些分支進(jìn)行壓縮。例如,下圖中最右側(cè)分支inn可以直接壓縮成一個(gè)節(jié)點(diǎn)“inn”,而不需要作為一個(gè)常規(guī)子樹存在。Radix樹就是根據(jù)這個(gè)原理來(lái)解決Trie樹過(guò)深的問(wèn)題。?
②節(jié)點(diǎn)映射表:這種方式也是Trie樹節(jié)點(diǎn)可能幾乎完全確定下采用的,針對(duì)Trie樹節(jié)點(diǎn)的每一個(gè)狀態(tài),如果狀態(tài)總數(shù)重復(fù)很多的話,通過(guò)一個(gè)元素為數(shù)字的多維數(shù)組(比如Triple Array Trie)來(lái)表示,這樣存儲(chǔ)Trie樹本身的空間開銷會(huì)小一些,雖然引入了額外的映射表。
3、雙數(shù)組TRIE樹(Double Array Trie)
它在保證Trie樹檢索速度的前提下,提高空間利用率而提出的一種數(shù)據(jù)結(jié)構(gòu),本質(zhì)上還是一個(gè)確定有限自動(dòng)機(jī)。(所謂DFA就是一個(gè)能夠?qū)崿F(xiàn)狀態(tài)專一的自動(dòng)機(jī),對(duì)于一個(gè)給定的屬于該自動(dòng)機(jī)的狀態(tài)和一個(gè)數(shù)據(jù)該自動(dòng)機(jī)字符表Σ的字符,它能夠根據(jù)預(yù)先給定的狀態(tài)轉(zhuǎn)移函數(shù)轉(zhuǎn)移到下一個(gè)狀態(tài)。)
對(duì)于DAT來(lái)說(shuō),每個(gè)節(jié)點(diǎn)代表自動(dòng)機(jī)的一個(gè)狀態(tài), 根據(jù)變量的不同,進(jìn)行狀態(tài)轉(zhuǎn)移,當(dāng)達(dá)到結(jié)束狀態(tài)或者無(wú)法轉(zhuǎn)移時(shí),完成查詢。
參考資料:http://blog.csdn.net/zzran/article/details/8462002
七、例題
http://acm.hdu.edu.cn/showproblem.php?pid=1251
http://acm.hdu.edu.cn/showproblem.php?pid=1671
八、參考文章
https://blog.csdn.net/hihozoo/article/details/51248823
http://www.raychase.net/1783?replytocom=264917
https://blog.csdn.net/v_july_v/article/details/6897097
http://www.cnblogs.com/tanky_woo/archive/2010/09/24/1833717.html
總結(jié)
- 上一篇: 浙江理工大学2019年1月赛
- 下一篇: 积性函数(Product_Functio