字典樹(trie)——楊子曰數據結構
先扔一道題:HDU - 1251統計難題
就是說給你一堆字符串,再是一堆詢問,問你以這個字符串爲前綴的字符串有多少個?
今天我們來曰一個字符串中常用的數據結構——字典樹(高雅的人稱之爲trie樹(讀作:踹樹))
trie樹有一下幾個特點:
1.根節點是空的
2.每個節點上都會記錄一個字符(除了根節點)
3.從根節點下面出發,往下走路徑上記錄字符串,So,兩個串相同的前綴是它們後綴的公共祖先
我都覺得我沒有講清楚,相信你也一臉懵逼,於是我們來搞一個例子:
假設我們要把一下字符串放進trie:
aab
ab
abbc
abba
ac
bbb
bbc
ba
bac
cca
我們可以得到這樣一棵trie樹:
藍色的節點表示這個節點是一個字符串的結尾,也就是從根到藍色節點的路徑上是是一個串
只要你是地球人就能秒懂
那我們怎麼建樹呢?,可以說是非常滴簡單呀!!直接上代碼:
//tr[k][p]表示節點k的字符爲p的兒子的編號
void build(string s){ //我們要把s放進trie裏
int k=1; //根節點是1,我們現在在k節點
for (int i=0;s[i];i++){
int p=s[i]-'a';
if (!tr[k][p]) tr[k][p]=++sum;//如果這個節點不存在,那就建一個,並把當前節點的這條邊指向它
k=tr[k][p]; //繼續往下走
}
}
很顯然trie能解決的問題都和前綴有很大關係,解決問題時我們要考慮在trie上是否要記錄東西?記錄什麼東西?
這些東西可以是:這個節點是不是一個串的結尾,這個節點經過了幾次……(我也一時半會兒舉不出,尷尬)
到現在爲止,我們會發現上面那個問題會變得灰常簡單了,我們要在每個節點記錄它在建樹是時經過的次數,也就是所有字符串中,以根節點到這個節點路徑上的字符串爲前綴的字符串個數(←,很繞,自己模擬一下)
Then,對於每次查詢我們從根出發,找到這個字符串末尾的節點,輸出節點上記錄的值就歐了。
代碼走起:
int find(char *s){
int k=1;
for (int i=0;s[i];i++){
int p=s[i]-'a';
if (!tr[k][p]) return 0;//這個前綴不存在
k=tr[k][p];
}
return num[k];
}
總結一波:建樹複雜度O(n|s|),查詢複雜度O(|s|),可以說是非常優啊!
OK,完事
c++代碼:
#include<cstdio>
#include<iostream>
using namespace std;
char s[100];
int sum=1;
int tr[500005][30],num[500005];
void build(char *s){
int k=1;
for (int i=0;s[i];i++){
int p=s[i]-'a';
if (!tr[k][p]) tr[k][p]=++sum;
k=tr[k][p];
num[k]++;
}
}
int find(char *s){
int k=1;
for (int i=0;s[i];i++){
int p=s[i]-'a';
if (!tr[k][p]) return 0;
k=tr[k][p];
}
return num[k];
}
int main(){
while(gets(s) && s[0]) build(s);
while(~scanf("%s",&s)){
cout<<find(s)<<endl;
}
return 0;
}
注意:
對於有些題目,你要考慮把哪些字符串放進trie,推薦一道題:POJ - 1204 Word Puzzles
你以爲字典樹只能解決字符串的問題嗎?推薦一道題:[HDU 4825 - Xor Sum]
(https://blog.csdn.net/HenryYang2018/article/details/81452474)
如果你還知道一個東西叫KMP的話(不知道,戳)
字典樹是媽媽,KMP是爸爸,會有一個小孩紙叫做AC自動機(注意!不是自動AC機,他們有本質區別)
戳我學習自動AC AC自動機
他是一個用KMP的想法在trie樹上實現的算法
於XJ機房607