mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-12 09:20:36 +00:00
AK: Make single pivot quick_sort guarantee a max stack depth of log(n)
- The change to quick_sort requires SimpleIterator to support assignment. - Rename quick_sort to single_pivot_quick_sort to make it easier to choose a specific implementation (not based on signature). - Ensure single_pivot_quick_sort does not copy the pivots - Expand sorts_without_copy test case to cover both single and dual pivot implementations.
This commit is contained in:
parent
40da077f6c
commit
9068398f6b
Notes:
sideshowbarker
2024-07-18 22:41:15 +09:00
Author: https://github.com/Maato Commit: https://github.com/SerenityOS/serenity/commit/9068398f6b9 Pull-request: https://github.com/SerenityOS/serenity/pull/5200 Reviewed-by: https://github.com/alimpfard
3 changed files with 105 additions and 19 deletions
|
@ -78,6 +78,13 @@ public:
|
|||
constexpr const ValueType* operator->() const { return &m_container[m_index]; }
|
||||
constexpr ValueType* operator->() { return &m_container[m_index]; }
|
||||
|
||||
SimpleIterator& operator=(const SimpleIterator& other)
|
||||
{
|
||||
m_index = other.m_index;
|
||||
return *this;
|
||||
}
|
||||
SimpleIterator(const SimpleIterator& obj) = default;
|
||||
|
||||
private:
|
||||
static constexpr SimpleIterator begin(Container& container) { return { container, 0 }; }
|
||||
static constexpr SimpleIterator end(Container& container)
|
||||
|
|
|
@ -86,18 +86,19 @@ void dual_pivot_quick_sort(Collection& col, int start, int end, LessThan less_th
|
|||
}
|
||||
|
||||
template<typename Iterator, typename LessThan>
|
||||
void quick_sort(Iterator start, Iterator end, LessThan less_than)
|
||||
void single_pivot_quick_sort(Iterator start, Iterator end, LessThan less_than)
|
||||
{
|
||||
for (;;) {
|
||||
int size = end - start;
|
||||
if (size <= 1)
|
||||
return;
|
||||
|
||||
int pivot_point = size / 2;
|
||||
auto pivot = *(start + pivot_point);
|
||||
|
||||
if (pivot_point)
|
||||
swap(*(start + pivot_point), *start);
|
||||
|
||||
auto&& pivot = *start;
|
||||
|
||||
int i = 1;
|
||||
for (int j = 1; j < size; ++j) {
|
||||
if (less_than(*(start + j), pivot)) {
|
||||
|
@ -107,14 +108,28 @@ void quick_sort(Iterator start, Iterator end, LessThan less_than)
|
|||
}
|
||||
|
||||
swap(*start, *(start + i - 1));
|
||||
quick_sort(start, start + i - 1, less_than);
|
||||
quick_sort(start + i, end, less_than);
|
||||
// Recur into the shorter part of the remaining data
|
||||
// to ensure a stack depth of at most log(n).
|
||||
if (i > size / 2) {
|
||||
single_pivot_quick_sort(start + i, end, less_than);
|
||||
end = start + i - 1;
|
||||
} else {
|
||||
single_pivot_quick_sort(start, start + i - 1, less_than);
|
||||
start = start + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Iterator>
|
||||
void quick_sort(Iterator start, Iterator end)
|
||||
{
|
||||
quick_sort(start, end, [](auto& a, auto& b) { return a < b; });
|
||||
single_pivot_quick_sort(start, end, [](auto& a, auto& b) { return a < b; });
|
||||
}
|
||||
|
||||
template<typename Iterator, typename LessThan>
|
||||
void quick_sort(Iterator start, Iterator end, LessThan less_than)
|
||||
{
|
||||
single_pivot_quick_sort(start, end, move(less_than));
|
||||
}
|
||||
|
||||
template<typename Collection, typename LessThan>
|
||||
|
|
|
@ -46,13 +46,77 @@ TEST_CASE(sorts_without_copy)
|
|||
};
|
||||
|
||||
Array<NoCopy, 64> array;
|
||||
|
||||
// Test the dual pivot quick sort.
|
||||
for (size_t i = 0; i < 64; ++i)
|
||||
array[i].value = (64 - i) % 32 + 32;
|
||||
|
||||
quick_sort(array, [](auto& a, auto& b) { return a.value < b.value; });
|
||||
dual_pivot_quick_sort(array, 0, array.size() - 1, [](auto& a, auto& b) { return a.value < b.value; });
|
||||
|
||||
for (size_t i = 0; i < 63; ++i)
|
||||
EXPECT(array[i].value <= array[i + 1].value);
|
||||
|
||||
// Test the single pivot quick sort.
|
||||
for (size_t i = 0; i < 64; ++i)
|
||||
array[i].value = (64 - i) % 32 + 32;
|
||||
|
||||
AK::single_pivot_quick_sort(&array[0], &array[64], [](auto& a, auto& b) { return a.value < b.value; });
|
||||
|
||||
for (size_t i = 0; i < 63; ++i)
|
||||
EXPECT(array[i].value <= array[i + 1].value);
|
||||
}
|
||||
|
||||
// This test case may fail to construct a worst-case input if the pivot choice
|
||||
// of the underlying quick_sort no longer matches the one used here.
|
||||
// So it provides no strong guarantees about the properties of quick_sort.
|
||||
TEST_CASE(maximum_stack_depth)
|
||||
{
|
||||
const int size = 256;
|
||||
int* data = new int[size];
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
data[i] = i;
|
||||
}
|
||||
|
||||
// Construct the data in such a way that the assumed pivot choice
|
||||
// of (size / 2) causes the partitions to be of worst case size.
|
||||
for (int i = 0; i < size / 2; i++) {
|
||||
swap(data[i], data[i + (size - i) / 2]);
|
||||
}
|
||||
|
||||
// Measure the depth of the call stack through the less_than argument
|
||||
// of quick_sort as it gets copied for each recursive call.
|
||||
struct DepthMeasurer {
|
||||
int& max_depth;
|
||||
int depth { 0 };
|
||||
DepthMeasurer(int& max_depth)
|
||||
: max_depth(max_depth)
|
||||
{
|
||||
}
|
||||
DepthMeasurer(const DepthMeasurer& obj)
|
||||
: max_depth(obj.max_depth)
|
||||
{
|
||||
depth = obj.depth + 1;
|
||||
if (depth > max_depth) {
|
||||
max_depth = depth;
|
||||
}
|
||||
}
|
||||
bool operator()(int& a, int& b)
|
||||
{
|
||||
return a < b;
|
||||
}
|
||||
};
|
||||
|
||||
int max_depth = 0;
|
||||
DepthMeasurer measurer(max_depth);
|
||||
AK::single_pivot_quick_sort(data, data + size, measurer);
|
||||
|
||||
EXPECT(max_depth <= 64);
|
||||
|
||||
for (int i = 0; i < size; i++)
|
||||
EXPECT(data[i] == i);
|
||||
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
TEST_MAIN(QuickSort)
|
||||
|
|
Loading…
Reference in a new issue