第四周的題目是前兩週的綜合,綜合在一個是KMP算法的思想,一個是樹的這麼一個數據結構。
題目 : Trie圖
輸入
每個輸入文件有且僅有一組測試數據。
每個測試數據的第一行爲一個整數N,表示河蟹詞典的大小。
接下來的N行,每一行爲一個由小寫英文字母組成的河蟹詞語。
接下來的一行,爲一篇長度不超過M,由小寫英文字母組成的文章。
對於60%的數據,所有河蟹詞語的長度總和小於10, M<=10
對於80%的數據,所有河蟹詞語的長度總和小於10^3, M<=10^3
對於100%的數據,所有河蟹詞語的長度總和小於10^6, M<=10^6, N<=1000
輸出
對於每組測試數據,輸出一行"YES"或者"NO",表示文章中是否含有河蟹詞語。
樣例輸入
6
aaabc
aaac
abcc
ac
bcd
cd
aaaaaaaaaaabaaadaaac
樣例輸出
YES
一開始我的思路是對每一個節點,每一個struct立面定義一個suffix指針,代表它的後綴節點,但是因爲題目要不斷地查找後綴節點,這樣指針覺得麻煩,所以直接定義一個node[1000005],因爲題目中已經說了最多的詞語長度就是10^6。
不多說,直接上代碼,代碼中細說。
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
#define len 1000000
char s[1000006];
char dir[1000006];
int node_count=0;//節點總數
struct Node {
int flag;//是否結束
int suffix;//node[p].suffix的值就直接代表它的後綴節點
int next[26];//這裏的next是爲構建樹所用,即node[p].next['a']就代表當前節點經過字符'a'跳到哪一個節點中去
}node[len];
void init()
{
int count;
for(count =0;count<len;count++)
{
node[count].flag = 0;
node[count].suffix = 0;
for(int i=0;i<26;i++)
node[count].next[i]= 0;
}
}
void Add_Trie(char *f_s)
{
int len_s = strlen(f_s);
int p=0,i;
for(i=0;f_s[i];i++)
{
if(!node[p].next[f_s[i]-'a'] )
{
node[p].next[f_s[i]-'a']= ++node_count;
}
p=node[p].next[f_s[i]-'a'];
}
node[p].flag = 1;
}
void Cal_Suffix()
{
queue<int> q;
/*這個隊列我沒想到,是看到其他人的代碼纔想到的
一開始我是循環了所有節點,結果就是處理的很亂,
有的處理了兩遍。所以,實際上用queue的好處在於
能理清思路,這個節點進入隊列,開始計算這個節點
後綴節點,頓時思路很清晰明瞭了。
其實後綴節點只是爲了計算next[]數組時所用的工具。
因爲next數組在樹中一開始只是記錄真正經過的節點,
現在要通過next數組來計算如果這時字符串與字典不
匹配的話,要跳到哪裏去,實際上感覺這像是自動機
的內容*/
int p, i;
q.push(0);
while(!q.empty())
{
p = q.front();
q.pop();
if(node[node[p].suffix].flag ==1)
node[p].flag = 1;
for(i = 0; i < 26; i++)
if(node[p].next[i])//如果該節點有下一個節點
{
q.push(node[p].next[i]);//就把它放入到隊列中
if(p)
node[node[p].next[i]].suffix = node[node[p].suffix].next[i];//如果不是起始節點,那麼當前節點P的經過字符i下一個節點的後綴節點是P的後綴節點經過字符i後的節點
}
else
node[p].next[i] = node[node[p].suffix].next[i];//如果沒有下一個節點,那麼這時suffix發威了,就跳到當前節點P的後綴節點經過字符i的節點上去
}
}
bool Search(char *f_s)
{
int len_f_s = strlen(f_s);
int count1=0,p=0;
while(f_s[count1])
{
p=node[p].next[f_s[count1] - 'a'];
if(node[p].flag == 1)
{
return true;
}
count1++;
}
return false;
}
int main()
{
init();
int dir_count;
cin>>dir_count;
while(dir_count--)
{
cin>>s;
Add_Trie(s);//添加到樹種
}
Cal_Suffix();//計算每個節點的後綴節點
cin>>dir;
if(Search(dir))//計算結果
cout<<"YES"<<endl;
else
cout<<"NO"<<endl;
return 0;
}
整個程序第一點感受就是賦初值別亂賦,想清楚了在開始,一開始的suffix,next數組初值設置爲-1,殊不知,可能那裏一個取值(數組[-1]),程序就出錯了。
第二點感受在memset函數的使用上,以後memset除了0,除了char型數組,用的話要小心謹慎。
第三點感受就是這道題看着複雜,但人家都有hint了。。。理清思路的話,不是很難。
最後一點感受就是 好像是拖得越久,記得越深。。。現在每次做題都能把焦點從算法轉移到某一個函數用法或是數據結構上去,說明自己基礎還是遠遠不夠。STL中的vector、queue、list等 ,只是知道了其用法,根本沒有完全掌握。
下次再編代碼之前,要首先理清思路,設計好整個程序的數據結構,各個函數的用法,再去實現其具體功能,比現在這樣上來就編然後就提交,再然後就是WA,再去找bug,改得最後面目全非的,思路邏輯混亂,幾乎就是看正確答案才能編出來的這幅德行好多了。