1. 程式人生 > >字典樹學習筆記

字典樹學習筆記

ant image oid char s 練習 str ems trie樹 速度

字典樹

原理

字典樹的本質是什麽?它其實是一棵存儲了很多字符串的樹,這棵樹上的每一條邊就是某個或某些字符串中的一個字符,而從根節點到某一個特定節點所經過的一條路徑上的所有邊組成的就是字典樹所保存的某一個字符串。不難看出,字典樹就是一顆多叉樹,它利用字符串的前綴來建立了這棵樹,從而達到了節省存儲空間(所有相同前綴都只存儲一次),優化查詢速度(查詢單個單詞是否存在的時間復雜度僅為該單詞的長度)的目的。
Tire的每個節點都擁有若幹個字符指針,若在插入或檢索字符串時掃描到一個字符c,就沿著當前節點的c這個字符指針,走向該指針指向的節點。

技術分享圖片

字典樹與字典很相似,當你要查一個單詞是不是在字典樹中,首先看單詞的第一個字母是不是在字典的第一層,如果不在,說明字典樹裏沒有該單詞,如果在就在該字母的孩子節點裏找是不是有單詞的第二個字母,沒有說明沒有該單詞,有的話用同樣的方法繼續查找.字典樹不僅可以用來儲存字母,也可以儲存數字等其它數據。

應用於快速字符串插入,查找字符串,在大量字符串的查找中,體現其高效性。

代碼

  • 封裝

    查找的時間復雜度只和樹的深度有關,跟表中有多少個單詞無關。
    考慮到每一個節點可能不止延伸出一條邊,不難想到用一個指針數組去保存當前節點可以到達的所有下一個節點。

const int MAXN = 26;
struct Trie{
    // 代表當前節點可以延伸出的邊,數量可變
    Trie *Next[MAXN];
    // 標記當前節點是否是保存信息的結尾,也可以代表前綴個數
    int Flag;
    Trie(){
     // 初始化以該信息為前綴的信息個數
       Flag = 1;
     memset(Next, NULL, sizeof(Next));
    }
}*root;//挺像C++的類
  • 插入操作

    將某一個信息插入字典樹,其實就是將該信息的前綴信息保存到數對應節點中,並修改相應的值

void Insert(char *str){
   int len = strlen(str);
   Trie *p = root, *q;
    // 將str的每一個字符插入trie樹
    for(int i=0; i<len; ++i){
        int id = str[i] - 'a';
        // 如果沒有邊,則新建一個trie節點,產生一條邊,用以代表該字符
        if(p->Next[id] == NULL){
            q = new Trie();
            p->Next[id] = q;
            p = p->Next[id];
        }
        // 如果存在邊,則沿著該邊走
        else{
            p = p->Next[id];
            // 累加以該信息為前綴的信息個數
            ++(p->Flag);
        }
    }
}
  • 查詢操作

    查詢某個信息是否存在於字典樹中,實質上也是將該信息的前綴信息與字典樹上存儲的對應位置的信息進行匹配,然後判斷標記的值即可。

int Query(char *str)
{
    int len = strlen(str);
    Trie *p = root;
    // 在trie樹上順序搜索str的每一個字符
    for(int i=0; i<len; ++i)
    {
        int id = str[i] - 'a';
        p = p->Next[id];
        // 若為空集,表示不存以此為前綴的信息
        if(p == NULL) return 0;
    }
    // 返回以該信息為前綴的信息個數
    return p->Flag;
}
  • 刪除操作

    遞歸釋放字典樹的每一個節點占用的空間即可。

void Free(Trie* T)
{
    if(T == NULL) return;
    // 釋放trie樹的每一個節點占用的內存
    for(int i=0; i<MAXN; ++i)
    {
        if(T->Next[i]) Free(T->Next[i]);
    }
    delete(T);
}

非指針做法

上面的一大串指針挺煩的....

//前綴統計實現
void ins(char *str){
    int len=strlen(str);
    int p=0;
    for(int i=0; i<len; i++){
        int ch=str[i]-'a';
        if(!tire[p][ch])
            tire[p][ch]=++ant;
        p=tire[p][ch];
    }
    cnt[p]++;
}
int srh(char *str){
    int ans=0;
    int len=strlen(str);
    int p=0;
    for(int i=0; i<len; i++){
        int ch=str[i]-'a';
        p=tire[p][ch];
        if(!p) return ans;
        ans+=cnt[p];
    }
    return ans;
}

練習

【數據結構】前綴統計

#include <bits/stdc++.h>
using namespace std;
int ant,n,m;
int tire[1000005][30],cnt[1000005];
void ins(char *str){
    int len=strlen(str);
    int p=0;
    for(int i=0; i<len; i++){
        int ch=str[i]-'a';
        if(!tire[p][ch])
            tire[p][ch]=++ant;
        p=tire[p][ch];
    }
    cnt[p]++;
}

int srh(char *str){
    int ans=0;
    int len=strlen(str);
    int p=0;
    for(int i=0; i<len; i++){
        int ch=str[i]-'a';
        p=tire[p][ch];
        if(!p) return ans;
        ans+=cnt[p];
    }
    return ans;
}

int main(){
    char s[1000000];
    scanf("%d %d",&n,&m);
    for(int i=0;i<n;i++)
    {
        scanf("%s",s);
        ins(s);
    }
    for(int i=0;i<m;i++)
    {
        scanf("%s",s);
        printf("%d\n",srh(s));
    }
    return 0;
}

字典樹學習筆記