演算法詳解——Trie樹
Tire樹(字典樹)是用於字串檢索的一種多叉樹結構,其中每一個節點包含了若干個字元,在插入或檢索某一個字串時,就沿著當前節點的指標訪問下一個節點,我們先來講一下Tire樹的基礎操作
初始化
一顆空的Tire樹僅包含根節點,且該點的指標為空
插入
當我們要插入一個字串a時,我們先令指標p指向根節點,然後掃描a中的每一個字元c,執行以下操作:
1.當p中的c指向一個已存在的節點q,令p = q
2.當p中的c指標為空,則新建一個節點q,使c指向q,令p = q
檢索
當我們要檢索一個字串a在Trie樹中是否存在時,我們先令指標p指向根節點,然後掃描a中的每一個字元c,執行以下操作:
1.當p中的c指向一個已存在的節點q,令p = q
2.當p中的c指標為空,則說明S不存在於Trie樹中,結束檢索
下面給出具體操作程式碼:
inline void Insert(char* a) //插入一個字串a { int len = strlen(a),p = 0; for(int k = 0;k < len;k ++) { int ch = a[k] - '0'; if(!trie[p][ch]) trie[p][ch] = tot ++; //不存在元素c,新建一個節點q p = trie[p][ch]; //令p = q } return ; } inline bool Search(char* a) //對字串a進行檢索 { int len = strlen(a),p = 0; for(int k = 0;k < len;k ++) { int ch = a[k] - 'a'; if(trie[p][ch]) p = trie[p][ch]; //存在元素c,令p = q else return false; //不存在元素c,結束檢索 } return true; }
下面給出兩道例題
T1 HDU1251
Problem Description
Ignatius最近遇到一個難題,老師交給他很多單詞(只有小寫字母組成,不會有重複的單詞出現),現在老師要他統計出以某個字串為字首的單詞數量(單詞本身也是自己的字首). Input
輸入資料的第一部分是一張單詞表,每行一個單詞,單詞的長度不超過10,它們代表的是老師交給Ignatius統計的單詞,一個空行代表單詞表的結束.第二部分是一連串的提問,每行一個提問,每個提問都是一個字串.
Output
對於每個提問,給出以該字串為字首的單詞的數量.
題解
顯然是一道Trie樹的板子題,在插入的時候,統計每個節點被訪問的次數,最後查詢就行了
程式碼如下:
#include <cstdio> #include <cstring> #include <iostream> const int maxn = 1e6; int trie[maxn][26]; int cnt[maxn]; int tot = 1; char a[100]; inline void Insert(char* a) { int len = strlen(a),p = 0; for(int k = 0;k < len;k ++) { int ch = a[k] - 'a'; if(!trie[p][ch]) trie[p][ch] = tot ++; p = trie[p][ch]; cnt[p] ++; } } inline int Search(char* a) { int len = strlen(a),p = 0; for(int k = 0;k < len;k ++) { int ch = a[k] - 'a'; if(trie[p][ch]) p = trie[p][ch]; else return 0; } return cnt[p]; } int main(int argc, char const *argv[]) { while(gets(a)) { if(!strlen(a)) break; Insert(a); } while(std::cin>>a) { printf("%d\n",Search(a)); } return 0; }
T2 洛谷P2580
題目背景
XS中學化學競賽組教練是一個酷愛爐石的人。
他會一邊搓爐石一邊點名以至於有一天他連續點到了某個同學兩次,然後正好被路過的校長髮現瞭然後就是一頓尤拉尤拉尤拉(詳情請見已結束比賽CON900)。
題目描述
這之後校長任命你為特派探員,每天記錄他的點名。校長會提供化學競賽學生的人數和名單,而你需要告訴校長他有沒有點錯名。(為什麼不直接不讓他玩爐石。)
輸入輸出格式
輸入格式:
第一行一個整數 n,表示班上人數。接下來 n 行,每行一個字串表示其名字(互不相同,且只含小寫字母,長度不超過 50)。第 n+2 行一個整數 m,表示教練報的名字。接下來 m 行,每行一個字串表示教練報的名字(只含小寫字母,且長度不超過 50)。
輸出格式:
對於每個教練報的名字,輸出一行。如果該名字正確且是第一次出現,輸出“OK”,如果該名字錯誤,輸出“WRONG”,如果該名字正確但不是第一次出現,輸出“REPEAT”。(均不加引號)
題解
也是一道非常簡單的Trie板子題,在檢索的時候注意加一個訪問標記,當檢索的元素已被檢索過,輸出“REPEAT”
程式碼如下:
#include <cstdio> #include <cstring> #include <iostream> const int maxn = 1e6 + 5; int n,m; int trie[maxn][26]; int tot = 1; int vis[maxn]; bool flag; inline void Insert(char* a) { int len = strlen(a),p = 0; for(int k = 0;k < len;k ++) { int ch = a[k] - 'a'; if(!trie[p][ch]) trie[p][ch] = tot ++; p = trie[p][ch]; } return ; } inline bool Search(char* a) { int len = strlen(a),p = 0; for(int k = 0;k < len;k ++) { if(trie[p][a[k] - 'a']) p = trie[p][a[k] - 'a']; else return false; } if(vis[p]) flag = true; vis[p] = 1; return true; } int main(int argc, char const *argv[]) { scanf("%d",&n); for(int i = 1;i <= n;i ++) { char a[100]; std::cin>>a; Insert(a); } scanf("%d",&m); for(int i = 1;i <= m;i ++) { char a[100]; flag = false; std::cin>>a; if(Search(a)) { if(!flag) printf("OK\n"); else printf("REPEAT\n"); } else printf("WRONG\n"); } return 0; }