Trie( prefix tree ) 原理、C++实现及应用
Trie
There are several data structures, like balanced trees and hash tables, which give us the possibility to search for a word in a dataset of strings. Then why do we need trie? Although hash table has O ( 1 ) O(1) O(1) time complexity for looking for a key, it is not efficient in the following operations :
- Finding all keys with a common prefix.
- Enumerating a dataset of strings in lexicographical order.
Another reason why trie outperforms hash table, is that as hash table increases in size, there are lots of hash collisions and the search time complexity could deteriorate to O ( n ) O(n) O(n), where nn is the number of keys inserted. Trie could use less space compared to Hash Table when storing many keys with the same prefix. In this case using trie has only O ( m ) O(m) O(m) time complexity, where mm is the key length. Searching for a key in a balanced tree costs O ( m log n ) O(m \log n) O(mlogn) time complexity.
基本结构
题目来源于LeetCode, 代码上传后测试通过
使用unique_ptr,构建数据结构,在一定程度上减少memory leak
#include "stdafx.h"
#include<iostream>
#include<memory>
#include<algorithm>
#define ALPHABET_SIZE 26
class Trie {
public:
bool isEnd;
unique_ptr<Trie> links[ALPHABET_SIZE];
/** Initialize your data structure here. */
Trie() {
this->isEnd = false;
}
bool containsChar(char ch) {
return links[ch - 'a'] != nullptr;
}
void put(char ch, Trie* newt) {
links[ch - 'a'].reset(newt);
}
Trie* get(char ch) {
return links[ch - 'a'].get();
}
/** Inserts a word into the trie. */
void insert(string word) {
// start from root node
Trie* root = this;
for (int i = 0; i < word.length(); i++) {
// create a new node if path doesn't exists
if (!root->containsChar(word[i])) {
root->put(word[i], new Trie());
}
root = root->get(word[i]);
}
// make current node as a keyword
root->isEnd = true;
}
/** Returns if the word is in the trie. */
bool search(string word) {
if (this == nullptr)
return false;
Trie* root = this;
for (int i = 0; i < word.length(); i++) {
root = root->get(word[i]);
if (root == nullptr)
return false;
}
return root->isEnd;
}
/** Returns if there is any word in the trie that starts with the given prefix. */
bool startsWith(string prefix) {
if (this == nullptr)
return false;
Trie* root = this;
int i = 0;
for (; i < prefix.length(); i++) {
root = root->get(prefix[i]);
if (root == nullptr)
return false;
}
return i == prefix.length();
}
~Trie(){}
};
回溯查找
class WordDictionary {
public:
unique_ptr<Trie> t;
/** Initialize your data structure here. */
WordDictionary() {
t.reset(new Trie());
}
/** Adds a word into the data structure. */
void addWord(string word) {
t->insert(word);
}
/** Returns if the word is in the data structure. A word could contain the dot character '.' to represent any one letter. */
bool search(string word) {
return searchUtil(t.get(), word, 0);
}
bool searchUtil(Trie* t, const string& word, int index) {
if (!t)
return false;
if (index == word.length())
return t->isEnd;
char c = word[index];
if (c == '.') {
for (int i = 0; i < ALPHABET_SIZE; i++) {
if (searchUtil(t->get('a' + i), word, index + 1))
return true;
}
}
else {
return searchUtil(t->get(c), word, index + 1);
}
return false;
}
~WordDictionary(){}
};
找词根
In English, we have a concept called root, which can be followed by some other words to form another longer word - let’s call this word successor. For example, the root an, followed by other, which can form another word another.
Now, given a dictionary consisting of many roots and a sentence. You need to replace all the successor in the sentence with the root forming it. If a successor has many roots can form it, replace it with the root with the shortest length.
You need to output the sentence after the replacement.
Example 1:
Input: dict = [“cat”,“bat”,“rat”], sentence = “the cattle was rattled by the battery”
Output: “the cat was rat by the bat”
class ReplaceWords {
public:
unique_ptr<Trie> t;
ReplaceWords() {
t.reset(new Trie());
}
string replaceWords(vector<string>& dict, string sentence) {
for (auto elem : dict) {
t->insert(elem);
}
string ans;
istringstream iss(sentence);
string curr;
while (iss >> curr) {
Trie* root = t.get();
int i = 0;
for (; i < curr.length(); i++) {
root = root->get(curr[i]);
if (!root){
ans.append(curr + " ");
break;
}
if (root->isEnd){
ans.append(curr.substr(0, i + 1) + " ");
break;
}
}
if (i == curr.length()) {
ans.append(curr + " ");
}
}
ans.erase(ans.length() - 1);
return ans;
}
};