trie 樹(字典樹)

2022-04-29 21:48:23 字數 3571 閱讀 9311

目錄異或對

可持久化 trie 樹

總結trie 樹(字典樹)的名字告訴了我們一切,就是一顆像字典一樣的樹。

先不管怎麼實現,先讓我們了解它有什麼用。

實現字串快速檢索(「檢」即看乙個字串有沒有出現過,「索」即檢視字串相關資訊)

實現最大異或對相關問題(基於貪心思想,之後會講)

當然,具體用處要看具體問題與具體情況,這裡僅僅給出常用的兩種方式。

接下來我們將一起探索 trie 樹的實現。

trie 樹的本質是一顆多叉樹,它的基礎操作包括:初始化、插入與檢索。

插入與檢索的時間複雜度都為 \(o(c)\) ( \(c\) 為該字串的長度),但是空間複雜度高達 \(o(nc)\) (\(n\) 為節點的個數)。

可見 trie 樹對空間要求較高,這就要求同學們對於空間大小有準確的判斷。

其實沒什麼好說的,沒有特殊的初始化(都為 0 就可以了)。

唯一需要注意的是 \(tot=1\) ,\(tot\) 是什麼之後再說,記住就可以了。

當要插入乙個字串 \(s\) 是,令乙個指標 \(p=1\) (即指向根節點),然後依次掃瞄 \(s\) 中的每乙個字元 \(c\):

若 \(p\) 的 \(c\) 字元指標指向乙個已經存在的節點 \(q\) ,則令 \(p=q\)。

若 \(p\) 的 \(c\) 字元指標指向空,則新建乙個節點 \(q\) ,令 \(p\) 的 \(c\) 字元指標指向 \(q\) ,然後 \(p=q\)。

當 \(s\) 的字元掃瞄完畢時,在當前節點 \(p\) 上標記它是乙個字串的末尾。

注意: 在《演算法競賽高階指南》中,用 \(end\) 陣列標記字串末尾,但 \(end\) 是 \(c++11\)的關鍵字,所以容易發生編譯錯誤(可能本地編譯沒有問題,但提交就是不是到**錯了),所以應換成其他名字

**如下:(這裡沒有改動 \(end \) 陣列,但考試時一定要改動)

void insert(string s)

for(int i=1;i<=m;i++)

return 0;

}

字首統計

trie 的簡單運用僅僅有了一點點改變。

同樣的把這 \(n\) 個字串插入到 \(trie\) 中,在每個節點中記錄乙個 \(cnt\) 表示該節點是多少個字串的末尾節點

**如下:

#include#include#include#include#include#define n 1000010

using namespace std;

int n,m,cnt[n],trie[n][30],tot=1;

char a[n];

void insert(char* a)-1\),所以是 \(32\) 位)

接著列舉每乙個數,與在 \(trie\) 中檢索相類似的取查詢,每次盡量找 \(xor a[i]\) 的一位就好(貪心)

這個貪心的正確性是很容易證明的,舉個栗子:

\(a=「00000000」\)

\(b=「10000000」\),\(axorb=「10000000」\)

\(c=「01111111」\),\(axorc=「01111111」\)

那麼按照貪心策略,\(a\) 的最大異或對為 \(axorb\) (即使 \(b\) 只有第一位與 \(a\) 不同,\(axorb\) 還是優於 \(axorc\))

其實很好實現的,就只要按照上面說的做就好了:

#include#include#include#include#include#define n 100010

using namespace std;

int n,a[n],trie[n*32][5],tot=1;//注意trie大小的定義

void insert(int x)

return;

}int search(int x)edge[2*n];

void addedge(int x,int y,int z)

void dfs(int x,int fa)

return;

}void insert(int x)

return;

}int search(int x)\bigoplus ...\bigoplus a_n=a_\bigoplus \bigoplus^_ a_i

\]所以:\(a[p] \bigoplus a[p+1] \bigoplus ... \bigoplus a[n] \bigoplus x=s[p−1] \bigoplus s[n] \bigoplus x\)。(其中 \(s[i]=\bigoplus_^n a_i\))

此時查詢轉變為:已知 \(val=\mathrm\),求乙個 \(p\in[l-1,r-1]\),使得 \(\mathrm\)最大。

可以構建一顆可持久化 \(\mathrm\),第 \(i\) 個版本為插入了 \(s[i]\) 後的 \(\mathrm\) 樹。

每次查詢,從根節點開始,貪心地選與這一位相反的值。(貪心證明參見上文)

此外,還有乙個 \(l-1\leq p\leq r-1\) 的限制。

先考慮 \(p\leq r-1\),查詢第 \(r-1\) 個版本的 \(\mathrm\) 即可,因為此時不可能訪問到 \(r-1\) 之後的 \(s\)。

再考慮 \(l-1\leq p\),對每個節點維護乙個 \(latest\)值,表示子樹中所有 \(s\) 值的下標的最大值。

這樣,在查詢時只訪問 \(latest\geq l-1\) 的節點就行了。

上**:

#include#include#include#include#include#define n 600010

using namespace std;

int n,m,s[n],root[n],tot=0;

int trie[n*24][2],latest[n*24];

int read()

void insert(int i,int k,int p,int q)

int c=s[i]>>k & 1;

if(p) trie[q][c^1]=trie[p][c^1];

trie[q][c]=++tot;

insert(i,k-1,trie[p][c],trie[q][c]);

latest[q]=max(latest[trie[q][0]],latest[trie[q][1]]);

return;

}int ask(int p,int val,int k,int limit)

int main()

char str[2];

for(int i=1;i<=m;i++)

else

} return 0;

}

在解決字串相關問題以及異或對相關問題是可以往 trie 樹一方面想一想。

當然只有這麼多的做題量是遠遠不夠的,請讀者務必自己尋找更多相關習題。

注:全篇大多思想均來自:《演算法競賽高階指南》。

可持久化 \(trie\) 例題部分 部分講解來自 這篇部落格

完結撒花。

Trie樹(字典樹)

trie樹的核心思想是用空間換時間,通過在樹中儲存字串的公共字首,來達到加速檢索的目的。例如,對於一棵儲存由英文本母組成的字串的trie樹,如下圖 trie樹在實現的時候,可以用左兒子右兄弟的表示方法,也可以在每個節點處開設乙個陣列,如上圖的方法。trie樹的主要操作是插入 查詢,也可以進行刪除。插...

字典樹 Trie樹

字典樹 trie樹 顧名思義是一種樹形結構,屬於雜湊樹的一種。應用於統計 排序 查詢單詞 統計單詞出現的頻率等。它的優點是 利用字串的公共字首來節約儲存空間,最大限度地減少無謂的字串比較,查詢效率比雜湊表高。字典樹的結構特點 根節點不代表任何字元。其他節點從當前節點回溯到根節點可以得到它代表的字串。...

字典樹 trie樹

amy 56 ann 15 emma 30 rob 27 roger 52首先存入amy,level 0表示根,不持有資料。其餘每個節點持有乙個字元 葉子節點持有資料,且持有的字元為 0 level 0 root a level 1 m level 2 y level 3 0 56 level 4新...