其實這道題好像大部分人都直接用Tries倒序來解,但我以爲AC自動機可能更高效一點(畢竟是在Tries基礎上優化的算法若是還不如原始Tries彷佛說不過去)。node
根據定義寫了個原始的在堆上建立樹形結構的solution但好像性能並非很樂觀。另一些用AC解的dalao好像是用一條線性結構存儲全部結點再用指針記錄樹種結點的鏈接關係,理解起來相對複雜一些但這樣性能應該會比原始的樹形結構提高不少。算法
Tries函數
根據設定的字典中全部單詞建立的字典樹,對輸入字符串的匹配過程就是對tries的遍歷。若是某個結點爲字典中單詞的結束字母時則標記爲終止結點(終止結點不必定沒有子節點)。當遍歷走到終止結點時說明從根節點到該終止結點的路徑所表明的字符串匹配成功。性能
e.g. dictionary包含"ab", "ba", "aaab", "abab", "baa"。Tries建立爲:優化
(終止結點標記爲紅色)ui
AC自動機spa
AC自動機在tries的基礎上給各結點指定fail指針,在當前分支路徑上匹配失敗(當前結點的子結點沒有能夠繼續匹配的字符值)時可經過fail指針找到其fail結點,把目前的匹配狀態更新爲該fail結點上,若是仍然匹配則繼續轉移下一個fail結點。debug
結點結構:指針
e.g. 爲dictionary包含"ab", "ba", "aaab", "abab", "baa"的tries分配fail指針。code
輸入字符串爲abaa,當從第1個位置開始試圖匹配"abab"失敗得知只能匹配到"aba"後,轉而從第2個位置開始,利用之前的結果知道已經能夠成功匹配"ba",直接從"ba"的下一個位置開始匹配。
則走到第3層中間第2個結點a時由於當前結點沒有字符值爲a的子節點,匹配失敗。說明當前只能匹配到"aba",AC自動機會選擇轉移到對應的fail結點,也就是原先當前結點的fail指針所指向的第2層第2個a,該結點有值爲a的子結點,則向後繼續匹配,當前結點轉移爲第3層第3個a,匹配狀況更新root->b->a->a所表示的"baa"。
fail指針的分配
1) 根節點 和 根節點的全部子結點 的fail指針都指向根結點;
2) 以BFS的順序遍歷tries中每一個結點i,看它parent的fail結點是否有與i字符值相同的子結點,若是有則將fail指針指向那個子結點,沒有則繼續找下一個fail結點。若是一直找到root仍然沒有(root也沒有與i有相同字符值的子結點)則將fail指針指向root。
解題
直接套用AC自動機來構造StreamChecker。每次check當前輸入字符c時在AC自動機中沿着fail搜索,若是能找到值爲c的終止結點(isEnd==true)則表示能找到。
因此判斷邏輯是:
1. 若是沿着fail一直搜索到根結點都找不到(說明整個trie數中都沒有)值爲c的結點,返回false;
2. 在搜索過程當中找到值爲c的結點,若是是終止結點則返回true,不然一直繼續沿着fail搜索(一樣,直到搜索到根節點)仍找不到值爲c的終止結點才返回false。
直接照着這個定義寫了份粗製劣造的code。缺點:1. 性能差,速度慢;2. debug麻煩,另加了輔助打印函數才調正確; 3. 由於結點基本都是在堆上建立的,還要另寫一份析構函數確保內存都成功釋放。
class TrieNode { public: int value; TrieNode* children[26]={NULL}; TrieNode* fail; bool isEnd; TrieNode() : value(-1), fail(NULL), isEnd(false) {} TrieNode(char charValue) : value(charValue-'a'),fail(NULL),isEnd(false){} /* used to debug TrieNode() : value(-1), fail(NULL), isEnd(false), parent(-1), tier(0){} TrieNode(char charValue, TrieNode* pParent) : value(charValue-'a'), fail(NULL), isEnd(false), parent(pParent->value), tier(pParent->tier+1) {} int parent; int tier; void printBasic() const { if (tier==0) { cout << "root\n"; } else { cout << "Node: "<<(char)('a'+value) << "\nlevel: "<<tier << '\t' <<"parent: "; if (tier==1) cout << "root"; else cout <<(char)('a'+parent); cout <<endl; //how to print node whose parent is root } } void print() const { printBasic(); cout << "fail-> "; fail->printBasic(); cout << endl; } */ }; class StreamChecker { public: StreamChecker(vector<string>& words):root() { buildTrie(words); buildFail(); current=&root; } ~StreamChecker() { for(int i=0;i<26;i++) { if (root.children[i]!=NULL) releaseChildren(root.children[i]); } } void releaseChildren(TrieNode* temp) { for (int i=0;i<26;i++) { if (temp->children[i]!=NULL) releaseChildren(temp->children[i]); } delete temp; } void buildTrie(vector<string>& words) { TrieNode* temp_current; for (string word : words) { temp_current=&root; for (char c : word) { if (temp_current->children[c-'a']==NULL) //if node not build yet //used to debug //temp_current->children[c-'a']=new TrieNode(c, temp_current); temp_current->children[c-'a']=new TrieNode(c); temp_current=temp_current->children[c-'a']; //go to the target node } temp_current->isEnd=true; } } void buildFail() { root.fail=&root; queue<TrieNode*> process_queue; for (int i=0;i<26;i++) { if(root.children[i]!=NULL) { root.children[i]->fail=&root; process_queue.push(root.children[i]); } } while (!process_queue.empty()) { TrieNode* temp_current=process_queue.front(); for (int i=0;i<26;i++) { if (temp_current->children[i]!=NULL) { setFailForChild(temp_current, i); process_queue.push(temp_current->children[i]); } } process_queue.pop(); } } void setFailForChild(TrieNode* parent, int child_index) { TrieNode* to_search=parent->fail; while (to_search!=&root) { if (to_search->children[child_index]!=NULL) { parent->children[child_index]->fail=to_search->children[child_index]; return; } to_search=to_search->fail; } if (root.children[child_index]!=NULL) { parent->children[child_index]->fail=root.children[child_index]; return; } parent->children[child_index]->fail=&root; } bool query(char letter) { while (current!=&root) { if (current->children[letter-'a']!=NULL) { current=current->children[letter-'a']; if (current->isEnd==false) { TrieNode* temp=current; while (temp!=&root) { if (temp->isEnd==true) return true; else temp=temp->fail; } return false; } else { return true; } } else { current=current->fail; } } if (root.children[letter-'a']!=NULL) { current=root.children[letter-'a']; return current->isEnd; } return false; } TrieNode root; TrieNode* current; /* add for debug void printTrie() const { root.print(); queue<TrieNode*> nodes; for (int i=0;i<26;i++) { if (root.children[i]!=NULL) nodes.push(root.children[i]); } while (!nodes.empty()) { const TrieNode* current=nodes.front(); current->print(); for (int i=0;i<26;i++) { if (current->children[i]!=NULL) nodes.push(current->children[i]); } nodes.pop(); } cout << "--end.\n"; } */ }; int main() { string inputs[]={"ab","ba","aaab","abab","baa"}; vector<string> words(inputs,inputs+5); StreamChecker* checker=new StreamChecker(words); cout << "000001111100111100011111101110" << endl << endl; string testinput="aaaaabababbbababbbbababaaabaaa"; for (char c:testinput) cout << checker->query(c); delete checker; cout << endl; }