簡體   English   中英

STL :: Map - 瀏覽列表或使用find?

[英]STL::Map - Walk through list or use find?

假設我有一個方法需要從地圖中提取8個值,其中包含100個元素。 你認為哪個更好:

從開始到結束一次進入for循環,通過打開鍵拉出元素?

或者使用find 8次來獲取這些值?

走在列表中需要花費O(n)時間來查找隨機元素。

Map是一個平衡的二叉樹,因此查找是O(log n)。

因此,8執行8 * log2(n)的結果,並且行列表是(n)。 列表越大,增益越大,但在所有隨機情況下,執行查找將比執行迭代更快。

在非隨機情況下,如果有理由將所需的項目在樹中或在“開始”(左側)附近彼此靠近,那么步行/迭代將更快。 但這似乎是不相干的。

雖然我選擇了find選項,但人們對漸近性能的壓力過大。

事實上,漸近性能是可以接收相當大的輸入的算法的便利指南,但即便如此,它也不是萬無一失的。 對於任何合理的輸入,具有比另一個更差的漸近性能的算法很可能更快。

然后有時候你的輸入大小相當小(甚至是固定的)。 在這種情況下,漸近性能幾乎毫無意義。

我會用find 8次。 這將是更少(和更明顯)的代碼。

您應該盡量不做基於小數字的性能判斷,因為(a)它不可能是這種大小的性能瓶頸和(b)數字可能在未來發生變化 - 選擇具有最佳漸近的算法性能。

注意:你提到'切換'鍵可能適用於你的情況,但一般情況下你不能在地圖中打開鍵值。 允許這樣做會使代碼通過迭代在地圖中搜索M項更加復雜。

8發現是最好的,因為代碼更簡單,更清晰。

但是,考慮性能會更有趣,所以我也會這樣做。

正如Artelius在寫這個答案時說的那樣,忽略復雜性。 這是不相關的,因為你知道n = 100。 例如,插入排序比快速排序具有更差的算法復雜度(至少在平均情況下),但在幾乎所有實現中,插入排序比小n的快速排序快,因此快速排序突破到插入排序到最后。 你的n也很小,所以n - >無限的限制並不重要。

由於兩個選項的代碼都很容易編寫,我建議對其進行分析。 這將(a)告訴你哪個更快,並且(b)證明兩者都是如此之快以至於你做了什么並不重要(除非它是你的程序唯一做的事情,它做了很多)。 特別是因為你談到了打開密鑰 - 如果密鑰是一個整數類型,那么限制因素比任何實際處理更可能是內存緩存問題。

然而,如果不這樣做,通常比較搜索算法的方法是計算比較,假設它們比遍歷結構慢得多。 如果沒有別的,每個比較訪問內存並且是一個不可預測的分支,這是CPU經常最糟糕的兩件事。

如果您在開始之前對8個元素進行排序(這需要進行24次比較)而不是您建議的開關,那么因為地圖也是排序的,您只需要在每個遍歷的節點進行一次比較,再加上每個項目的一個比較你要搜索(比較每個“side”的一個節點。如果它們匹配增加兩邊。如果它們不匹配,用較小的元素增加一邊)。 所以在最壞的情況下是8 + 100,或者如果你在結束之前找到所有8個,則更少。 但是,如果他們隨機地位於地圖中,那么8的最后一個的平均位置仍然是8/9。 所以稱之為8 + 88 + 24 = 120比較,其中132為最差情況。 最好的情況是8(如果您正在搜索的內容都在開始時)+24(初始排序)= 32次比較,或者如果您在排序方面也很幸運,或者您的8個搜索項目是由於某種原因准備好了。

紅黑樹(通常是地圖)中節點的平均深度略高於log2(n)。 在這種情況下將其稱為7,因為2 ^ 7是128.所以找到8個元素應該采取類似於56的比較。 IIRC紅黑樹的平衡特征是最深節點不超過最淺節點深度的兩倍。 所以最壞的情況深度是floor(2 * log2(n)),稱之為15,總共120,最好是ceil(1/2 log2(n)),這是4.這是32次比較。

因此,假設比較是影響速度的唯一因素,那么8個發現將比線性遍歷快4倍,慢4倍,平均值高2倍。

然而,線性遍歷可能會觸及更多內存,因此該帳戶可能會更慢。 但最終對於n = 100,你說的是毫秒級,所以只做最簡單的代碼(可能是8個發現)。 我是否提到如果你真的想知道你無法預測的速度,你只需要描述一下嗎?

正如其他人所指出的那樣,我可能只會在地圖上使用find()八次並完成它。 但根據您的需求,可以考慮另一種選擇。 如果構建映射后映射中的項目不會發生太大變化,或者您不需要將插入與查找交錯,則可以嘗試將鍵/值對保持在已排序的向量中。 如果執行此操作,則可以使用lower_bound()函數以對數時間執行二進制搜索。 這樣做的好處是,如果您要查找的鍵可以被排序(並且您知道它們將始終存在),那么您可以使用從先前查找返回的迭代器作為下一個的下限。 例如,

vector::iterator a = my_map.lower_bound( my_map.begin(), my_map.end(), "a" );
vector::iterator b = my_map.lower_bound( a + 1, my_map.end(), "b" );
vector::iterator c = my_map.lower_bound( b + 1, my_map.end(), "c" );
// ...

這兩種方法都有對數查找,但這有助於減少常數。

以下是對它們的時間復雜度的分析(n是映射中的項目計數),保證以對數或更好的時間復雜度查找查找:

8 * log2 n  for 8 times find
n for the iterate through all

對於較小的數字,第一個更大(例如,對於n = 2,則為8),但是在大約43時,第一個將變得比第二個更好並保持不變。 因此,您將需要使用第一種方法,因為它也更方便代碼。

你應該使用find 8次。

將切換方法視為占用每個節點並將其比較8次。 這是800次比較,你會失去所有關鍵地圖的好處,它也可能是一個列表。

使用查找方法,您可以使用地圖的優勢遍歷列表。 我相信std :: maps是作為二叉樹實現的,這意味着搜索一個鍵只需要將你的密鑰與樹的深度進行比較,對於一個100元素的二叉樹,它將是8~。 現在,您可以找到所有8個元素,只有8 * 8個比較,或64個比較。

如果它是關鍵的,我會實現兩者並對性能進行基准測試。

從理論上講,這是否是

8 * lg(100)>?<100

其他考慮因素是這些數字中的任何一個都會改變 - 它是否會超過100個元素; 你會做超過8次搜索嗎?

讓我們假設找到鑰匙時“找到”保釋金。

讓我們進一步假設您明智地編碼“開關”,並在找到匹配后退出檢查。 我們還會假設一旦找到所有8個代碼,你就不用費心去編碼整個過程(這可能是編碼的痛苦)。

“8 find”方法可以預期(低平均值)執行50 * 8 = 400次比較。

“切換”方法可以預期(低於:平均)執行(8 * 100) - 28 = 772比較。

因此,在比較方面,8發現方法更好。 但是,比較的數量足夠小,以便您更好地選擇更容易理解的選項。 盡管如此,這可能是8找到的方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM