【问题标题】:What is the best way to sort a vector leaving the original one unaltered?对向量进行排序而不改变原始向量的最佳方法是什么?
【发布时间】:2018-05-12 05:35:58
【问题描述】:

正如标题所说,我正在寻找一种在不修改原始向量的情况下对向量进行排序的方法。 我的第一个想法当然是在排序之前创建一个向量的副本,例如:

std::vector<int> not_in_place_sort(const std::vector<int>& original)
{
   auto copy = original;
   std::sort(copy.begin(), copy.end());
   return copy;
}

但是,也许有一种更有效的方式来使用 C++ 标准算法执行排序(可能是 sorttransform 的组合?)

【问题讨论】:

  • 按值传递向量,因此它是函数的本地,排序和返回。这是你能得到的最好的。
  • @JakeFreeman 不,不是,它是一种快速的“平均”排序,但我认为你应该在做出这样的笼统陈述之前重新审视排序算法
  • 解释你所说的“排序”是什么意思。如果你想要一个已排序的向量,那么没有复制和排序是不可能的。
  • 我只是想知道是否有一个算法f(v) 的时间成本小于create_copy + std::sort。如果我需要复制向量,我可以做到,只是想知道我是否遗漏了什么
  • @Emiliano -- 一种选择 -- 不要对向量本身进行排序 -- 您可以对索引数组进行排序并使用它来索引向量。

标签: c++ sorting stl


【解决方案1】:

使用部分排序复制。这是一个例子:

vector<int> v{9,8,6,7,4,5,2,0,3,1};
vector<int> v_sorted(v.size());
partial_sort_copy(begin(v), end(v), begin(v_sorted), end(v_sorted));

现在,v 保持不变,但 v_sorted 包含 {0,1,2,3,4,5,6,7,8,9}。

【讨论】:

  • 这个解决方案被低估了。
  • 这个解决方案被高估了:它没有解释为什么partial_sort_copy 比最初复制向量并在结果上调用std::sort 更好。只要两个范围长度相同,它就不是“partial”。它更快吗?它更具可读性吗?我认为两者的答案都是“不”。
  • @SethJohnson 至少应该比向量的复制过程更快。这就是提问者可能试图实现的目标,而不是复制然后排序,而是直接将排序后的值写入目标。 partial 真的应该说general
【解决方案2】:

这是我最喜欢的。对 index 进行排序,而不是对原始数组/向量本身进行排序。

#include <algorithm>

int main() {

    int intarray[4] = { 2, 7, 3, 4 };//Array of values
    //or you can have vector of values as below
    //std::vector<int> intvec = { 2, 7, 3, 4 };//Vector of values
    int indexofarray[4] = { 0, 1, 2, 3 };//Array indices

    std::sort(indexofarray, indexofarray + 4, [intarray](int index_left, int index_right) { return intarray[index_left] < intarray[index_right]; });//Ascending order.
    //have intvec in place of intarray for vector.


}

在此之后,indexofarray[] 元素将是 0, 2, 3, 1,而 intarray[] 保持不变。

