什麼是字典樹?
字典樹(又叫單詞查找樹、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;
}