昨天校赛,遇到了一道字典树的题目,卡了挺久,原因不是不会写,而是被vector指针初始化坑了(现在改用数组了,哼)。感觉得好好总结一下字典树,下面先po这个问题,然后写个字典树模板,最后将模板修改成能够解决这个问题。
A. 问题描述
给定n个字符串(长度不超过8)构成一个字典,m个字符串作为查询,问查询的字符串是否有可能在字典中。为什么是可能呢?因为字典里有某些单词因为bug,可能有的字符被变成了’?’这个符号,相当于这个符号变成了通配符,比如c?t可以匹配cat,cbt,cct,…,cut等等。输入保证都是小写英文字母,字典里的单词最多只有一个字符是英文的问号。
B. 字典树通用模板?
可能还不够通用,不过也算是模板了:
#include <iostream>
#include <stdio.h>
#include <string>
#include <vector>
using namespace std;
const int CharsetSize = 26;
class Node
{
private:
bool final; // final为true表示有以该节点结尾的单词出现过
Node** children;
public:
Node() : final(false), children(NULL) {}
void ensureChildrenNotEmpty() {
if (children == NULL) {
children = new Node*[CharsetSize];
for (int i = 0; i < CharsetSize; ++i)
children[i] = new Node();
}
}
void insert(const string& s) {
insertHelper(this, s, 0);
}
bool find(const string& s) {
return findHelper(this, s, 0);
}
private:
void insertHelper(Node* root, const string& s, int index) {
if (index == s.size()) {
root->final = true;
return ;
}
int key = s[index] - 'a';
root->ensureChildrenNotEmpty();
root->insertHelper(root->children[key], s, index + 1);
}
bool findHelper(Node* root, const string& s, int index) {
if (index == s.size())
return root->final;
if (root->children == NULL)
return false;
int key = s[index] - 'a';
return findHelper(root->children[key], s, index + 1);
}
};
int main() {
int n, m;
cin >> n >> m;
Node* root = new Node();
string buf;
for (int i = 0; i < n; ++i) {
cin >> buf;
root->insert(buf);
}
for (int i = 0; i < m; ++i) {
cin >> buf;
if (root->find(buf))
printf("[%s] Found\n", buf.data());
else
printf("[%s] Not Found\n", buf.data());
}
return 0;
}
C. 问题分析
这个问题关键在于’?’如何表示和匹配,直接用26个分支代替它吗?很明显是不明智的做法,复杂度没必要搞到这么大,万一字符集的大小是128呢?岂不是内存浪费太多了?
所以可以考虑给它加多一个分支,特殊判断就行了。比如(注意加注释的地方):
#include <iostream>
#include <stdio.h>
#include <string>
#include <vector>
using namespace std;
const int CharsetSize = 27;
class Node
{
private:
bool final; // final为true表示有以该节点结尾的单词出现过
bool taken; // 需要加多一个标记来记录某个分支是否被占用
Node** children;
public:
Node() : final(false), taken(false), children(NULL) {}
void ensureChildrenNotEmpty() {
if (children == NULL) {
children = new Node*[CharsetSize];
for (int i = 0; i < CharsetSize; ++i)
children[i] = new Node();
}
}
void insert(const string& s) {
insertHelper(this, s, 0);
}
bool find(const string& s) {
return findHelper(this, s, 0);
}
private:
void insertHelper(Node* root, const string& s, int index) {
if (index == s.size()) {
root->final = true;
return ;
}
int key = s[index] - 'a';
// 特殊的问号放在最后面的分支
if (s[index] == '?')
key = CharsetSize - 1;
root->ensureChildrenNotEmpty();
root->children[key]->taken = true;
root->insertHelper(root->children[key], s, index + 1);
}
bool findHelper(Node* root, const string& s, int index) {
if (index == s.size())
return root->final;
// 无法往下搜索,即无法匹配
if (root->children == NULL)
return false;
int key = s[index] - 'a', tail = CharsetSize - 1;
// 如果有正常的分支,还要考虑是否有?匹配的分支。若有,则结果是两者的or
if (root->children[key]->taken) {
bool result = findHelper(root->children[key], s, index + 1);
if (root->children[tail]->taken)
return result || findHelper(root->children[tail], s, index + 1);
return result;
}
// 否则,只剩下?的分支可以匹配了
return root->children[tail]->taken && findHelper(root->children[tail], s, index + 1);
}
};
int main() {
int n, m;
cin >> n >> m;
Node* root = new Node();
string buf;
for (int i = 0; i < n; ++i) {
cin >> buf;
root->insert(buf);
}
for (int i = 0; i < m; ++i) {
cin >> buf;
if (root->find(buf))
printf("[%s] Found\n", buf.data());
else
printf("[%s] Not Found\n", buf.data());
}
return 0;
}
D. 测试样例
Case 1:
3 4
cu?e
?irl
hello
ctue
girl
irl
helloCase 2:
1 1
?
z