【问题标题】:How to efficiently merge sort with vectors using C++如何使用 C++ 有效地将排序与向量合并
【发布时间】:2017-07-07 14:49:29
【问题描述】:

我通过使用向量作为函数在 C++ 中实现了合并排序 参数而不是索引(开始,结束)。但是,我很想 知道这样做是否有任何权衡,就速度而言 和空间复杂度

代码:

void mergeSort(std::vector<int> &array) {
    if(array.size() == 1) return;
    else {
      const unsigned int len = array.size();
      const int lo = floor((double)len/2);
      const int hi = ceil((double)len/2);

      std::vector<int> L(&array[0], &array[lo]);
      std::vector<int> R(&array[lo], &array[len]);

      mergeSort(L);
      mergeSort(R);
      merge(array, L, R);
    }
    return;
}

每次调用合并排序都创建新列表可能不是可行的方法, 但这就是合并排序功能的工作方式。另外,速度有多快/多慢:

std::vector<int> L(&array[0], &array[lo]);

合并函数如下所示:

void merge(
           std::vector<int> &array, 
           std::vector<int> &L, 
           std::vector<int> &R
          ) {
    std::vector<int>::iterator a = array.begin();
    std::vector<int>::iterator l = L.begin();
    std::vector<int>::iterator r = R.begin();

    while(l != L.end() && r != R.end()) {
      if (*l <= *r) {
      *a = *l;
      l++;
      }
      else {
        *a = *r;
        r++;
      }
      a++;
   }
   while (l != L.end()) {
     *a = *l;
     a++;
     l++;
   }
   while (r != R.end()) {
     *a = *r;
     a++;
     r++;
   }
   return;

}

【问题讨论】:

  • 使用std::vector 而不是迭代器的权衡实际上只是您的算法不再是通用的。由于算法本身在内部使用迭代器,我很难找到优势。您真的必须测试和衡量自己才能知道是否有任何性能优势。
  • 考虑将问题发布到codereview.stackexchange.com
  • 这不太可能产生明显的性能差异,但执行 lo = len / 2; hi = len - lo; 可以避免浮点数学运算。
  • 保留一些长度 (l.size() + r.size());由于您采用了一体化方法,我认为使用原始数据会更快( l.data() ),然后使用 duff 设备和/或 omp for-loop 优化。如果你在某个循环中使用这个函数,那么考虑 do-while 更快
  • 性能是主要目标吗?如果是这样,那么对于自上而下的合并排序,对工作向量进行一次分配,并使用相互递归来根据递归级别改变合并的方向。实现自下而上的合并排序会稍微快一些。输入参数是指向向量或数组的开始和结束的指针会更通用。

标签: c++ sorting merge


【解决方案1】:

嗯,不需要在每次调用合并时创建新空间。 std::vector&lt;int&gt; L(&amp;array[0], &amp;array[lo]); 实际上会创建空间来容纳 lo 元素,并将执行 lo 副本。

您永远不会使用超过O(n) 的额外空间来存储值。那么,为什么不分配一个足够大的缓冲区来容纳 预先复制整个向量并使每个递归调用对数据的特定部分进行操作?这样您就不必在每次调用时创建新向量。

另外,我还鼓励您使 mergesort 仅在迭代器上工作,而不是仅在 vector&lt;int&gt; 上工作。像下面这样的界面应该足够了。

template < typename Iterator, typename Compare>
void mergesort(Iterator s, Iterator e, Compare cmp);

On Github 你可以找到我不久前实现的合并排序版本。我猜应该够了。

【讨论】:

  • 让暂存向量适应不可复制分配的类型并不是那么简单。您的链接实现没有考虑到这一点。
  • move assignable 不足以容纳这种情况?感谢您指出这一点,我以前从未想过这个问题。
  • 我没有仔细检查代码,但我确实看到你没有移动元素。标准容器现在非常对要保存的类型的要求很宽松,您可能需要检查一下。
  • 太棒了!谢谢@大卫。最好开始使用这些模板。另外,std::distance 看起来很可爱!
【解决方案2】:

合并排序所需的唯一额外内存是大小为n 的数组,用于合并在算法的任何步骤中产生的两个已排序vectors 中的任何一个。显然,您的解决方案使用更多。在第一次合并时,它分配了两个 n/2 长度的向量,然后它将是四个 n/4 的向量,等等,总共给了 n * log(n)。这比n 略多。

分配vector 的成本通常与其长度成线性关系(如果复制vector 的元素可以在O(1) 中完成),但你应该记住,如果你在堆上分配内存是昂贵的操作没有使用自定义分配器。分配内存可能会发出系统调用,它可能会使用复杂的算法来找到连续的内存块以满足您的要求。它甚至可能需要将已分配的内存块移动到其他地方。因此,如果您只能坚持一次分配,那么多次分配内存真的没有意义。

【讨论】:

    猜你喜欢
    • 2016-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-29
    • 2020-10-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多