std :: set如何比std :: map慢?

时间:2013-04-12 12:27:59

标签: c++ performance visual-c++ map set

我试图解决this problem from acm.timus.ru,它基本上要我输出给定字符串的不同子串的数量(最大长度5000)。

我即将呈现的解决方案极其低效,并且考虑到约束条件,注定会超时限制超过判决。但是,这两种解决方案不同的唯一方式(至少就我所见/理解而言)是一个使用std::map<long long, bool>,而另一个使用std::set <long long>(参见最后一个解决方案的开头)其余的是相同的,您可以通过任何差异工具进行检查)。映射解决方案导致“测试3超出时间限制”,而设置解决方案导致“测试2超出时间限制”,这意味着测试2使得映射解决方案比设置解决方案更快地工作。如果我选择Microsoft Visual Studio 2010编译器就是这种情况。如果我选择GCC,则两种解决方案都会在测试3中产生TLE。

我不是要求如何有效地解决问题。我在问什么 是如何解释使用std::map显然比使用std::set更有效。我只是没有看到这种现象的机制,希望有人能有任何见解。

Code1(使用map,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;
}

3 个答案:

答案 0 :(得分:2)

我看到的实际差异(告诉我,如果我遗漏了任何东西)就是你在地图中的情况

hash_ans[key] = true;

在设定的情况下你做

hash_ans.insert(key);

在这两种情况下,都会插入一个元素,除非它已经存在,否则它不会执行任何操作。在这两种情况下,查找都需要找到相应的元素并在失败时插入它。在有效的每个实现中,容器将使用树,使查找同样昂贵。更重要的是,C ++标准实际上要求set::insert()map::operator[]()的复杂度为O(log n),因此两种实现的复杂性应该相同。

现在,一个人表现更好的原因是什么?一个区别是,在一种情况下,基础树的节点包含string,而在另一种情况下,它是pair<string const, bool>。由于该对包含一个字符串,它必须更大并且对机器的RAM接口施加更大的压力,因此这并不能解释加速。它可以做的是扩大节点大小,以便其他节点被推离缓存线,这对多核系统的性能不利。

总之,我会尝试一些事情:

  1. 在集合中使用相同的数据 我用struct data: string {bool b};执行此操作,即将字符串捆绑在一个结构中,该结构应具有与地图元素类似的二进制布局。作为比较器,使用less<string>,以便只有字符串实际参与比较。

  2. 在地图上使用insert()
    我不认为这应该是一个问题,但插入可能会产生一个参数的副本,即使最后没有插入。我希望它不会,所以我不太自信这会改变任何事情。

  3. 关闭调试
    大多数实现都具有诊断模式,其中验证了迭代器。您可以使用它来捕获C ++仅表示“未定义的行为”的错误,耸耸肩并且崩溃。这种模式通常不符合复杂性保证,并且总是有一些开销。

  4. 阅读代码
    如果集合和映射的实现具有不同的质量和优化级别,则可以解释这些差异。在引擎盖下,我希望map和set都可以构建在相同类型的树上,所以这里也没有太多希望。

答案 1 :(得分:1)

在这种情况下,我认为一组只比地图快一点。我仍然不认为你应该这么做,因为TLE 2或TLE 3并不是什么大不了的事。如果您在给定提交上的测试2上的相同解决方案时间限制以及下次测试3的时间限制时,可能会发生这种情况。我有一些解决方案仅在时间限制上通过测试我打赌如果我重新提交他们会失败。

我使用Ukonen Sufix树解决了这个特殊问题。

答案 2 :(得分:1)

这取决于所使用的实现算法。通常只使用关键字段使用地图实现集合。在这种情况下,使用集合而不是地图会产生非常小的开销。