-
-
Save orlp/2c417ab76391b126e1d58bac4bf4af0f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Implementation of pattern-defeating quicksort. | |
// Copyright Orson R. L. Peters 2016 | |
// Distributed under the Boost Software License, Version 1.0. | |
// (See accompanying file LICENSE_1_0.txt or copy at | |
// http://www.boost.org/LICENSE_1_0.txt) | |
// See http://www.boost.org/libs/sort/ for library home page. | |
#ifndef BOOST_PDQSORT_HPP | |
#define BOOST_PDQSORT_HPP | |
#include <utility> | |
#include <algorithm> | |
#include <functional> | |
#if __cplusplus >= 201103L | |
#define BOOST_PDQSORT_PREFER_MOVE(x) std::move(x) | |
#else | |
#define BOOST_PDQSORT_PREFER_MOVE(x) (x) | |
#endif | |
namespace boost { | |
namespace sort { | |
namespace pdqsort_detail { | |
enum { | |
// Partitions below this size are sorted using insertion sort. | |
insertion_sort_threshold = 24, | |
// When we detect an already sorted partition, attempt an insertion sort that allows this | |
// amount of element moves before giving up. | |
partial_insertion_sort_limit = 8 | |
}; | |
// Returns floor(log2(n)), assumes n > 0. | |
template<class T> | |
inline int log2(T n) { | |
int log = 0; | |
while (n >>= 1) ++log; | |
return log; | |
} | |
// Sorts [begin, end) using insertion sort with the given comparison function. | |
template<class Iter, class Compare> | |
inline void insertion_sort(Iter begin, Iter end, Compare comp) { | |
typedef typename std::iterator_traits<Iter>::value_type T; | |
if (begin == end) return; | |
for (Iter cur = begin + 1; cur != end; ++cur) { | |
Iter sift = cur; | |
Iter sift_1 = cur - 1; | |
// Compare first so we can avoid 2 moves for an element already positioned correctly. | |
if (comp(*sift, *sift_1)) { | |
T tmp = BOOST_PDQSORT_PREFER_MOVE(*sift); | |
do { *sift-- = BOOST_PDQSORT_PREFER_MOVE(*sift_1); } | |
while (sift != begin && comp(tmp, *--sift_1)); | |
*sift = BOOST_PDQSORT_PREFER_MOVE(tmp); | |
} | |
} | |
} | |
// Sorts [begin, end) using insertion sort with the given comparison function. Assumes | |
// *(begin - 1) is an element smaller than or equal to any element in [begin, end). | |
template<class Iter, class Compare> | |
inline void unguarded_insertion_sort(Iter begin, Iter end, Compare comp) { | |
typedef typename std::iterator_traits<Iter>::value_type T; | |
if (begin == end) return; | |
for (Iter cur = begin + 1; cur != end; ++cur) { | |
Iter sift = cur; | |
Iter sift_1 = cur - 1; | |
// Compare first so we can avoid 2 moves for an element already positioned correctly. | |
if (comp(*sift, *sift_1)) { | |
T tmp = BOOST_PDQSORT_PREFER_MOVE(*sift); | |
do { *sift-- = BOOST_PDQSORT_PREFER_MOVE(*sift_1); } | |
while (comp(tmp, *--sift_1)); | |
*sift = BOOST_PDQSORT_PREFER_MOVE(tmp); | |
} | |
} | |
} | |
// Attempts to use insertion sort on [begin, end). Will return false if more than | |
// partial_insertion_sort_limit elements were moved, and abort sorting. Otherwise it will | |
// successfully sort and return true. | |
template<class Iter, class Compare> | |
inline bool partial_insertion_sort(Iter begin, Iter end, Compare comp) { | |
typedef typename std::iterator_traits<Iter>::value_type T; | |
if (begin == end) return true; | |
int limit = 0; | |
for (Iter cur = begin + 1; cur != end; ++cur) { | |
if (limit > partial_insertion_sort_limit) return false; | |
Iter sift = cur; | |
Iter sift_1 = cur - 1; | |
// Compare first so we can avoid 2 moves for an element already positioned correctly. | |
if (comp(*sift, *sift_1)) { | |
T tmp = BOOST_PDQSORT_PREFER_MOVE(*sift); | |
do { *sift-- = BOOST_PDQSORT_PREFER_MOVE(*sift_1); } | |
while (sift != begin && comp(tmp, *--sift_1)); | |
*sift = BOOST_PDQSORT_PREFER_MOVE(tmp); | |
limit += cur - sift; | |
} | |
} | |
return true; | |
} | |
// Sorts the elements *a, *b and *c using comparison function comp. | |
template<class Iter, class Compare> | |
inline void sort3(Iter a, Iter b, Iter c, Compare comp) { | |
if (!comp(*b, *a)) { | |
if (!comp(*c, *b)) return; | |
std::iter_swap(b, c); | |
if (comp(*b, *a)) std::iter_swap(a, b); | |
return; | |
} | |
if (comp(*c, *b)) { | |
std::iter_swap(a, c); | |
return; | |
} | |
std::iter_swap(a, b); | |
if (comp(*c, *b)) std::iter_swap(b, c); | |
} | |
// Partitions [begin, end) around pivot *begin using comparison function comp. Elements equal | |
// to the pivot are put in the right-hand partition. Returns the position of the pivot after | |
// partitioning and whether the passed sequence already was correctly partitioned. Assumes the | |
// pivot is a median of at least 3 elements and that [begin, end) is at least | |
// insertion_sort_threshold long. | |
template<class Iter, class Compare> | |
inline std::pair<Iter, bool> partition_right(Iter begin, Iter end, Compare comp) { | |
typedef typename std::iterator_traits<Iter>::value_type T; | |
// Move pivot into local for speed. | |
T pivot(BOOST_PDQSORT_PREFER_MOVE(*begin)); | |
Iter first = begin; | |
Iter last = end; | |
// Find the first element greater than or equal than the pivot (the median of 3 guarantees | |
// this exists). | |
while (comp(*++first, pivot)); | |
// Find the first element strictly smaller than the pivot. We have to guard this search if | |
// there was no element before *first. | |
if (first - 1 == begin) while (first < last && !comp(*--last, pivot)); | |
else while ( !comp(*--last, pivot)); | |
// If the first pair of elements that should be swapped to partition are the same element, | |
// the passed in sequence already was correctly partitioned. | |
bool already_partitioned = first >= last; | |
// Keep swapping pairs of elements that are on the wrong side of the pivot. Previously | |
// swapped pairs guard the searches, which is why the first iteration is special-cased | |
// above. | |
while (first < last) { | |
std::iter_swap(first, last); | |
while (comp(*++first, pivot)); | |
while (!comp(*--last, pivot)); | |
} | |
// Put the pivot in the right place. | |
Iter pivot_pos = first - 1; | |
*begin = BOOST_PDQSORT_PREFER_MOVE(*pivot_pos); | |
*pivot_pos = BOOST_PDQSORT_PREFER_MOVE(pivot); | |
return std::make_pair(pivot_pos, already_partitioned); | |
} | |
// Similar function to the one above, except elements equal to the pivot are put to the left of | |
// the pivot and it doesn't check or return if the passed sequence already was partitioned. | |
template<class Iter, class Compare> | |
inline Iter partition_left(Iter begin, Iter end, Compare comp) { | |
typedef typename std::iterator_traits<Iter>::value_type T; | |
T pivot(BOOST_PDQSORT_PREFER_MOVE(*begin)); | |
Iter first = begin; | |
Iter last = end; | |
while (comp(pivot, *--last)); | |
if (last + 1 == end) while (first < last && !comp(pivot, *++first)); | |
else while ( !comp(pivot, *++first)); | |
while (first < last) { | |
std::iter_swap(first, last); | |
while (comp(pivot, *--last)); | |
while (!comp(pivot, *++first)); | |
} | |
Iter pivot_pos = last; | |
*begin = BOOST_PDQSORT_PREFER_MOVE(*pivot_pos); | |
*pivot_pos = BOOST_PDQSORT_PREFER_MOVE(pivot); | |
return pivot_pos; | |
} | |
template<class Iter, class Compare> | |
inline void pdqsort_loop(Iter begin, Iter end, Compare comp, int bad_allowed, bool leftmost = true) { | |
typedef typename std::iterator_traits<Iter>::difference_type diff_t; | |
// Use a while loop for tail recursion elimination. | |
while (true) { | |
diff_t size = end - begin; | |
// Insertion sort is faster for small arrays. | |
if (size < insertion_sort_threshold) { | |
if (leftmost) insertion_sort(begin, end, comp); | |
else unguarded_insertion_sort(begin, end, comp); | |
return; | |
} | |
// Choose pivot as median of 3. | |
sort3(begin + size / 2, begin, end - 1, comp); | |
// If *(begin - 1) is the end of the right partition of a previous partition operation | |
// there is no element in [begin, end) that is smaller than *(begin - 1). Then if our | |
// pivot compares equal to *(begin - 1) we change strategy, putting equal elements in | |
// the left partition, greater elements in the right partition. We do not have to | |
// recurse on the left partition, since it's sorted (all equal). | |
if (!leftmost && !comp(*(begin - 1), *begin)) { | |
begin = partition_left(begin, end, comp) + 1; | |
continue; | |
} | |
// Partition and get results. | |
std::pair<Iter, bool> part_result = partition_right(begin, end, comp); | |
Iter pivot_pos = part_result.first; | |
bool already_partitioned = part_result.second; | |
// Check for a highly unbalanced partition. | |
diff_t l_size = pivot_pos - begin; | |
diff_t r_size = end - (pivot_pos + 1); | |
bool highly_unbalanced = l_size < size / 8 || r_size < size / 8; | |
// If we got a highly unbalanced partition we shuffle elements to break many patterns. | |
if (highly_unbalanced) { | |
// If we had too many bad partitions, switch to heapsort to guarantee O(n log n). | |
if (--bad_allowed == 0) { | |
std::make_heap(begin, end, comp); | |
std::sort_heap(begin, end, comp); | |
return; | |
} | |
if (l_size >= insertion_sort_threshold) { | |
std::iter_swap(begin, begin + l_size / 4); | |
std::iter_swap(pivot_pos - 1, pivot_pos - l_size / 4); | |
} | |
if (r_size >= insertion_sort_threshold) { | |
std::iter_swap(pivot_pos + 1, pivot_pos + 1 + r_size / 4); | |
std::iter_swap(end - 1, end - r_size / 4); | |
} | |
} else { | |
// If we were decently balanced and we tried to sort an already partitioned | |
// sequence try to use insertion sort. | |
if (already_partitioned && partial_insertion_sort(begin, pivot_pos, comp) | |
&& partial_insertion_sort(pivot_pos + 1, end, comp)) return; | |
} | |
// Sort the left partition first using recursion and do tail recursion elimination for | |
// the right-hand partition. | |
pdqsort_loop(begin, pivot_pos, comp, bad_allowed, leftmost); | |
begin = pivot_pos + 1; | |
leftmost = false; | |
} | |
} | |
} | |
template<class Iter, class Compare> | |
inline void pdqsort(Iter begin, Iter end, Compare comp) { | |
if (begin == end) return; | |
pdqsort_detail::pdqsort_loop(begin, end, comp, pdqsort_detail::log2(end - begin)); | |
} | |
template<class Iter> | |
inline void pdqsort(Iter begin, Iter end) { | |
typedef typename std::iterator_traits<Iter>::value_type T; | |
pdqsort(begin, end, std::less<T>()); | |
} | |
} | |
} | |
#undef BOOST_PDQSORT_PREFER_MOVE | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment