Here is my first try to implement merge sort using iterators -
#include <algorithm>
#include <iterator>
#include <vector>
namespace base {
namespace merge {
template <typename Iterator, typename Compare>
void mergeBack(Iterator begin, Iterator mid, Iterator end, Compare comp)
{
typedef typename std::iterator_traits<Iterator>::value_type ElemType;
std::vector<ElemType> left, right;
left.reserve(mid - begin);
right.reserve(end - mid);
std::copy(begin, mid, std::back_inserter(left));
std::copy(mid, end, std::back_inserter(right));
size_t j = 0, k = 0;
auto i = begin;
for (; i != end && j < left.size() && k < right.size(); ++i) {
*i = comp(left[j], right[k]) ? left[j++] : right[k++];
}
while (j < left.size()) {
*(i++) = left[j++];
}
while (k < right.size()) {
*(i++) = right[k++];
}
}
template <typename Iterator, typename Compare = std::less<>>
void sort(Iterator begin, Iterator end, Compare comp = Compare())
{
if (end - begin > 1) {
const auto middle = (end - begin) / 2;
merge::sort(begin, begin + middle, comp);
merge::sort(begin + middle, end, comp);
mergeBack(begin, begin + middle, end, comp);
}
}
}
}
I'm open to advice/suggestions about the implementation and would love to hear how I could make it better.
1 Answer 1
You should prefer to move values whenever possible.
std::move(begin, mid, std::back_inserter(left));
std::move(mid, end, std::back_inserter(right));
size_t j = 0, k = 0;
auto i = begin;
for (; i != end && j < left.size() && k < right.size(); ++i) {
*i = std::move(comp(left[j], right[k]) ? left[j++] : right[k++]);
}
while (j < left.size()) {
*(i++) = std::move(left[j++]);
}
while (k < right.size()) {
*(i++) = std::move(right[k++]);
}
Every call of mergeBack will end up allocating 2 new buffers. You can avoid that by preallocating the secondary buffer and flip flopping between them but that is tricky to do in the recursive version. Which is why I prefer the iterative version that goes bottom up.
-
\$\begingroup\$ if move isn't possible, will that operation fail to compile or switch to copy operation? \$\endgroup\$Abhinav Gauniyal– Abhinav Gauniyal2017年02月10日 16:01:32 +00:00Commented Feb 10, 2017 at 16:01
-
\$\begingroup\$ Point of OP is correct, there should be some fallback mechanism. Solution can be std::move_if_noexcept() \$\endgroup\$Incomputable– Incomputable2017年02月10日 16:57:05 +00:00Commented Feb 10, 2017 at 16:57
-
\$\begingroup\$ @AbhinavGauniyal, I think that if move isn't possible it will still compile. But if move can throw, the story changes a lot. \$\endgroup\$Incomputable– Incomputable2017年02月10日 17:40:41 +00:00Commented Feb 10, 2017 at 17:40
-
\$\begingroup\$ The bottom-up mergesort suffers from the following: suppose the length of the input range is \2ドル^k + 1\,ドル where \$k\$ is a positive integer. Now the very last merge will merge a run of length \2ドル^k\$ with a run of length of one element. (One single element will introduce an entire pass over the range.) \$\endgroup\$coderodde– coderodde2017年02月10日 17:59:30 +00:00Commented Feb 10, 2017 at 17:59
-
\$\begingroup\$ @coderodde you can adjust for that by letting the last range be larger by doing a extra merge if the last block size < 0.5*the current block size \$\endgroup\$ratchet freak– ratchet freak2017年02月10日 20:18:41 +00:00Commented Feb 10, 2017 at 20:18