[英]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.