简体   繁体   English

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

[英]Sets and Vectors. Are sets fast in C++?

Please read the question here - http://www.spoj.com/problems/MRECAMAN/ 请在此处阅读问题-http: //www.spoj.com/problems/MRECAMAN/

The question was to compute the recaman's sequence where, a(0) = 0 and, a(i) = a(i-1)-i if, a(i-1)-i > 0 and does not come into the sequence before else, a(i) = a(i-1) + i . 问题是要计算recaman的序列,其中,如果a(i-1)-i > 0并且不属于该序列,则a(0) = 0a(i) = a(i-1)-i否则a(i) = a(i-1) + i

Now when I use vectors to store the sequence, and use the find function, the program times out. 现在,当我使用向量存储序列并使用find函数时,程序将超时。 But when I use an array and a set to see if the element exists, it gets accepted (very fast). 但是,当我使用数组和集合查看元素是否存在时,它会被接受(非常快)。 IS using set faster? 使用set更快吗?

Here are the codes: 以下是代码:

Vector implementation 向量执行

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);
}

Set Implementation 设定执行

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]);
}

Lookup in an std::vector : std::vector查找:

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

is an O(n) operation ( n being the number of elements in the vector). O(n)运算( n是向量中元素的数量)。

Lookup in an std::set (which is a balanced binary search tree): std::set查找(这是一个平衡的二进制搜索树):

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

is an O(log n) operation. O(log n)操作。

So yes, lookup in a set is (asymptotically) faster than a linear lookup in vector . 所以是的, set查找(渐近地)比vector的线性查找要快。

For the task at hand, set is faster than vector because it keeps its contents sorted and does a binary search to find a specified item, giving logarithmic complexity instead of linear complexity. 对于手头的任务, setvector更快,因为set使内容保持排序并进行二进制搜索以找到指定的项目,从而给出对数复杂度而不是线性复杂度。 When the set is small, that difference is also small, but when the set gets large the difference grows considerably. 当集合较小时,该差异也较小,但是当集合较大时,该差异会大大增加。 I think you can improve things a bit more than just that though. 我认为您可以做的事情不仅限于此。

First, I'd avoid the clumsy lookup to see if an item is already present by just attempting to insert an item, then see if that succeeded: 首先,我将避免笨拙的查找,只需尝试插入一个项目即可查看该项目是否已经存在,然后查看该操作是否成功:

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

This avoids looking up the same item twice, once to see if it was already present, and again to insert the item. 这样可以避免查找同一项目两次,一次查看是否已经存在,再一次插入该项目。 It only does a second lookup when the first one was already present, so we're going to insert some other value. 当第一个已经存在时,它只会进行第二次查找,因此我们将插入其他值。

Second, and even more importantly, you can use std::unordered_set to improve the complexity from logarithmic to (expected) constant. 其次,甚至更重要的是,可以使用std::unordered_set来提高从对数常数到(预期)常数的复杂度。 Since unordered_set uses (mostly) the same interface as std::set , this substitution is easy to make (including the optimization above. 由于unordered_set使用(大多数)与std::set相同的接口,因此这种替换很容易进行(包括上面的优化)。

Here's some code to compare the three methods: 以下是一些代码,可以比较这三种方法:

#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>>);
}

Note how std::set and std::unordered_set provide similar enough interfaces that I've written the code as a single template that can use either type of container, then for timing just instantiated that for both set and unordered_set . 请注意std::setstd::unordered_set提供足够相似的接口,我已经将代码编写为可以使用任何一种类型的容器的单个模板,然后为了定时,仅实例化了setunordered_set

Anyway, here's some results from g++ (version 4.8.1, compiled with -O3): 无论如何,这是g ++(版本4.8.1,使用-O3编译)的一些结果:

212972756       Time: 137 ms
212972756       Time: 101 ms
212972756       Time: 63 ms

Changing the lookup strategy improves speed by about 30% 1 and using unordered_set with the improved lookup strategy better than doubles the speed compared to the original--not bad, especially when the result actually looks cleaner, at least to me. 更改查找策略可以将速度提高30% 1左右,并且将unordered_set与改进后的查找策略一起使用比将原始速度提高一倍更好-不错,尤其是当结果看起来更清晰时,至少对我而言。 You might not agree that it's cleaner looking, but I think we can at least agree that I didn't write code that was a lot longer or more complex to get the speed improvement. 您可能不同意它看起来更简洁,但是我认为我们至少可以同意我没有编写更长或更复杂的代码来提高速度。


1. Simplistic analysis indicates that it should be around 25%. 1.简单化的分析表明,它应该是 25% 左右 Specifically, if we assume there are even odds of a given number being in the set already, then this eliminates half the lookups about half the time, or about 1/4 th of the lookups. 特别是,如果我们假设有一个给定数量的组已经是连赔率,那么这消除了大约一半一半的时间查找,或约1/4 的查找的。

There is only one valid answer to most "Is XY faster than UV in C++" questions: 对于大多数 “ XY比C ++中的UV快于UV”问题,只有一个有效的答案:

Use a profiler. 使用探查器。

While most algorithms (including container insertions, searches etc.) have a guaranteed complexity, these complexities can only tell you about the approximate behavior for large amounts of data. 尽管大多数算法(包括容器插入,搜索等)都保证了复杂性,但是这些复杂性只能告诉您大量数据的近似行为。 The performance for any given smaller set of data can not be easily compared, and the optimizations that a compiler can apply can not be reasonably guessed by humans. 任何给定的较小数据集的性能都无法轻易比较,而且人类无法合理地猜测编译器可以应用的优化。 So use a profiler and see what is faster. 因此, 使用探查器 ,看看有什么更快。 If it matters at all. 如果有关系的话。 To see if performance matters in that special part of your program, use a profiler . 要查看性能在程序的特定部分是否重要,请使用事件探查器

However, in your case it might be a safe bet that searching a set of ~250k elements can be faster than searching an unsorted vector of tat size. 但是,在您的情况下,一个安全的选择是,搜索约25万个元素比搜索一个未排序的tat大小的向量要快。 However, if you use the vector only for storing the inserted values and leave the sequence[i-1] out in a separate variable, you can keep the vector sorted and use an algorithm for sorted ranges like binary_search , which can be way faster than the set. 但是,如果仅将向量用于存储插入的值,并将sequence[i-1]留在单独的变量中,则可以使向量保持排序状态,并使用诸如binary_search类的排序范围算法,其速度可能比集合。

A sample implementation with a sorted vector: 具有排序向量的示例实现:

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);
}

I hope I did not introduce any bugs... 我希望我没有引入任何错误...

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

The set is a huge speedup because it's faster to look up. 该集合极大地加快了速度,因为查找起来更快。 (Btw, exists.count(a) == 0 is prettier than using find .) (顺便说一句, exists.count(a) == 0比使用find更漂亮。)

That doesn't have anything to do with vector vs array though. 虽然这与向量vs数组没有任何关系。 Adding the set to the vector version should work just as fine. 将集合添加到矢量版本应该也可以正常工作。

It is classic space-time tradeoff. 这是经典的时空权衡。 When you use only vector your program uses minimum memory but you should to find existing numbers on every step. 当您仅使用向量时,您的程序将使用最少的内存,但是您应该在每个步骤中找到现有的数字。 It is slowly. 慢慢来 When you use additional index data structure (like a set in your case) you dramatically speed up your code but your code now takes at least twice greater memory. 当您使用其他索引数据结构(例如您的情况下的集合)时,可以大大加快代码的速度,但是现在代码至少需要两倍的内存。 More about tradeoff here . 更多关于权衡的信息

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM