Как реализовать бор (trie) для вставки и поиска строк, и сколько он ест памяти?
Нужна структура для множества строк с быстрой проверкой «есть ли такая строка / такой префикс». Понимаю, что это бор (trie), но не уверен, как лучше хранить детей — массивом или мапой — и как это влияет на память при алфавите из 26 букв.
2 ответа
Бор — дерево, где путь от корня до узла кодирует префикс. Каждый узел хранит переходы по символам и флаг «здесь кончается слово». Реализация массивом узлов (а не указателями) на CP быстрее и не течёт.
struct Trie {
struct Node {
array<int, 26> next;
bool leaf = false;
Node() { next.fill(-1); }
};
vector<Node> t{1}; // корень = узел 0
void insert(const string& s) {
int v = 0;
for (char ch : s) {
int c = ch - 'a';
if (t[v].next[c] == -1) {
t[v].next[c] = t.size();
t.emplace_back();
}
v = t[v].next[c];
}
t[v].leaf = true;
}
bool contains(const string& s) {
int v = 0;
for (char ch : s) {
int c = ch - 'a';
if (t[v].next[c] == -1) return false;
v = t[v].next[c];
}
return t[v].leaf;
}
};
Вставка/поиск — O(|s|) на строку, не зависят от числа строк. Память — O(суммарная длина · алфавит) в худшем случае: при алфавите 26 каждый узел — 26 интов. Если алфавит большой (Unicode, 10^5), массив next раздуется — тогда берите unordered_map<int,int> next (медленнее по константе, но память пропорциональна реальным рёбрам).
Совет по памяти: array<int,26> это 104 байта на узел. На 10^6 символов суммарной длины это до ~10^8 байт — может не влезть в лимит. Если боры большие, либо map/unordered_map на детей, либо сжатый бор (radix tree). На большинстве олимпиадных задач массив всё же предпочтительнее из-за константы — просто следите за лимитом памяти.