字典數Trie樹詳解及其應用
最近在看字串演算法了,其中字典樹、AC自動機和字尾樹的應用是最廣泛的了,下面將會重點介紹下這幾個演算法的應用。
字典樹(Trie)可以儲存一些字串->值的對應關係。基本上,它跟 Java 的 HashMap 功能相同,都是 key-value 對映,只不過 Trie 的 key 只能是字串。
Trie 的強大之處就在於它的時間複雜度。它的插入和查詢時間複雜度都為 O(k) ,其中 k 為 key 的長度,與 Trie 中儲存了多少個元素無關。Hash 表號稱是 O(1) 的,但在計算 hash 的時候就肯定會是 O(k) ,而且還有碰撞之類的問題;Trie 的缺點是空間消耗很高。
至於Trie樹的實現,可以用陣列,也可以用指標動態分配,我做題時為了方便就用了陣列,靜態分配空間。
Trie樹,又稱單詞查詢樹或鍵樹,是一種樹形結構,是一種雜湊樹的變種。典型應用是用於統計和排序大量的字串(但不僅限於字串),所以經常被搜尋引擎系統用於文字詞頻統計。它的優點是:最大限度地減少無謂的字串比較,查詢效率比雜湊表高。
Trie的核心思想是空間換時間。利用字串的公共字首來降低查詢時間的開銷以達到提高效率的目的。
Trie樹的基本性質可以歸納為:
(1)根節點不包含字元,除根節點意外每個節點只包含一個字元。
(2)從根節點到某一個節點,路徑上經過的字元連線起來,為該節點對應的字串。
(3)每個節點的所有子節點包含的字串不相同。
Trie樹有一些特性:
1)根節點不包含字元,除根節點外每一個節點都只包含一個字元。
2)從根節點到某一節點,路徑上經過的字元連線起來,為該節點對應的字串。
3)每個節點的所有子節點包含的字元都不相同。
4)如果字元的種數為n,則每個結點的出度為n,這也是空間換時間的體現,浪費了很多的空間。
5)插入查詢的複雜度為O(n),n為字串長度。
基本思想(以字母樹為例):
1、插入過程
對於一個單詞,從根開始,沿著單詞的各個字母所對應的樹中的節點分支向下走,直到單詞遍歷完,將最後的節點標記為紅色,表示該單詞已插入Trie樹。
2、查詢過程
同樣的,從根開始按照單詞的字母順序向下遍歷trie樹,一旦發現某個節點標記不存在或者單詞遍歷完成而最後的節點未標記為紅色,則表示該單詞不存在,若最後的節點標記為紅色,表示該單詞存在。
二、字典樹的資料結構:
利用串構建一個字典樹,這個字典樹儲存了串的公共字首資訊,因此可以降低查詢操作的複雜度。
下面以英文單詞構建的字典樹為例,這棵Trie樹中每個結點包括26個孩子結點,因為總共有26個英文字母(假設單詞都是小寫字母組成)。
則可宣告包含Trie樹的結點資訊的結構體:
- typedefstruct Trie_node
- {
- int count; // 統計單詞前綴出現的次數
- struct Trie_node* next[26]; // 指向各個子樹的指標
-
bool exist;
- }TrieNode , *Trie;
如給出字串"abc","ab","bd","dda",根據該字串序列構建一棵Trie樹。則構建的樹如下:
Trie樹的根結點不包含任何資訊,第一個字串為"abc",第一個字母為'a',因此根結點中陣列next下標為'a'-97的值不為NULL,其他同理,構建的Trie樹如圖所示,紅色結點表示在該處可以構成一個單詞。很顯然,如果要查詢單詞"abc"是否存在,查詢長度則為O(len),len為要查詢的字串的長度。而若採用一般的逐個匹配查詢,則查詢長度為O(len*n),n為字串的個數。顯然基於Trie樹的查詢效率要高很多。
如上圖中:Trie樹中存在的就是abc、ab、bd、dda四個單詞。在實際的問題中可以將標記顏色的標誌位改為數量count等其他符合題目要求的變數。
已知n個由小寫字母構成的平均長度為10的單詞,判斷其中是否存在某個串為另一個串的字首子串。下面對比3種方法:
1、 最容易想到的:即從字串集中從頭往後搜,看每個字串是否為字串集中某個字串的字首,複雜度為O(n^2)。
2、 使用hash:我們用hash存下所有字串的所有的字首子串。建立存有子串hash的複雜度為O(n*len)。查詢的複雜度為O(n)* O(1)= O(n)。
3、 使用Trie:因為當查詢如字串abc是否為某個字串的字首時,顯然以b、c、d....等不是以a開頭的字串就不用查找了,這樣迅速縮小查詢的範圍和提高查詢的針對性。所以建立Trie的複雜度為O(n*len),而建立+查詢在trie中是可以同時執行的,建立的過程也就可以成為查詢的過程,hash就不能實現這個功能。所以總的複雜度為O(n*len),實際查詢的複雜度只是O(len)。
三、Trie樹的操作
在Trie樹中主要有3個操作,插入、查詢和刪除。一般情況下Trie樹中很少存在刪除單獨某個結點的情況,因此只考慮刪除整棵樹。
1、插入
假設存在字串str,Trie樹的根結點為root。i=0,p=root。
1)取str[i],判斷p->next[str[i]-97]是否為空,若為空,則建立結點temp,並將p->next[str[i]-97]指向temp,然後p指向temp;
若不為空,則p=p->next[str[i]-97];
2)i++,繼續取str[i],迴圈1)中的操作,直到遇到結束符'\0',此時將當前結點p中的 exist置為true。
2、查詢
假設要查詢的字串為str,Trie樹的根結點為root,i=0,p=root
1)取str[i],判斷判斷p->next[str[i]-97]是否為空,若為空,則返回false;若不為空,則p=p->next[str[i]-97],繼續取字元。
2)重複1)中的操作直到遇到結束符'\0',若當前結點p不為空並且 exist 為true,則返回true,否則返回false。
3、刪除
刪除可以以遞迴的形式進行刪除。
字首查詢的典型應用:
http://acm.hdu.edu.cn/showproblem.php?pid=1251
- #include<iostream>
- #include<cstring>
- usingnamespace std;
- typedefstruct Trie_node
- {
- int count; // 統計單詞前綴出現的次數
- struct Trie_node* next[26]; // 指向各個子樹的指標
- bool exist; // 標記該結點處是否構成單詞
- }TrieNode , *Trie;
- TrieNode* createTrieNode()
- {
- TrieNode* node = (TrieNode *)malloc(sizeof(TrieNode));
- node->count = 0;
- node->exist = false;
- memset(node->next , 0 , sizeof(node->next)); // 初始化為空指標
- return node;
- }
- void Trie_insert(Trie root, char* word)
- {
- Trie node = root;
- char *p = word;
- int id;
- while( *p )
- {
- id = *p - 'a';
- if(node->next[id] == NULL)
- {
- node->next[id] = createTrieNode();
- }
- node = node->next[id]; // 每插入一步,相當於有一個新串經過,指標向下移動
- ++p;
- node->count += 1; // 這行程式碼用於統計每個單詞前綴出現的次數(也包括統計每個單詞出現的次數)
- }
- node->exist = true; // 單詞結束的地方標記此處可以構成一個單詞
- }
- int Trie_search(Trie root, char* word)
- {
- Trie node = root;
- char *p = word;
- int id;
- while( *p )
- {
- id = *p - 'a';
- node = node->next[id];
- ++p;
- if(node == NULL)
- return 0;
- }
- return node->count;
- }
- int main(void)
- {
- Trie root = createTrieNode(); // 初始化字典樹的根節點
- char str[12] ;
- bool flag = false;
- while(gets(str))
- {
- if(flag)
- printf("%d\n",Trie_search(root , str));
- else
- {
- if(strlen(str) != 0)
- {
- Trie_insert(root , str);
- }
- else
- flag = true;
- }
- }
- return 0;
- }
- #include<iostream>
- #include<cstring>
- usingnamespace std;
- typedefstruct Trie_node
- {
- int count; // 統計單詞前綴出現的次數
- struct Trie_node* next[26]; // 指向各個子樹的指標
- bool exist; // 標記該結點處是否構成單詞
- char trans[11]; // 翻譯
- }TrieNode , *Trie;
- TrieNode* createTrieNode()
- {
- TrieNode* node = (TrieNode *)malloc(sizeof(TrieNode));
- node->count = 0;
- node->exist = false;
- memset(node->next , 0 , sizeof(node->next)); // 初始化為空指標
- return node;
- }
- void Trie_insert(Trie root, char* word , char* trans)
- {
- Trie node = root;
- char *p = word;
- int id;
- while( *p )
- {
- id = *p - 'a';
- if(node->next[id] == NULL)
- {
- node->next[id] = createTrieNode();
- }
- node = node->next[id]; // 每插入一步,相當於有一個新串經過,指標向下移動
- ++p;
- node->count += 1; // 這行程式碼用於統計每個單詞前綴出現的次數(也包括統計每個單詞出現的次數)
- }
- node->exist = true; // 單詞結束的地方標記此處可以構成一個單詞
- strcpy(node->trans , trans);
- }
- char* Trie_search(Trie root, char* word)
- {
- Trie node = root;
- char *p = word;
- int id;
- while( *p )
- {
- id = *p - 'a';
- node = node->next[id];
- ++p;
- if(node == NULL)
- return 0;
- }
- if(node->exist) // 查詢成功
- return node->trans;
- else// 查詢失敗
- return NULL;
- }
- int main(void)
- {
- Trie root = createTrieNode(); // 初始化字典樹的根節點
- char str1[3003] , str2[3003] , str[3003] , *p;
- int i , k;
- scanf("%s",str1);
-
while(scanf("%s",str1) && strcmp(str1 ,
相關推薦
字典數Trie樹詳解及其應用
一、知識簡介 最近在看字串演算法了,其中字典樹、AC自動機和字尾樹的應用是最廣泛的了,下面將會重點介紹下這幾個演算法的應用。 字典樹(Trie)可以儲存一些字串->值的對應關係。基本上,它跟 Java 的 HashMap 功能相同,都是 k
Trie樹詳解及其應用
一、知識簡介 最近在看字串演算法了,其中字典樹、AC自動機和字尾樹的應用是最廣泛的了,下面將會重點介紹下這幾個演算法的應用。 字典樹(Trie)可以儲存一些字串->值的對應關係。基本上,它跟 Java 的 HashMap 功能相同,都是 key-v
自然語言--Trie樹詳解及其應用
連結:http://blog.csdn.net/hackbuteer1/article/details/7964147 參考連結:https://segmentfault.com/a/1190000005810561 一、知識簡介 最近在看字串演算法了,其
[轉] Trie樹詳解及其應用
一、知識簡介 最近在看字串演算法了,其中字典樹、AC自動機和字尾樹的應用是最廣泛的了,下面將會重點介紹下這幾個演算法的應用。 字典樹(Trie)可以儲存一些字串->值的對應關係。基本上,它跟 Java 的 HashMap 功能相同,都是 k
Storm概念、原理詳解及其應用(一)BaseStorm
when 結構 tails 並發數 vm 虛擬機 cif 異步 優勢 name 本文借鑒官文,添加了一些解釋和看法,其中有些理解,寫的比較粗糙,有問題的地方希望大家指出。寫這篇文章,是想把一些官文和資料中基礎、重點拿出來,能總結出便於大家理解的話語。與大多數“wordc
LSTM網路層詳解及其應用例項
上一節我們介紹了RNN網路層的記憶性原理,同時使用了keras框架聽過的SimpleRNN網路層到實際運用中。然而使用的效果並不理想,主要是因為simpleRNN無法應對過長單詞串的輸入,在理論上,當它接收第t個輸入時,它應該能把前面好幾個單詞的處理資訊記錄下來,但實際上它無法把前面已經
查詢(二)簡單清晰的B樹、Trie樹詳解
查詢(二) 散列表 散列表是普通陣列概念的推廣。由於對普通陣列可以直接定址,使得能在O(1)時間內訪問陣列中的任意位置。在散列表中,不是直接把關鍵字作為陣列的下標,而是根據關鍵字計算出相應的下標。 使用雜湊的查詢演算法分為兩步。第一步是用雜湊函式將被查詢的鍵轉化為陣
最簡單清晰的B樹、Trie樹詳解
查詢(二) 散列表 散列表是普通陣列概念的推廣。由於對普通陣列可以直接定址,使得能在O(1)時間內訪問陣列中的任意位置。在散列表中,不是直接把關鍵字作為陣列的下標,而是根據關鍵字計算出相應的下標。 使用雜湊的查詢演算法分為兩步。第一步是用雜湊函式將被查詢的鍵轉化
奇異值分解(SVD)詳解及其應用
這個假設是一個攝像機採集一個物體運動得到的圖片,上面的點表示物體運動的位置,假如我們想要用一條直線去擬合這些點,那我們會選擇什麼方向的線呢?當然是圖上標有signal的那條線。如果我們把這些點單純的投影到x軸或者y軸上,最後在x軸與y軸上得到的方差是相似的(因為這些點的趨勢是在45度左右的方向,所以投影到x軸
算法筆記--sg函數詳解及其模板
clas ref http spa for tail details false art sg函數大神詳解:http://blog.csdn.net/luomingjun12315/article/details/45555495 模板: int f[N],SG[N];
GoLang基礎數據類型--->字典(map)詳解
golang ont nbsp spa 數據 否則 創作 聲明 作者 GoLang基礎數據類型--->字典(map)詳解
python之路 第二篇 數據類型詳解及其方法
字符 引號 print 成員 移除 join att pri str 字符串 #作用:描述名字,性別,國籍,地址等信息#定義:在單引號\雙引號\三引號內,由一串字符組成 name=‘Matthew‘ #優先掌握的操作: #1、按索引取值(正向取+反向取) :只能取 #2
Oracle中的substr()函數 詳解及應用
arch rep ont string類 tco -c where int mco 註:本文來源於《Oracle中的substr()函數 詳解及應用》1)substr函數格式 (俗稱:字符截取函數) 格式1: substr(string string, int a
線段樹詳解 (原理,實現與應用)
比如,以左側的的藍色為例,若該節點是其父節點的右子節點,就證明它右側的那個紫色節點不會留下,會被其父替代,所以沒必要在這一步計算,若該節點是其父節點的左子節點,就證明它右側的那個紫色節點會留在這一層,所以必須在此刻計算,否則以後都不會再計算這個節點了。這樣逐層上去,容易發現,對於左側的藍色節點來說,只要它是左
CRC校驗詳解及其在網路程式設計中的應用
基本概念 CRC(Cyclic Redundancy Check,迴圈冗餘校驗),其實是個很古老的資料校驗方法了,記得以前大學<<微機原理>>和<<微機原理與介面技術>>課程都對其進行了介紹,其校驗準確度
AutoML 詳解及其在推薦系統中的應用、優缺點
大家好,我是為人造的智慧操碎了心的智慧禪師。昨天人工智慧頭條的股東粉群裡,有人問到推薦系統的話題
設計模式 - 模板方法模式詳解及其在Spring中的應用
基本介紹 模板方法模式(Template Method Pattern)也叫模板模式,它在一個抽象類中公開定義了執行它的方法的模板,它的字類可以按需重寫方法實現,但呼叫將以抽象類中定義的方式進行。 簡單來說,模板方法模式定義一個操作中的演算法的骨架,將一些步驟延遲到子類中,使得子類可以不改變一個演算法的結構,
設計模式 - 命令模式詳解及其在JdbcTemplate中的應用
# 基本介紹 > 在軟體設計中,我們經常需要向某些物件傳送一些請求,但是並不知道請求的接收者是誰,也不知道被請求的操作是哪個,我們只需要在程式執行時指定具體的請求接收者即可,此時,可以使用命令模式來設計,使得請求傳送者與請求接收者消除彼此之間的耦合,讓物件之間的呼叫關係更加靈活。 命令模式(Command
【字串演算法】字典樹詳解
### 字典樹 字典樹,又稱單詞查詢樹,`Trie`樹,是一種樹形結構,是一種雜湊樹的變種。典型應用是用於統計,排序和儲存大量的字串(但不僅限於字串),所以經常被搜尋引擎系統用於文字詞頻統計。它的優點是:利用字串的公共字首來節約儲存空間,最大限度地減少無謂的字串比較,查詢效率比雜湊表高。 字典樹與字
Python的反射機制、hasattr() getattr() setattr() 函數使用方法詳解
對象 tee lin sel __main__ err ace traceback 一個 hasattr(object, name)判斷一個對象裏面是否有name屬性或者name方法,返回BOOL值,有name特性返回True, 否則返回False。需要註意的是name要用