简体   繁体   English

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

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

This answer demonstrates how to efficiently obtain an indices vector using std::sort on a vector of values using the nice new-ish C++11 functionality (there's also a variety of duplicates of that question as well).此答案演示了如何使用不错的新式 C++11 功能对值向量使用std::sort有效地获取索引向量(该问题也有各种重复)。 It also hints that you can obtain the double output of both the sorted vector and the sorted indices by "using an extra vector."它还暗示您可以通过“使用额外的向量”来获得排序向量和排序索引的双重输出。 However, the only way I can achieve this is by calling std:sort a second time.但是,我可以实现这一点的唯一方法是第二次调用std:sort I'm working with arrays with lengths of tens, maybe hundreds, of thousands of elements trying to focus on efficiency.我正在处理长度为数十、数百、数千个元素的数组,试图专注于效率。 Is it possible to obtain both the sorted vector and the indices of the sort from a single call to std::sort ?是否可以通过对std::sort的单个调用同时获得排序向量和排序索引?

More generally, my question is: can one sort multiple vectors with a single sort call?更一般地说,我的问题是:可以通过一次排序调用对多个向量进行排序吗? The assumption is the sorting order is based on only one of the supplied vectors.假设排序顺序仅基于提供的向量之一。

What I've come up with in the meantime is below (a slight modification to the code in the linked answer).我在此期间提出的内容如下(对链接答案中的代码稍作修改)。 As you can see, it requires a call to std::sort for each vector being sorted, even though they are all to be ordered according to the sorting of a single vector.如您所见,它需要为每个被排序的向量调用std::sort ,即使它们都是根据单个向量的排序进行排序的。 I suspect there may be a way to do this by passing references to the lambda compare function, but I can't seem to make it work.我怀疑可能有一种方法可以通过传递对 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 takes iterators: Although a custom sort could likely take both indexes and the values in a single sort step, it's unlikely to be of much use (and may require different algorithms, making it slower). std::sort 采用迭代器:虽然自定义排序可能在单个排序步骤中同时采用索引和值,但它不太可能有太大用处(并且可能需要不同的算法,使其变慢)。

Algorithm Design算法设计

Why?为什么? Because std::sort performs in O(n*logn) time.因为 std::sort 在O(n*logn)时间内执行。 Moving elements from the sorted indexes will take O(n) time, which is relatively cheap in comparison.从排序索引中移动元素将花费O(n)时间,相比之下,这相对便宜。

Using the example from above, in the link given, we have this existing code:使用上面的示例,在给定的链接中,我们有以下现有代码:

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

We can now create a sorted array from these indexes, a cheap step:我们现在可以从这些索引创建一个排序数组,这是一个便宜的步骤:

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

If copying the values is too prohibitive, you can use a std::reference_wrapper to create a non-nullable wrapper.如果复制值太难了,您可以使用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]));
    }
}

Even for large arrays, this should be pretty efficient.即使对于大型数组,这也应该非常有效。

Caution警告

Don't try to sort two arrays at once.不要尝试一次对两个数组进行排序。 Don't try to move items in your value array when sorting the index array.在对索引数组进行排序时,不要尝试移动值数组中的项目。 Why?为什么? Because the comparison is index-based for the value array: moving items will destroy the sort in the original array.因为值数组的比较是基于索引的:移动项目将破坏原始数组中的排序。 Since moving the items to the correct position is so cheap once you have the sorted indexes, don't worry about performance here: the sort is the bottleneck.由于一旦您拥有已排序的索引,将项目移动到正确的位置就非常便宜,因此请不要担心这里的性能:排序是瓶颈。

The reorder of the array(s) or vector(s) according to sorted indices can be done in place in O(n) time.根据排序索引对数组或向量的重新排序可以在 O(n) 时间内完成。 This example sorts two arrays using a third array of indices.此示例使用第三个索引数组对两个数组进行排序。 During the reorder, the array of indices is restored back to it's original state of going from 0 to n-1.在重新排序期间,索引数组将恢复到从 0 到 n-1 的原始状态。 I manually did the iota part in this example, and it doesn't use templates, but could be easily converted to templates and vectors:我在这个例子中手动做了 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;
}

or an array of pointers can be used instead of an array of indices, which allows a normal compare function instead of a lambda compare function.或者可以使用指针数组代替索引数组,这允许使用普通比较函数而不是 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;
}

Rather than constructing an index vector, and applying it to each of the vectors you want to sort, you can instead make a range that references all the vectors to sort, and sort that by the appropriate element.您可以创建一个引用所有要排序的向量的范围,并按适当的元素对其进行排序,而不是构建一个索引向量,并将其应用于每个要排序的向量。

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

The only unfortunate thing about this formulation is that there isn't a simple way of specifying a custom compare, because ... arguments are greedy.这个公式唯一不幸的事情是没有一种简单的方法来指定自定义比较,因为...参数是贪婪的。

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