【字串演算法】字典樹詳解
阿新 • • 發佈:2020-12-02
### 字典樹
字典樹,又稱單詞查詢樹,`Trie`樹,是一種樹形結構,是一種雜湊樹的變種。典型應用是用於統計,排序和儲存大量的字串(但不僅限於字串),所以經常被搜尋引擎系統用於文字詞頻統計。它的優點是:利用字串的公共字首來節約儲存空間,最大限度地減少無謂的字串比較,查詢效率比雜湊表高。
字典樹與字典很相似,當你要查一個單詞是不是在字典樹中,首先看單詞的第一個字母是不是在字典的第一層,如果不在,說明字典樹裡沒有該單詞,如果在就在該字母的孩子節點裡找是不是有單詞的第二個字母,沒有說明沒有該單詞,有的話用同樣的方法繼續查詢.字典樹不僅可以用來儲存字母,也可以儲存數字等其它資料。
**Trie的資料結構定義:**
```cpp
#define MAX 26
typedef struct Trie {
Trie* next[MAX];
int v; //根據需要變化
};
Trie* root;
```
next是表示每層有多少種類的數,如果只是小寫字母,則26即可,若改為大小寫字母,則是52,若再加上數字,則是62了,這裡根據題意來確定。 v可以表示一個字典樹到此有多少相同字首的數目,這裡根據需要應當學會自由變化。
Trie的查詢(最主要的操作):
(1) 每次從根結點開始一次搜尋;
(2) 取得要查詢關鍵詞的第一個字母,並根據該字母選擇對應的子樹並轉到該子樹繼續進行檢索;
(3) 在相應的子樹上,取得要查詢關鍵詞的第二個字母,並進一步選擇對應的子樹進行檢索。 (4) 迭代過程……
(5) 在某個結點處,關鍵詞的所有字母已被取出,則讀取附在該結點上的資訊,即完成查詢。
這裡給出生成字典樹和查詢的模版:
**生成字典樹:**
```c++
void createTrie(char* str) {
int len = strlen(str);
Trie *p = root, *q;
for (int i = 0; i < len; ++i) {
int id = str[i] - '0';
if (p->next[id] == NULL) {
q = (Trie*)malloc(sizeof(Trie));
q->v = 1; //初始v==1
for (int j = 0; j < MAX; ++j)
q->next[j] = NULL;
p->next[id] = q;
p = p->next[id];
} else {
p->next[id]->v++;
p = p->next[id];
}
}
p->v = -1; //若為結尾,則將v改成-1表示
}
```
**查詢:**
```c++
int findTrie(char* str) {
int len = strlen(str);
Trie* p = root;
for (int i = 0; i < len; ++i) {
int id = str[i] - '0';
p = p->next[id];
if (p == NULL) //若為空集,表示不存以此為字首的串
return 0;
if (p->v == -1) //字符集中已有串是此串的字首
return -1;
}
return -1; //此串是字符集中某串的字首
}
```
#### 例題
- [hdu 1251 統計難題](http://acm.hdu.edu.cn/showproblem.php?pid=1251)
題意:在給出的字串中找出由給出的字串中出現過的兩個串拼成的字串。
字典樹的模板題,先建字典數,然後再查詢每個給定的單詞。。
**程式碼如下:**
```c++
#include
#include
using namespace std;
const int sonsum = 26, base = 'a';
char s1[12], ss[12];
struct Trie {
int num;
bool flag;
struct Trie* son[sonsum];
Trie() {
num = 1;
flag = false;
memset(son, NULL, sizeof(son));
}
};
Trie* NewTrie() {
Trie* temp = new Trie;
return temp;
}
void Inset(Trie* root, char* s) {
Trie* temp = root;
while (*s) {
if (temp->son[*s - base] == NULL) {
temp->son[*s - base] = NewTrie();
} else
temp->son[*s - base]->num++;
temp = temp->son[*s - base];
s++;
}
temp->flag = true;
}
int search(Trie* root, char* s) {
Trie* temp = root;
while (*s) {
if (temp->son[*s - base] == NULL)
return 0;
temp = temp->son[*s - base];
s++;
}
return temp->num;
}
int main() {
Trie* root = NewTrie();
root->num = 0;
// while(cin.get(s1,12))
while (gets(s1) && strcmp(s1, "") != 0) {
// if(strcmp(s1," ")==0)
// break;
Inset(root, s1);
}
while (cin >> ss) {
int ans = search(root, ss);
cout << ans << endl;
}
return 0;
}
```
- [poj 2001 Shortest Prefixes](http://poj.org/problem?id=2001)
題意:找出能唯一標示一個字串的最短字首,如果找不出,就輸出該字串。
用字典樹即可
**程式碼如下:**
```c++
#include
#include
#include
#include
using namespace std;
char list[1005][25];
struct node {
int count;
node* childs[26];
node() {
count = 0;
int i;
for (i = 0; i < 26; i++)
childs[i] = NULL;
}
};
node* root = new node;
node *current, *newnode;
void insert(char* str) {
int i, m;
current = root;
for (i = 0; i < strlen(str); i++) {
m = str[i] - 'a';
if (current->childs[m] != NULL) {
current = current->childs[m];
++(current->count);
} else {
newnode = new node;
++(newnode->count);
current->childs[m] = newnode;
current = newnode;
}
}
}
void search(char* str) {
int i, m;
char ans[25];
current = root;
for (i = 0; i < strlen(str); i++) {
m = str[i] - 'a';
current = current->childs[m];
ans[i] = str[i];
ans[i + 1] = '\0';
if (current->count == 1) //可以唯一標示該字串的字首
{
printf("%s %s\n", str, ans);
return;
}
}
printf("%s %s\n", str, ans); // 否則輸出該字串
}
int main() {
int i, t = 0;
while (scanf("%s", list[t]) != EOF) {
insert(list[t]);
t++;
}
for (i = 0; i < t; i++)
search(list[i]);
return 0;
}
```
- [hdu 4825 Xor Sum](http://acm.hdu.edu.cn/showproblem.php?pid=4825)
題意:給你一些數字,再詢問Q個問題,每個問題給一個數字,使這個數字和之前給出的數字的異或和最大。
構造字典樹,高位在前,低位在後,然後順著字典樹根向深處遞迴查詢
**程式碼如下:**
```c++
#include
#include
#include
#include
#include
#include
#include