1. 程式人生 > 實用技巧 >【字串演算法】字典樹(Trie樹)

【字串演算法】字典樹(Trie樹)

什麼是字典樹

基本概念

字典樹,又稱為單詞查詢樹或Tire樹,是一種樹形結構,它是一種雜湊樹的變種,用於儲存字串及其相關資訊。

基本性質

1.根節點不包含字元,除根節點外的每一個子節點都包含一個字元
2.從根節點到某一節點。從根節點到該節點路徑上經過的字元連線起來,就是該節點對應的字串
3.同一個節點的所有子節點包含的字元都不相同

運用方面

典型應用是用於統計,排序和儲存大量的字串(不僅限於字串),經常被搜尋引擎系統用於文字詞頻統計。

優點缺點

字典樹是經典的空間換時間的資料結構,利用字串的公共字首來減少查詢時間,最大限度的減少無謂的字串比較,查詢效率據說比雜湊樹高。

但缺點就很顯然了,就是空間比較大……

舉個栗子

什麼不太瞭解,沒事,讓我們來結合栗子來分析一下!
我們首先讀入四個字串

ba
b
band
abc

在沒有讀入前,我們有一個空空的根節點;

接著我們插入單詞ba;

接著插入單詞b,由於根節點有連向b的子節點,所以只需路徑上的s++就好了;

接著插入單詞bank,ba之前就有,只需s++,而nk需要在ba後新增子節點完成儲存;

最後插入單詞abc;

如何構造字典樹

我們來結合程式一步一步來構造這棵可愛的字典樹吧!!

構造節點

我們需要運用struct來儲存字典樹上每個節點的資訊:

struct node
{
    int s,v[27],val;
    node()
    {
        s=0;
        memset(v,-1,sizeof(v));
    }
}t[410000];

s是用來儲存有多少個單詞進過了這個節點,v是用來儲存這個點從a到z的兒子節點分別在哪,而val則是儲存這個節點的權值,至於權值代表什麼,就要依照題目靈活變換了。

構造字典樹

我們先丟擲程式:

int bt(int root)
{
    int len=strlen(a+1);int x=root;
    for(int i=1;i<=len;i++)
    {
        int y=a[i]-'a'+1;//將a^z轉化為1^26
        if(t[x].v[y]==-1)t[x].v[y]=++tot;
        x=t[x].v[y];t[x].s++;
    }
}

首先我們先讀入了一個字串a,它的長度為len;
接著我們對於它的字元進行迴圈處理,當我們處理到這個字串的第i個字元的時候,我們要進行分類討論——

我們用x儲存第i-1個字元在字典樹中的位置;
當我的前一個字元沒有指向我的字元的時候,我就tot++,在字典樹中開創一個新的空間,我就把自己放在這個空間裡;

如果我的前一個字元有指向我的字元的子節點時,我就放心地走到那個子節點就好了;最後記得更新x的值為當前處理的子節點的位置,並且s++,表示又多了一個單詞進過了這個節點,以及完成val的修改之類的工作;

i++,進入下一重迴圈!

這樣一棵完整的字典樹就出來了!

模板&&模板題

【caioj 1463】統計字首
題目描述
【題意】
給出很多個字串(只有小寫字母組成)和很多個提問串,統計出以某個提問串為字首的字串數量(單詞本身也是自己的字首).
【輸入格式】
輸入n,表示有n個字串(n<=10000)
接下來n行,每行一個字串,字串度不超過10
輸入m,表示有m個提問(m<=100)
第二部分是一連串的提問,每行一個提問,每個提問都是一個字串.
【輸出格式】
對於每個提問,給出以該提問為字首的字串的數量.
【樣例輸入】
5
banana
band
bee
absolute
acm
4
ba
b
band
abc
【樣例輸出】
2
3
1
0

就是一道裸題,查詢時輸出對應節點的s就好了;

附上程式碼:

#include<bits/stdc++.h>
using namespace std;
struct node
{
    int s,v[27];
    node()
    {
        s=0;
        memset(v,-1,sizeof(v));
    }
}t[410000];
char a[410000];
int i,j,k,m,n,tot,js,jl;

int bt(int root)
{
    int len=strlen(a+1);int x=root;
    for(int i=1;i<=len;i++)
    {
        int y=a[i]-'a'+1;
        if(t[x].v[y]==-1)t[x].v[y]=++tot;
        x=t[x].v[y];t[x].s++;
    }
}

int solve(int root)
{
    int len=strlen(a+1);int x=root;
    for(int i=1;i<=len;i++)
    {
        int y=a[i]-'a'+1;
        if(t[x].v[y]==-1)return 0;
        x=t[x].v[y];
    }
    return(t[x].s);
}

int main()
{
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",a+1);
        bt(0);
    }

    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",a+1);
        printf("%d\n",solve(0));
    }
}

結語

通過這篇BLOG相信你已經掌握了Trie樹,那就向著AC自動機前進吧!希望你喜歡這篇BLOG!

字典樹練習題:
HDU1251(本題原版)
HDU1075
HDU1800

參考

https://ethsonliu.com/2019/09/trie-tree.html

https://oi-wiki.org/string/trie/