集和向量。在C ++中快速设置?

时间:2014-02-05 13:27:03

标签: c++ vector stl set

请在此处阅读问题 - http://www.spoj.com/problems/MRECAMAN/

问题是要计算recaman的序列,a(0) = 0a(i) = a(i-1)-i if,a(i-1)-i > 0并且不会在其他之前进入序列a(i) = a(i-1) + i

现在,当我使用向量存储序列并使用find函数时,程序会超时。但是当我使用一个数组和一个集来查看元素是否存在时,它会被接受(非常快)。是否更快地使用set

以下是代码:

矢量实现

vector <int> sequence;
sequence.push_back(0);
for (int i = 1; i <= 500000; i++)
{
    a = sequence[i - 1] - i;
    b = sequence[i - 1] + i;
    if (a > 0 && find(sequence.begin(), sequence.end(), a) == sequence.end())
        sequence.push_back(a);
    else
        sequence.push_back(b);
}

设置实施

int a[500001]
set <int> exists;
a[0] = 0;
for (int i = 1; i <= MAXN; ++i)
{
    if (a[i - 1] - i > 0 && exists.find(a[i - 1] - i) == exists.end()) a[i] = a[i - 1] - i;
    else a[i] = a[i - 1] + i;
    exists.insert(a[i]);
}

6 个答案:

答案 0 :(得分:3)

std::vector中查找:

find(sequence.begin(), sequence.end(), a)==sequence.end()

O(n)操作(n是向量中的元素数量。)

std::set(这是一个平衡的二叉搜索树)中查找:

exists.find(a[i-1] - i) == exists.end()

O(log n)操作。

所以是的,set中的查找(渐近)比vector中的线性查找更快。

答案 1 :(得分:2)

对于手头的任务,setvector更快,因为它保持其内容排序并进行二进制搜索以查找指定项目,给出对数复杂度而不是线性复杂度。当集合较小时,该差异也很小,但是当集合变大时,差异会显着增大。我认为你可以改善一些事情,但不仅如此。

首先,我会通过尝试插入项目来避免笨拙的查找以查看某个项目是否已存在,然后查看是否成功:

    if (b>0 && exists.insert(b).second)
        a[i] = b;
    else {
        a[i] = c;
        exists.insert(c);
    }

这样可以避免两次查找相同的项目,一次查看是否已经存在,再次插入项目。当第一个查找已经存在时,它只进行第二次查找,因此我们将插入一些其他值。

其次,更重要的是,您可以使用std::unordered_set来提高从对数到(预期)常量的复杂性。由于unordered_set使用(大部分)与std::set相同的界面,因此很容易进行此替换(包括上述优化。

以下是一些比较三种方法的代码:

#include <iostream>
#include <string>
#include <set>
#include <unordered_set>
#include <vector>
#include <numeric>
#include <chrono>

static const int MAXN = 500000;

unsigned original() {
    static int a[MAXN+1];
    std::set <int> exists;
    a[0] = 0;
    for (int i = 1; i <= MAXN; ++i)
    {
        if (a[i - 1] - i > 0 && exists.find(a[i - 1] - i) == exists.end()) a[i] = a[i - 1] - i;
        else a[i] = a[i - 1] + i;
        exists.insert(a[i]);
    }
    return std::accumulate(std::begin(a), std::end(a), 0U);
}

template <class container>
unsigned reduced_lookup() {
    container exists;
    std::vector<int> a(MAXN + 1);

    a[0] = 0;

    for (int i = 1; i <= MAXN; ++i) {
        int b = a[i - 1] - i;
        int c = a[i - 1] + i;

        if (b>0 && exists.insert(b).second)
            a[i] = b;
        else {
            a[i] = c;
            exists.insert(c);
        }
    }
    return std::accumulate(std::begin(a), std::end(a), 0U);

}

template <class F>
void timer(F f) {
    auto start = std::chrono::high_resolution_clock::now();
    std::cout << f() <<"\t";
    auto stop = std::chrono::high_resolution_clock::now();
    std::cout << "Time: " << std::chrono::duration_cast<std::chrono::milliseconds>(stop - start).count() << " ms\n";
}

int main() {
    timer(original);
    timer(reduced_lookup<std::set<int>>);
    timer(reduced_lookup<std::unordered_set<int>>);
}

请注意std::setstd::unordered_set如何提供类似的足够接口,我将代码编写为可以使用任何类型容器的单个模板,然后为实例化{{1 }和set

无论如何,这里有一些来自g ++(版本4.8.1,用-O3编译)的结果:

unordered_set

更改查找策略可将速度提高约30% 1 并使用212972756 Time: 137 ms 212972756 Time: 101 ms 212972756 Time: 63 ms 改进查找策略,比原始速度提高一倍 - 不错,特别是当结果实际上看起来更干净,至少对我而言。你可能不同意它看起来更干净,但我认为我们至少可以同意我没有编写更长或更复杂的代码来提高速度。


<子> 1.简单分析表明它应该大约 25%。具体来说,如果我们假设已经在集合中存在给定数字的偶数概率,那么这将大约一半的查找消除了一半的时间,或大约1/4 th 的查找。

答案 2 :(得分:1)

大多数只有一个有效的答案“在X ++中,XY比UV更快”问题:

使用分析器。

虽然大多数算法(包括容器插入,搜索等)都具有保证的复杂性,但这些复杂性只能告诉您大量数据的近似行为。任何给定的较小数据集的性能都不容易比较,并且编译器可以应用的优化不能被人类合理猜测。所以使用分析器并查看更快的内容。如果重要的话。要查看效果在您计划的特殊部分中是否重要,使用分析器

然而,在你的情况下,搜索一组~250k元素可能比搜索一个未分类的tat大小的矢量更快,这可能是一个安全的选择。但是,如果仅使用向量来存储插入的值并将sequence[i-1]保留在单独的变量中,则可以对向量进行排序并对binary_search等已排序范围使用算法,这可以是方式比集合快。

带有排序向量的示例实现:

const static size_t NMAX = 500000;
vector<int> values = {0};
values.reserve(NMAX );
int lastInserted = 0;

for (int i = 1; i <= NMAX) {
  auto a = lastInserted - i;
  auto b = lastInserted + i;

  auto iter = lower_bound(begin(values), end(values), a);
  //a is always less than the last inserted value, so iter can't be end(values)

  if (a > 0 && a < *iter) {
    lastInserted = a;
  }
  else {
    //b > a => lower_bound(b) >= lower_bound(a)
    iter = lower_bound(iter, end(values), b);
    lastInserted = b;
  }
  values.insert(iter, lastInserted);
}

我希望我没有介绍任何错误......

答案 3 :(得分:1)

如果你可以对矢量进行排序,那么在大多数情况下查找比在集合中更快,因为它更加缓存友好。

答案 4 :(得分:0)

这个集合是一个巨大的加速,因为它的查找速度更快。 (顺便说一下,exists.count(a) == 0比使用find更漂亮。)

这与vector vs array没有任何关系。将集合添加到矢量版本应该可以正常工作。

答案 5 :(得分:0)

这是经典的时空权衡。当您仅使用向量时,您的程序使用最小内存,但您应该在每一步找到现有数字。这是缓慢的。当您使用其他索引数据结构(如您的情况下的一个集合)时,您可以大大加快代码速度,但您的代码现在至少需要两倍的内存。更多关于权衡here