【讨论】:

    【解决方案3】:

    按照 cmets 中的建议,按值 std::vector&lt;int&gt; original 传递函数参数:

    #include <iostream>
    #include <vector>
    #include <algorithm>
    
    std::vector<int> not_in_place_sort(std::vector<int> original) {
        std::sort(original.begin(), original.end());
        return original;
    }
    
    int main() {
        std::vector<int> v = { 8, 6, 7, 2, 3, 4, 1, 5, 9 };
        std::vector<int> v2 = not_in_place_sort(v); // pass the vector by value
        std::cout << "v1: " << '\n';
        for (auto el : v) {
            std::cout << el << ' ';
        }
        std::cout << "\nv2: " << '\n';
        for (auto el : v2) {
            std::cout << el << ' ';
        }
    }
    

    这将对原始向量的副本进行排序,使原始向量保持不变。 正如下面所指出的,这可能会限制一些优化,例如RVO,但会在return 语句中调用vector's move constructor

    【讨论】:

    • 这会抑制 RVO。这没什么大不了的,因为退货会被移动,但根据应用程序,这可能很重要。
    • 这如何禁止 RVO?我认为它应该像描述的那样工作。更好的是,v = not_in_place_sort(std::move(v)) 应该和std::sort(v.begin(), v.end()) 一样便宜。
    • 好有趣,它确实可以防止 RVO。尽管如此,只要容器具有移动构造函数,按值传递和按值返回就可以提供最大的灵活性提高调用代码的清晰度:sorted(std::move(v)) 意味着假设 v 将不再是函数后有效,sorted(v) 表示仍然有效。
    【解决方案4】:

    对于您对代理排序(对索引列表进行排序)感兴趣的情况,您可能希望实现一种更灵活的算法,允许您处理不支持随机访问的容器(例如std::list)。例如:

    #include <algorithm>
    #include <iostream>
    #include <list>
    #include <numeric>
    #include <vector>
    
    template <typename Container>
    auto sorted_indices(const Container& c) {
      std::vector<typename Container::size_type> indices(c.size());
      std::iota(indices.begin(), indices.end(), 0);
      std::sort(indices.begin(), indices.end(), [&c](auto lhs, auto rhs) {
        return (*(std::next(c.begin(), lhs)) < *(std::next(c.begin(), rhs)));
      });
      return indices;
    }
    
    template <typename Container, typename Indices>
    auto display_sorted(const Container& c, const Indices& indices) {
      std::cout << "sorted: ";
      for (auto&& index : indices) {
        std::cout << *(std::next(c.begin(), index)) << " ";
      }
      std::cout << std::endl;
    }
    
    template <typename Container>
    auto display_sorted(const Container& c) {
      return display_sorted(c, sorted_indices(c));
    }
    
    template <typename Container>
    auto display(const Container& c) {
      std::cout << "as provided: ";
      for (auto&& ci : c) std::cout << ci << " ";
      std::cout << std::endl;
    }
    
    int main() {
      // random access
      const std::vector<int> a{9, 5, 2, 3, 1, 6, 4};
      display(a);
      display_sorted(a);
      display(a);
    
      std::cout << "---\n";
    
      // no random access
      const std::list<int> b{9, 5, 2, 3, 1, 6, 4};
      display(b);
      display_sorted(b);
      display(b);
    }
    

    示例运行:

    $ clang++ example.cpp -std=c++17 -Wall -Wextra
    $ ./a.out
    as provided: 9 5 2 3 1 6 4 
    sorted: 1 2 3 4 5 6 9 
    as provided: 9 5 2 3 1 6 4 
    ---
    as provided: 9 5 2 3 1 6 4 
    sorted: 1 2 3 4 5 6 9 
    as provided: 9 5 2 3 1 6 4 
    

    如您所料,依赖代理排序可能会对性能产生重要影响。例如:每次要按顺序遍历时,都可能会发生缓存未命中。另外,对于随机访问,遍历会和底层容器一样复杂:std::vector的情况下std::next(v.begin(), n)O(1),但std::list的情况下std::next(l.begin(), n)O(n) .

    【讨论】:

      【解决方案5】:

      对于 int 来说,排序索引或制作副本并排序副本并没有太大区别;数据仍然需要初始化,在索引的情况下,这将涉及一个循环分配值,而不是更快的 memcpy 例程;所以最终可能会变慢;此外,您将更多地在记忆中跳跃;所以现在缓存不能很好地完成它的工作。

      对于较大的对象,我不会对索引进行排序,而是使用指针向量。与复制对象本身相比,指针的复制成本更低;容器仍然很明显,因为它们包含对象的指针;并且排序不会尝试引用另一个向量。

      【讨论】:

        【解决方案6】:

        您可以创建另一个向量来存储索引。代码如下:

        #include <iostream>
        #include <algorithm>
        #include <vector>
        using namespace std;
        
        int main()
        {
            vector<int> numbers = {50,30,20,10,40};
            vector<int> indexOfNumbers;
            
            for(int i = 0; i < numbers.size(); i++)
            {
                indexOfNumbers.push_back(i); 
            }
            // Now, indexOfNumbers = [0,1,2,3,4]
        
            std::sort(
                indexOfNumbers.begin(), indexOfNumbers.end(), 
                [numbers](int leftIndex, int rightIndex) 
                { 
                    return numbers[leftIndex] < numbers[rightIndex]; // sort in ascending order
                }
            );
            // After sorting, indexOfNumbers = [3, 2, 1, 4, 0]
        
            // Access the sorted elements
            cout << "Accessing the sorted elements : ";
            for(int i = 0; i < numbers.size(); i++)
            {
                cout << numbers[indexOfNumbers[i]] << " ";
            }
            // prints numbers in sorted order i.e. [10,20,30,40,50]
           return 0;
        }
        

        来源:根据 Tyrer 的回答(https://stackoverflow.com/a/47537314)稍作修改

        【讨论】:

          猜你喜欢
          • 2011-01-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-11-05
          • 2015-12-31
          相关资源
          最近更新 更多