[英]How is std::set slower than std::map?
我試圖從acm.timus.ru解決這個問題,它基本上要我輸出給定字符串的不同子串的數量(最大長度5000)。
我即將呈現的解決方案極其低效,並且考慮到約束條件,注定會超時限制超過判決。 但是,這兩種解決方案不同的唯一方法(至少據我所知/理解)是一個使用std::map<long long, bool>
,而另一個使用std::set <long long>
(參見最后一個for循環的開頭。其余的是相同的,你可以通過任何diff工具檢查)。 映射解決方案導致“測試3超出時間限制”,而設置解決方案導致“測試2超出時間限制”,這意味着測試2使得映射解決方案比設置解決方案更快地工作。 如果我選擇Microsoft Visual Studio 2010編譯器就是這種情況。 如果我選擇GCC,則兩種解決方案都會在測試3中產生TLE。
我不是要求如何有效地解決問題。 我要問的是如何解釋使用std::map
顯然比使用std::set
更有效。 我只是沒有看到這種現象的機制,並希望有人可以有任何見解。
Code1(使用地圖,TLE 3):
#include <iostream>
#include <map>
#include <string>
#include <vector>
using namespace std;
int main ()
{
string s;
cin >> s;
vector <long long> p;
p.push_back(1);
for (int i = 1; i < s.size(); i++)
p.push_back(31 * p[i - 1]);
vector <long long> hash_temp;
hash_temp.push_back((s[0] - 'a' + 1) * p[0]);
for (int i = 1; i < s.size(); i++)
hash_temp.push_back((s[i] - 'a' + 1) * p[i] + hash_temp[i - 1]);
int n = s.size();
int answer = 0;
for (int i = 1; i <= n; i++)
{
map <long long, bool> hash_ans;
for (int j = 0; j < n - i + 1; j++)
{
if (j == 0)
hash_ans[hash_temp[j + i - 1] * p[n - j - 1]] = true;
else
hash_ans[(hash_temp[j + i - 1] - hash_temp[j - 1]) * p[n - j - 1]] = true;
}
answer += hash_ans.size();
}
cout << answer;
}
Code2(使用set,TLE 2):
#include <iostream>
#include <string>
#include <vector>
#include <set>
using namespace std;
int main ()
{
string s;
cin >> s;
vector <long long> p;
p.push_back(1);
for (int i = 1; i < s.size(); i++)
p.push_back(31 * p[i - 1]);
vector <long long> hash_temp;
hash_temp.push_back((s[0] - 'a' + 1) * p[0]);
for (int i = 1; i < s.size(); i++)
hash_temp.push_back((s[i] - 'a' + 1) * p[i] + hash_temp[i - 1]);
int n = s.size();
int answer = 0;
for (int i = 1; i <= n; i++)
{
set <long long> hash_ans;
for (int j = 0; j < n - i + 1; j++)
{
if (j == 0)
hash_ans.insert(hash_temp[j + i - 1] * p[n - j - 1]);
else
hash_ans.insert((hash_temp[j + i - 1] - hash_temp[j - 1]) * p[n - j - 1]);
}
answer += hash_ans.size();
}
cout << answer;
}
我看到的實際差異(告訴我,如果我錯過了什么)是你在地圖中的情況
hash_ans[key] = true;
而在你做的情況下
hash_ans.insert(key);
在這兩種情況下,都會插入一個元素,除非它已經存在,否則它不會執行任何操作。 在這兩種情況下,查找都需要找到相應的元素並在失敗時插入它。 在有效的每個實現中,容器將使用樹,使查找同樣昂貴。 更重要的是,C ++標准實際上要求set::insert()
和map::operator[]()
復雜度為O(log n),因此兩種實現的復雜性應該相同。
現在,一個人表現得更好的原因是什么? 一個區別是,在一種情況下,底層樹的節點包含一個string
,而在另一種情況下,它是一pair<string const, bool>
。 由於該對包含一個字符串,它必須更大並且對機器的RAM接口施加更大的壓力,因此這並不能解釋加速。 它可以做的是擴大節點大小,以便其他節點被推離緩存線,這可能對多核系統的性能有害。
總之,我嘗試過一些事情:
在集合中使用相同的數據
我用struct data: string {bool b};
做這個struct data: string {bool b};
即將字符串捆綁在一個結構中,該結構應具有與地圖元素類似的二進制布局。 作為比較器,使用less<string>
,以便只有字符串實際參與比較。
在地圖上使用insert()
我不認為這應該是一個問題,但插入可能會產生一個參數的副本,即使最后沒有插入。 我希望它不會,所以我不太自信這將改變任何事情。
關閉調試
大多數實現都具有診斷模式,其中驗證了迭代器。 您可以使用它來捕獲C ++僅表示“未定義的行為”的錯誤,聳聳肩並且崩潰。 此模式通常不符合復雜性保證,並且總是有一些開銷。
閱讀代碼
如果集合和映射的實現具有不同的質量和優化級別,則可以解釋這些差異。 在引擎蓋下,我希望map和set都可以構建在相同類型的樹上,所以這里也沒有太多希望。
在我猜這種情況下,一組只比地圖快一點。 我仍然不認為你應該這么做,因為TLE 2或TLE 3並不是什么大不了的事。 如果您在給定提交上的測試2上的相同解決方案時間限制和下次測試3的時間限制時,可能會發生這種情況。我有一些解決方案僅在時間限制上通過測試我打賭如果我重新提交他們會失敗。
我使用Ukonen Sufix樹解決了這個特殊問題。
這取決於所使用的實現算法。 通常只使用關鍵字段使用地圖實現集合。 在這種情況下,使用集合而不是映射會有非常小的開銷。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.