繁体   English   中英

在单个排序调用 C++ 中对多个向量进行排序

[英]Sorting multiple vectors in single sort call C++

此答案演示了如何使用不错的新式 C++11 功能对值向量使用std::sort有效地获取索引向量(该问题也有各种重复)。 它还暗示您可以通过“使用额外的向量”来获得排序向量和排序索引的双重输出。 但是,我可以实现这一点的唯一方法是第二次调用std:sort 我正在处理长度为数十、数百、数千个元素的数组,试图专注于效率。 是否可以通过对std::sort的单个调用同时获得排序向量和排序索引?

更一般地说,我的问题是:可以通过一次排序调用对多个向量进行排序吗? 假设排序顺序仅基于提供的向量之一。

我在此期间提出的内容如下(对链接答案中的代码稍作修改)。 如您所见,它需要为每个被排序的向量调用std::sort ,即使它们都是根据单个向量的排序进行排序的。 我怀疑可能有一种方法可以通过传递对 lambda 比较函数的引用来做到这一点,但我似乎无法使其工作。

#include <numeric>
#include <algorithm>

using std;

void sort_vectors(vector<size_t> idx, vector<double> &v) {

  // sort indexes based on comparing values in v
  sort(idx.begin(), idx.end(),
       [&v](size_t i1, size_t i2) {return v[i1] < v[i2];});

  // Sort the actual vector
  sort(v.begin(), v.end());

  return idx;
}

std::sort 采用迭代器:虽然自定义排序可能在单个排序步骤中同时采用索引和值,但它不太可能有太大用处(并且可能需要不同的算法,使其变慢)。

算法设计

为什么? 因为 std::sort 在O(n*logn)时间内执行。 从排序索引中移动元素将花费O(n)时间,相比之下,这相对便宜。

使用上面的示例,在给定的链接中,我们有以下现有代码:

using namespace std;

template <typename T>
vector<size_t> sort_indexes(const vector<T> &v) 
{

  // initialize original index locations
  vector<size_t> idx(v.size());
  iota(idx.begin(), idx.end(), 0);

  // sort indexes based on comparing values in v
  sort(idx.begin(), idx.end(),
       [&v](size_t i1, size_t i2) {return v[i1] < v[i2];});

  return idx;
}

我们现在可以从这些索引创建一个排序数组,这是一个便宜的步骤:

template <typename T>
vector<T> sorted_array(const vector<T> &v, const vector<size_t>& i) 
{
    vector<T> out;
    out.reserve(v.size())
    for (auto j: i) {
        out.emplace_back(v[j]);
    }
}

如果复制值太难了,您可以使用std::reference_wrapper来创建不可为空的包装器。

template <typename T>
vector<reference_wrapper<const T>> sorted_array(const vector<T> &v, const vector<size_t>& i) 
{
    vector<reference_wrapper<const T>> out;
    out.reserve(v.size())
    for (auto j: i) {
        out.emplace_back(std::cref(v[j]));
    }
}

即使对于大型数组,这也应该非常有效。

警告

不要尝试一次对两个数组进行排序。 在对索引数组进行排序时,不要尝试移动值数组中的项目。 为什么? 因为值数组的比较是基于索引的:移动项目将破坏原始数组中的排序。 由于一旦您拥有已排序的索引,将项目移动到正确的位置就非常便宜,因此请不要担心这里的性能:排序是瓶颈。

根据排序索引对数组或向量的重新排序可以在 O(n) 时间内完成。 此示例使用第三个索引数组对两个数组进行排序。 在重新排序期间,索引数组将恢复到从 0 到 n-1 的原始状态。 我在这个例子中手动做了 iota 部分,它不使用模板,但可以很容易地转换为模板和向量:

#include <algorithm>
#include <iostream>

int main()
{
int A[8] = {8,6,1,7,5,3,4,2};
char B[8] = {'h','f','a','g','e','c','d','b'};
size_t I[8];
size_t i, j, k;
int ta;
char tb;
    // create array of indices to A[]
    for(i = 0; i < sizeof(A)/sizeof(A[0]); i++)
        I[i] = i;
    // sort array of indices according to A[]
    std::sort(I, I+sizeof(I)/sizeof(I[0]),
              [&A](int i, int j) {return A[i] < A[j];});
    // reorder A[] B[] I[] according to I[]
    for(i = 0; i < sizeof(A)/sizeof(A[0]); i++){
        if(i != I[i]){
            ta = A[i];
            tb = B[i];
            k = i;
            while(i != (j = I[k])){
                A[k] = A[j];
                B[k] = B[j];
                I[k] = k;
                k = j;
            }
            A[k] = ta;
            B[k] = tb;
            I[k] = k;
        }
    }
    for(i = 0; i < sizeof(A)/sizeof(A[0]); i++)
        std::cout << A[i] << ' ';
    std::cout << std::endl;
    for(i = 0; i < sizeof(B)/sizeof(B[0]); i++)
        std::cout << B[i] << ' ';
    std::cout << std::endl;
    return 0;
}

或者可以使用指针数组代替索引数组,这允许使用普通比较函数而不是 lambda 比较函数。

#include <algorithm>
#include <iostream>

bool compare(const int *p0, const int *p1)
{
    return *p0 < *p1;
}

int main()
{
int A[8] = {8,6,1,7,5,3,4,2};
char B[8] = {'h','f','a','g','e','c','d','b'};
int *pA[8];
size_t i, j, k;
int ta;
char tb;
    // create array of pointers to A[]
    for(i = 0; i < sizeof(A)/sizeof(A[0]); i++)
        pA[i] = &A[i];
    // sort array of pointers according to A[]
    std::sort(pA, pA+sizeof(A)/sizeof(A[0]), compare);
    // reorder A[] B[] pA[] according to pA[]
    for(i = 0; i < sizeof(A)/sizeof(A[0]); i++){
        if(i != pA[i]-A){
            ta = A[i];
            tb = B[i];
            k = i;
            while(i != (j = pA[k]-A)){
                A[k] = A[j];
                B[k] = B[j];
                pA[k] = &A[k];
                k = j;
            }
            A[k] = ta;
            B[k] = tb;
            pA[k] = &A[k];
        }
    }
    for(i = 0; i < sizeof(A)/sizeof(A[0]); i++)
        std::cout << A[i] << ' ';
    std::cout << std::endl;
    for(i = 0; i < sizeof(B)/sizeof(B[0]); i++)
        std::cout << B[i] << ' ';
    std::cout << std::endl;
    return 0;
}

您可以创建一个引用所有要排序的向量的范围,并按适当的元素对其进行排序,而不是构建一个索引向量,并将其应用于每个要排序的向量。

template <typename OrderBy, typename... Others>
void sort_multiple(OrderBy& order_by, Others&... others) {
    auto range = ranges::views::zip(order_by, others...);
    ranges::actions::sort(range.begin(), range.end(), std::less{}, [](auto & tuple){ return get<0>(tuple); });
}

这个公式唯一不幸的事情是没有一种简单的方法来指定自定义比较,因为...参数是贪婪的。

struct custom_compare_t {} custom_compare;

template <typename Compare, typename OrderBy, typename... Others>
void sort_multiple(custom_compare_t, Compare compare, OrderBy& order_by, Others&... others) {
    auto range = ranges::views::zip(order_by, others...);
    ranges::actions::sort(range.begin(), range.end(), compare, [](auto & tuple){ return get<0>(tuple); });
}

暂无
暂无

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

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