2009-01-26 106 views
7

我有一個關於C++中數百個唯一字符串的列表,我需要檢查列表中是否存在一個值,但最好快閃。快速搜索C++中的字符串排序列表

我currenly與使用的std ::串一的hash_set(因爲我無法得到它與爲const char *工作),像這樣:

stdext::hash_set<const std::string> _items; 
_items.insert("LONG_NAME_A_WITH_SOMETHING"); 
_items.insert("LONG_NAME_A_WITH_SOMETHING_ELSE"); 
_items.insert("SHORTER_NAME"); 
_items.insert("SHORTER_NAME_SPECIAL"); 

stdext::hash_set<const std::string>::const_iterator it = _items.find("SHORTER_NAME")); 

if(it != _items.end()) { 
    std::cout << "item exists" << std::endl; 
} 

有沒有人有一個好主意,以便更快搜索方法沒有建立一個完整的散列表我自己?


該列表是不會更改的字符串的固定列表。它包含一個受某些bug影響的元素名稱列表,並且應該在用新版本打開時即時修復。

我在使用Aho-Corasick之前就已經構建了哈希表,但是我不太願意添加太多的複雜性。


我很驚訝的答案的數量。最後,我測試了幾種方法,結果使用了kirkus和Rob K.的答案。我之前嘗試過二分搜索,但我想我有一個小錯誤實現它(有多難......)。

結果令人震驚...我以爲我有一個快速實現使用hash_set ......好吧,結果我沒有。下面是一些統計數據(和最終碼):

現有5個按鍵和一個不存在的鍵的隨機查找,50.000倍

我原來的算法,平均需18,62
平均檢索時間平均爲2,49
二分查找平均需要0,92秒。
使用gperf生成的完美hashtable進行搜索,平均需要0,51秒。

這是我現在使用的代碼:

bool searchWithBinaryLookup(const std::string& strKey) { 
    static const char arrItems[][NUM_ITEMS] = { /* list of items */ }; 

    /* Binary lookup */ 
    int low, mid, high; 

    low = 0; 
    high = NUM_ITEMS; 
    while(low < high) { 
     mid = (low + high)/2; 
     if(arrAffectedSymbols[mid] > strKey) { 
     high = mid - 1; 
     } 
     else if(arrAffectedSymbols[mid] < strKey) { 
     low = mid + 1; 
     } 
     else { 
     return true; 
     } 
    } 

    return false; 
} 

注:這是微軟VC++所以我不使用從SGI在std ::的hash_set。


我做了一些測試今天上午的gperf使用作爲VardhanDotNet建議,這是相當快一點確實如此。

回答

10

如果你的字符串列表是固定在編譯時,使用的gperf http://www.gnu.org/software/gperf/ QUOTE: 的gperf是一個完美的哈希函數發生器。對於給定的字符串列表,它會以C或C++代碼的形式生成散列函數和散列表,以根據輸入字符串查找值。哈希函數是完美的,這意味着哈希表沒有衝突,哈希表查找只需要單個字符串比較。

gperf的輸出不受gpl或lgpl,afaik控制。

+0

嗯...我想我目前的實現速度夠快,但是我會給gperf一個嘗試,只是爲了體驗和比較材料。 – Huppie 2009-01-27 07:30:51

2

我懷疑你會想出一個更好的哈希表;如果名單不時變化,你可能已經有了最好的辦法。

最快的方法是構造一個有限狀態機來掃描輸入。我不確定最好的現代工具是什麼(從我在實踐中做這樣的事情已經有十幾年了),但Lex/Flex是標準的Unix構造函數。

FSM有一個狀態表和一個接受狀態列表。它從開始狀態開始,並對輸入進行逐個字符的掃描。每個狀態都有一個輸入字符。條目可以是進入另一個狀態,或者是因爲字符串不在列表中而中止。如果FSM在不中止的情況下到達輸入字符串的末尾,它會檢查它所處的最終狀態,它是一個接受狀態(在這種情況下,您已經匹配了字符串),或者它不是(在這種情況下,您避難「T)。

任何一本書上的編譯器應該有更多的細節,或者你可以毫無疑問在網絡上找到更多信息。

