字典樹【入門介紹】

什麼是字典樹?

字典樹(又叫單詞查找樹、TrieTree),是一種樹形結構,典型應用是用於統計,排序和保存大量的字符串(但不僅限於字符串,如01字典樹)。主要思想是利用字符串的公共前綴來節約存儲空間。很好地利用了串的公共前綴,節約了存儲空間。字典樹主要包含兩種操作,插入和查找。
在這裏插入圖片描述

字典樹的優點

  • 節約空間

例如在現實生活中,我們需要存儲很多URL(Uniform Resoure Locator:統一資源定位器)是WWW頁的地址。
大多數以“http://www ”開頭,若以字典樹存儲,則50億個“http://www ”只需要10個存儲單位即可,而非50億*10個。

  • 快速檢索
    字典樹能很好地利用串的公共前綴,節約了存儲空間,同時用它來檢索同樣有着比較高的效率。
    我們需要將一個單詞列表建出一棵單詞查找樹,滿足:
    (1)根結點不包含字母,除根結點外的每個都僅含一個大寫英文字母;
    (2)從根結點到某一節點,路徑上經過的字母依次連起來所構成的單詞,稱爲該結點對應的單詞。單詞列表中的每個詞,都是該單詞查找樹某個結點所對應的單詞;
    (3)在滿足上述條件下,該單詞查找樹的結點數最少,統計出該單詞查找樹的結點數目。
    在這裏插入圖片描述

字典樹的結構

在這裏插入圖片描述
字典樹可以解決以下問題:
在題目給出的輸入字典中查詢單詞是否存在,以及以某單詞爲前綴查詢字典中有多少符合條件的單詞

例題:
題意:給出一些單詞找出其中某些可以由另外兩個單詞拼接而成的單詞

樣例輸入             樣例輸出
a                   ahat
ahat                hatword
hat 
hatword 
hziee 
word

分析:如果按最樸素的方法實現,即對於輸入的每個單詞依次進行遍歷,如第二個單詞‘ahat’,要查詢其是否由輸入的其他單詞組合而成,就要將ahat拆開,變爲a+hat,ah+at,aha+t,對輸入的n個單詞再查找一輪是否有一樣匹配的字符。也就是n(個單詞)*len-1(每個單詞長度)*n(個單詞) 的查詢次數兩兩單詞進行比對時用到strcmp函數又要花費len的查詢次數。

若在查詢過程中使用字典樹進行查找。依然對每個單詞進行遍歷。然後將n個單詞分別拆開,兩段分開的單詞進行字典樹查詢。

如果兩段單詞都在字典樹中查詢成功說明被查詢單詞即其中一個ans。

對字典樹進行查詢時,無須遍歷n個單詞,只用對被查詢單詞在樹上走出len的查詢次數。


字典樹還有很多除了字符串之外的應用,如對數字的二進制01串構造字典樹可以快速按位查詢數字是否存在。當結合字符串匹配算法KMP時,利用KMP的利用失配信息思想快速繼續匹配,可以實現AC自動機算法。只需給每個節點增加fail指針指向失配時快速指向的節點。

當對構造好的字典樹進行BFS預處理並增加直達葉子節點的指針時,可以解決輸入最小操作數的問題。

總之以字典樹爲基礎可以改造出各種有用而高效的處理方法。

數組模擬字典樹:

#include <stdio.h>
#include <string.h>
const int maxn=10000;//提前估計好可能會開的節點的個數

int tot;            //節點編號,模擬申請新節點,靜態申請
int trie[10000][26]; //假設每個節點的分支有26個
bool isw[10000];     //判斷該節點是不是單詞結尾

void insert(char *s,int rt)//投入的參數是一個字符串和節點數,建立字典樹
{
    for(int i=0; s[i]; i++)
    {
        int x=s[i]-'a';//假設單詞都是小寫字母組成
        if(trie[rt][x]==0) //沒有,申請新節點
        {
            trie[rt][x]=++tot;//每個字符的編號
        }
        rt=trie[rt][x];
    }
    isw[rt]=true;//整個字符串讀完後,在isw數組中記錄第rt層爲單詞結尾
}

bool find(char *s,int rt)
{
    for(int i=0; s[i]; i++)
    {
        int x=s[i]-'a';//假設單詞都是小寫字母組成
        if(trie[rt][x]==0)
        {
            return false;
        }
        rt=trie[rt][x];
    }
    return isw[rt];
}

char s[22];//單詞讀入

int main()
{
    tot=0;//一開始沒有節點

    int rt=++tot;//申請一個根節點
    memset(trie[rt],0,sizeof(trie[rt]));//初始化根節點
    memset(isw,false,sizeof(isw));

    while(scanf("%s",s),s[0]!='#') //新建字典,以一個'#'結束
    {
        insert(s,rt);
    }
    while(scanf("%s",s),s[0]!='#') //查單詞,以一個'#'結束
    {
        if(find(s,rt))//從字典中查找單詞
            printf("%s 在字典中\n",s);
        else
            printf("%s 不在字典中\n",s);
    }
    return 0;
}

指針型字典樹:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct trie//trie型結構體,建立樹
{
    int cnt;//用作標記查找
    trie *next[26];//連接樹與節點的指針
    bool flag;///單詞結尾節點標記
};
trie *root=new trie;//建立根節點

void insert(char ch[])
{
    trie *p=root,*newnode;
    for(int i=0; ch[i]!='\0'; i++)
    {
        if(p->next[ch[i]-'a']==0)//這裏以所有的字符全部爲小寫字母爲例
        {
            newnode=new trie;///創建新節點
            newnode.flag=false;///初始化新節點結尾標記
            for(int j=0; j!=26; j++)
            {
                newnode->next[j]=NULL;///初始化所有孩子
            }
            newnode->cnt=1;///新節點次數初始化
            p->next[ch[i]-'a']=newnode;
            p=newnode;
        }
        else
        {
            p=p->next[ch[i]-'a'];
            p->cnt++;///對走過的節點次數進行計數
        }
    }
    p->flag=true;///標記結尾
}

int find(char ch[])///查詢
{
    trie *p=root;
    for(int i=0; ch[i]!='\0'; i++)
    {
        if(p->next[ch[i]-'a']!=NULL)
            p=p->next[ch[i]-'a'];
        else
            return 0;
    }
    return p->cnt;
}

int deal(trie* T)//遞歸將字典樹清空
{
    int i;
    if(T==NULL)
        return 0;
    for(i=0;i<10;i++)
    {
        if(T->next[i]!=NULL)
            deal(T->next[i]);
    }
    free(T);
    return 0;
}

int main()
{
    char ch[20];
    for(int i=0; i!=26; i++)
    {
        root->next[i]=NULL;
    }
    root->cnt=0;
    while(gets(ch))
    {
        if(!strcmp(ch,""))
            break;
        insert(ch);
    }
    while(scanf("%s",ch)!=EOF)
    {
        printf("%d\n",find(ch));
    }
    deal(root);
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章