[英]Sets and Vectors. Are sets fast in C++?
请在此处阅读问题-http: //www.spoj.com/problems/MRECAMAN/
问题是要计算recaman的序列,其中,如果a(i-1)-i > 0
并且不属于该序列,则a(0) = 0
且a(i) = a(i-1)-i
否则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]);
}
在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
的线性查找要快。
对于手头的任务, set
比vector
更快,因为set
使内容保持排序并进行二进制搜索以找到指定的项目,从而给出对数复杂度而不是线性复杂度。 当集合较小时,该差异也较小,但是当集合较大时,该差异会大大增加。 我认为您可以做的事情不仅限于此。
首先,我将避免笨拙的查找,只需尝试插入一个项目即可查看该项目是否已经存在,然后查看该操作是否成功:
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::set
和std::unordered_set
提供足够相似的接口,我已经将代码编写为可以使用任何一种类型的容器的单个模板,然后为了定时,仅实例化了set
和unordered_set
。
无论如何,这是g ++(版本4.8.1,使用-O3编译)的一些结果:
212972756 Time: 137 ms
212972756 Time: 101 ms
212972756 Time: 63 ms
更改查找策略可以将速度提高30% 1左右,并且将unordered_set
与改进后的查找策略一起使用比将原始速度提高一倍更好-不错,尤其是当结果看起来更清晰时,至少对我而言。 您可能不同意它看起来更简洁,但是我认为我们至少可以同意我没有编写更长或更复杂的代码来提高速度。
1.简单化的分析表明,它应该是在 25% 左右 。 特别是,如果我们假设有一个给定数量的组已经是连赔率,那么这消除了大约一半一半的时间查找,或约1/4 个的查找的。
对于大多数 “ XY比C ++中的UV快于UV”问题,只有一个有效的答案:
使用探查器。
尽管大多数算法(包括容器插入,搜索等)都保证了复杂性,但是这些复杂性只能告诉您大量数据的近似行为。 任何给定的较小数据集的性能都无法轻易比较,而且人类无法合理地猜测编译器可以应用的优化。 因此, 使用探查器 ,看看有什么更快。 如果有关系的话。 要查看性能在程序的特定部分是否重要,请使用事件探查器 。
但是,在您的情况下,一个安全的选择是,搜索约25万个元素比搜索一个未排序的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);
}
我希望我没有引入任何错误...
如果可以对向量进行排序,则在大多数情况下,查找比在集合中查找要快,因为它对缓存更友好。
该集合极大地加快了速度,因为查找起来更快。 (顺便说一句, exists.count(a) == 0
比使用find
更漂亮。)
虽然这与向量vs数组没有任何关系。 将集合添加到矢量版本应该也可以正常工作。
这是经典的时空权衡。 当您仅使用向量时,您的程序将使用最少的内存,但是您应该在每个步骤中找到现有的数字。 慢慢来 当您使用其他索引数据结构(例如您的情况下的集合)时,可以大大加快代码的速度,但是现在代码至少需要两倍的内存。 更多关于权衡的信息 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.