【问题标题】:Simultaneous in-place std::sort on a vector of keys and a vector of values在键向量和值向量上同时就地 std::sort
【发布时间】:2015-06-07 10:13:52
【问题描述】:

我有一个vector<uint64_t> keys 和一个vector<char> vals,大小都是N。我想根据keys 中的条目对keysvals 进行排序。

一个明显的解决方案是复制到vector<pair<uint64_t, char>>,对其进行排序,然后将排序后的数据复制回来,但我想避免复制,我想避免对齐填充:sizeof(pair<uint64_t, char>)2*sizeof(uint64_t) ,或 16 个字节,由于对齐;远远超过所需的 9 个字节。

也就是说,虽然下面的C++11实现是正确的,但是效率不够:

#include <algorithm>
#include <tuple>
using namespace std;
void aux_sort(vector<uint64_t> & k, vector<char> & v) {
    vector<pair<uint64_t, char> > kv(k.size());
    for (size_t i = 0; i < k.size(); ++i) kv[i] = make_pair(k[i], v[i]);
    sort(kv.begin(), kv.end());
    for (size_t i = 0; i < k.size(); ++i) tie(k[i], v[i]) = kv[i];
}

虽然下面的 C++11 实现是正确的,但我想使用 std::sort 而不是手动编写自己的排序算法:

#include <algorithm>
using namespace std;
void aux_sort(vector<uint64_t> & k, vector<char> & v) {
    for (size_t i = 0; i < k.size(); ++i)
        for (size_t j = i; j--;)
            if (k[j] > k[j + 1]) {
                iter_swap(&k[j], &k[j + 1]);
                iter_swap(&v[j], &v[j + 1]);
            }
}

(编辑添加,以响应@kfsone)虽然以下实现是正确的,但它不是就地的,因为根据indices 的排列需要一个副本(或者,一个非常复杂的就地线性时间我不会实现的置换算法):

#include <algorithm>
#include <tuple>
using namespace std;
void aux_sort(vector<uint64_t> & k, vector<char> & v) {
    vector<size_t> indices(k.size());
    iota(indices.begin(), indices.end(), 0);
    sort(indices.begin(), indices.end(),
        [&](size_t a, size_t b) { return k[a] < k[b]; });
    vector<uint64_t> k2 = k;
    vector<char> v2 = v;
    for (size_t i = 0; i < k.size(); ++i)
        tie(k[i], v[i]) = make_pair(k2[indices[i]], v2[indices[i]]);
}

将诸如std::sort 之类的 STL 算法应用于就地键/值对序列的最简单方法是什么,键和值存储在单独的向量中?

背景:我的应用程序正在读取代表地形的大型(40 000 x 40 000)栅格,一次一行。一个栅格为每个单元分配一个介于 0 和 10 000 000 之间的标签,以便标签是连续的,另一个栅格为每个单元分配一个介于 0 和 255 之间的值。我想以有效的方式对每个标签的值求和,我认为最快的方法是对标签行进行排序,并且对于排序过程中的每个交换,在值行中应用相同的交换。我想避免手动编码 std::sort、std::set_intersection 和其他代码。

【问题讨论】:

  • 您始终可以为vector k 创建一个索引向量,对索引进行排序,然后将其用作vector v 值的跳转表。
  • 您对解决方案进行所有限制的目的是什么?你想节省时间吗?内存?
  • 我认为您可以创建一个“外观”对象,其中包含对keysvals 的引用(或者更好的是,直接链接到下划线内存位置),实现迭代器、比较和交换,以及然后在上面应用sort
  • @KarolyHorvath 这叫 zip 迭代器,不是吗?

标签: c++ algorithm c++11 stl


【解决方案1】:

范围适配器。最直接的路线是 zip range,它分别在 T 和 U 上采用两个相等长度的范围,并产生一个超过 pair&lt;T&amp;,U&amp;&gt; 的范围。 (容器是一种范围——一个拥有其内容的范围)

然后您按.first 排序(或使用默认排序,.second 确定平局)。

范围从来都不是一个容器,包装成对是在每次取消引用 zip 迭代器时动态进行的。

boost 有一个 zip 迭代器和 zip 范围,但您可以自己编写它们。 boost 迭代器/范围 may be read only,但该链接还包含一个未实现的 zipping 实现,并且可能 boost 已升级。

【讨论】:

  • @kfsone 代码的存在与否并不是某种神奇的属性,它本身就表明答案是有用还是无用。这个答案中的方法是一个很好的方法,并且答案有足够的细节,您可以根据这个答案编写工作代码。
【解决方案2】:

您可以使用thrust 库和sort by key 函数。不是 STL,但具有易于移植到 nVIdia GPU 的(可疑)优势。

【讨论】:

    【解决方案3】:

    事实上,很容易根据indices就地置换输入向量(与问题中的说法相反):

    #include <algorithm>
    #include <tuple>
    using namespace std;
    void aux_sort(vector<uint64_t> & k, vector<char> & v) {
        vector<size_t> indices(k.size());
        iota(indices.begin(), indices.end(), 0);
        sort(indices.begin(), indices.end(),
            [&](size_t a, size_t b) { return k[a] < k[b]; });
        for (size_t i = 0; i < k.size(); ++i)
            while (indices[i] != i) {
                swap(k[i], k[indices[i]]);
                swap(v[i], v[indices[i]]);
                swap(indices[i], indices[indices[i]]);
            }
    }
    

    但是,这种解决方案可能是不可取的,因为它会导致比排序本身更多的缓存错误,因为输入按indices 的顺序遍历,这可能会导致每个元素出现一个缓存错误。另一方面,快速排序导致的缓存错误要少得多(当枢轴是随机的时,O(n/B log n/M),其中 B 是缓存行的大小,M 是缓存的大小)。

    【讨论】:

      【解决方案4】:

      我认为不可能满足您为解决方案设置的所有约束。几乎可以肯定的是,破解 STL 来对数组进行排序是可能的。但是,与仅复制数据、对其进行排序和复制回来相比,该解决方案可能既笨拙又慢。

      如果您可以选择,您可能希望首先将数据存储在单个 vector 中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-04-28
        • 1970-01-01
        • 1970-01-01
        • 2022-08-18
        • 1970-01-01
        • 2016-03-01
        • 1970-01-01
        • 2018-05-16
        相关资源
        最近更新 更多