+0

我想出了一臺狀態機在這裏會做得更好,但我不太願意爲這種額外的表現增加更多的複雜性。 – Huppie 2009-01-26 14:34:52

+0

這實際上是Patricia Trie的搜索過程的工作原理。但是實施起來更直接簡單。 – user21714 2009-01-26 14:50:10

0

我不知道哪一種散列函數的MS用來蜇傷,但也許你能想出更簡單的東西(=更快),在你的特殊情況工作。該容器應該允許您使用自定義哈希類。

如果它的容器的實現問題,你也可以嘗試,如果提升std::tr1::unordered_set給出了更好的結果。

6

如果沒有標準容器滿足您的需求,您可以試試PATRICIA Trie。

最壞情況查找被你正在尋找了字符串的長度爲界。此外,字符串共享通用前綴,因此它在內存上非常容易。因此,如果您有很多相對較短的字符串,這可能是有益的。

Check it out here.

注:PATRICIA =實用算法檢索字母數字

3

編碼信息。如果它是一個固定列表,列表排序,做一個二進制搜索?我無法想象,現代CPU上只有一百個左右的字符串,你會發現算法之間有明顯的區別,除非你的應用程序除了在100%的時間內搜索所有的列表之外什麼都不做。

1

如果琴絃組的檢查數量在數百就像你說的,這是做I/O(加載一個文件,我認爲來自於磁盤,常見)時,那麼我會說:在尋找更多奇特/複雜的解決方案之前,先了解一下你的所得。

當然,也可能是你的「文件」包含數億這些字符串,在這種情況下,我想它真正開始需要時間......沒有更詳細,很難肯定地說。

我說的歸結爲「考慮用例和典型場景,之前(過度)優化」,我猜這只是一個關於邪惡根源的舊事物的專業化:) :)

0

散列表是一個很好的解決方案,通過使用預先存在的實現,您可能會獲得良好的性能。儘管我相信這個選擇被稱爲「索引」。

保留一些指針到方便的位置。例如如果它使用字母進行排序,請保留一個指向開始aa​​,ab,ac ... ba,bc,bd的所有內容...這是幾百個指針,但意味着您可以跳到列表的一部分在繼續之前非常接近結果。例如如果一個條目是「afunctionname」,那麼你可以在af和ag指針之間進行二進制搜索,比搜索整個指令要快得多......如果你總共有一百萬條記錄,你可能只需要二進制搜索一個列表幾千。

我重新發明了這個特定的輪子,但可能已經有很多實現,這將爲您節省執行頭痛,並且可能比我在此處可以粘貼的任何代碼都快。 :)

1

100個獨特的字符串?如果這不是頻繁調用,並且列表不會動態改變,我可能會使用一個直線型的const char數組來進行線性搜索。除非你經常搜索它,否則小的東西不值得額外的代碼。事情是這樣的:

const char _items[][MAX_ITEM_LEN] = { ... }; 
int i = 0; 
for (; strcmp(a, _items[i]) < 0 && i < NUM_ITEMS; ++i); 
bool found = i < NUM_ITEMS && strcmp(a, _items[i]) == 0; 

對於小,我覺得有什麼更復雜的實施和維護成本清單可能會超過其運行時間成本,你不是真的要得到比這個場地費用便宜。爲了獲得更多的速度,你可以做一個哈希表第一個字符 - >列表索引來設置i的初始值;

對於這個小的列表,你可能不會得到更快。

4

std :: vector有什麼問題?加載它,先排序(v.begin(),v.end()),然後使用lower_bound()來查看字符串是否在向量中。在已排序的隨機訪問迭代器中lower_bound保證爲O(log2 N)。如果值是固定的,我不明白需要散列。向量佔用的內存空間比散列少,分配也少。

0

您正在使用二進制搜索,即O(log(n))。你應該看插值搜索,這不是最好的「最壞的情況」,但它的平均情況是更好的:O(log(log(n))。

0

我削減&粘貼從上面的二進制搜索代碼..有與原來的二分查找代碼中的問題,如不能在100項的列表中找到第二個項目

行:

high = mid - 1; 

應該是:

high = mid;