【问题标题】:Algorithm: improve c++ sorting [closed]算法:改进 C++ 排序 [关闭]
【发布时间】:2020-12-22 15:47:28
【问题描述】:

我有一个程序要求输入N-numbers (Nint-s。 我有以下代码:

#include <iostream> 
#include <vector>
#include <algorithm> 
using namespace std;

int main()
{
    int n,x;
    cin >> n;        
    vector<int> v;
    
    for (int i = 0; i < n; i++) {
        cin >> x;
        v.push_back(x);
    }
    
    sort(v.begin(), v.begin() + n);
    
    for (int i = 0; i < n; i++) {
        cout << v[i];
    }
    return 0;
}

但是当我在测试服务器上运行它时,一项测试失败了。而且好像不符合1秒的时间限制。

【问题讨论】:

  • v中的数字范围是多少?
  • 我们不知道它是在 std::sort 还是在 iostream 中花费更多时间。 (注释掉排序,看看你是否仍然得到一个时间失败?)你也可以尝试一个排序容器,因为你一次得到一个数字。
  • 使用当前机器,运行时间应该低于 1 秒。在我的机器(Ryzen 3)上,{ echo 100000; shuf -i1-100000; } | /tmp/a.out 需要 1/10 秒。
  • 如果您确定代码产生正确的结果,请尝试分析以找到瓶颈。
  • 为什么不v.end()?如果您知道所需的容量,请.reserve()。这可能是您的瓶颈。

标签: c++ algorithm sorting


【解决方案1】:

随着vector 的增长,它有时需要重新定位所有数据。如果可以避免这种情况,通常会更快。

一种方法是reserve 知道您需要的元素数量。另一种方法是从一开始就使用正确数量的元素创建vector

vector<int> v(n);                 // created with n elements
    
for(auto& x : v) cin >> x;        // a range-based for loop (no push_back needed)

//sort(v.begin(), v.begin() + n); // no, why  begin() + n?  Do:
sort(v.begin(), v.end());

for(auto x : v) cout << x;        // another range-based for loop

可能加速排序的一件事(需要 C++17)是使用接受 ExecutionPolicy 的重载并查看实现是否支持并行排序:

#include <execution>

    //...

    sort(std::execution::par, v.begin(), v.end()); // try sorting in parallel

我为三种情况创建了quick-bench test

  • 根本不使用n 的知识来预留空间,让它重新分配。
  • 使用reserve(n)
  • 使用n 来创建v 并在构造时具有正确的大小

结果:

vector 的初始化 确实 come with some cost。这是上述获胜者与使用从原始new[] 创建的unique_ptr&lt;int[]&gt; 之间的比较:

总结一下:使用并行排序和填充1可以加快排序可能您的v可以设为~26 times faster 比你当前的实现:

1 - 填充,我指的是v的实际填充,不包括读取值

【讨论】:

  • 我怀疑重新分配是个问题,但是带有引用的基于范围的循环非常好。
  • @KellyBundy 谢谢!重新分配/重定位通常证明代价高昂,所以如果从一开始就知道要分配多少元素,那肯定会有所帮助。 example - 在示例中,当 N == 100000 时有 18 个重新分配,并且移动的元素总数可能很大。不过,我并没有对此进行真正的测试,但是,这很容易避免,并且据我所知只能提供帮助。 :)
  • 是的,但是 push_back 仍然以低常数摊销 O(1)。元素平均被(重新)写入 2 到 3 次。在这种情况下只有2.0486 times。并且重定位是非常低级的并且对缓存非常友好。我希望 OP 告诉我们来源,以便我们可以在那里进行测试。现在我的猜测是它是 IO,也许 OP 甚至没有向我们展示他们的真实代码,而是他们使用endl 删除的版本。我的意思是,如果不区分打印的数字,我希望他们的代码在第一个和小的测试用例中被拒绝,甚至不会在大的测试用例中得到测试。
  • 哦,我刚刚注意到的另一件事:vector&lt;int&gt; v(n); 也需要一些时间,对吧?至少在某个时候它会写 n 个零?除非有一些诡计,比如操作系统已经有真正的预置零内存可用。但我不会指望这一点。因此,按照您的方式,您将平均 2 次写入,一次用于初始零,一次用于实际值。与 2.0486 几乎相同。当然,零可能会更快,但仍然如此。
  • @KellyBundy 也许删除一些 C++ 开销可以使输出更好。我没有查看您正确呈现的数字,但我感觉您更喜欢分配一大块(未初始化)?如果向量的初始化变得昂贵,我完全赞成。不久前我制作了几个版本的容器。它们在哪里用于 2D 容器,我还注意到,当我知道我会在阅读之前写下它们时让它们未初始化,一切都变得更快。 old tinkering
【解决方案2】:

您可以改进代码的一件事是v.push_back(x); 这一行。可以在vector&lt;int&gt; v;后面加一行预分配内存v.reserve(n);

这样你就不会一次又一次地重新分配内存。

【讨论】:

    【解决方案3】:

    这几乎是你实际做的更快。您确定没有更快的方法来解决排序问题吗?也许您只需要最大或最小的数字?

    如果您想榨取剩余的性能收益:

    vector<int> v;
    v.reserve(n); // this will help a bit
    

    解决a.Li的评论:

    如果数字的范围很小,您可以简单地计算每个数字出现的次数。对于多达一万的数值,这可能会更快。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-14
      • 1970-01-01
      • 2014-08-16
      • 2014-11-06
      相关资源
      最近更新 更多