mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +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 const ValueType* operator->() const { return &m_container[m_index]; }
|
||||||
constexpr ValueType* operator->() { 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:
|
private:
|
||||||
static constexpr SimpleIterator begin(Container& container) { return { container, 0 }; }
|
static constexpr SimpleIterator begin(Container& container) { return { container, 0 }; }
|
||||||
static constexpr SimpleIterator end(Container& container)
|
static constexpr SimpleIterator end(Container& container)
|
||||||
|
|
|
@ -86,35 +86,50 @@ void dual_pivot_quick_sort(Collection& col, int start, int end, LessThan less_th
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Iterator, typename LessThan>
|
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)
|
||||||
{
|
{
|
||||||
int size = end - start;
|
for (;;) {
|
||||||
if (size <= 1)
|
int size = end - start;
|
||||||
return;
|
if (size <= 1)
|
||||||
|
return;
|
||||||
|
|
||||||
int pivot_point = size / 2;
|
int pivot_point = size / 2;
|
||||||
auto pivot = *(start + pivot_point);
|
if (pivot_point)
|
||||||
|
swap(*(start + pivot_point), *start);
|
||||||
|
|
||||||
if (pivot_point)
|
auto&& pivot = *start;
|
||||||
swap(*(start + pivot_point), *start);
|
|
||||||
|
|
||||||
int i = 1;
|
int i = 1;
|
||||||
for (int j = 1; j < size; ++j) {
|
for (int j = 1; j < size; ++j) {
|
||||||
if (less_than(*(start + j), pivot)) {
|
if (less_than(*(start + j), pivot)) {
|
||||||
swap(*(start + j), *(start + i));
|
swap(*(start + j), *(start + i));
|
||||||
++i;
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
swap(*start, *(start + i - 1));
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
swap(*start, *(start + i - 1));
|
|
||||||
quick_sort(start, start + i - 1, less_than);
|
|
||||||
quick_sort(start + i, end, less_than);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Iterator>
|
template<typename Iterator>
|
||||||
void quick_sort(Iterator start, Iterator end)
|
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>
|
template<typename Collection, typename LessThan>
|
||||||
|
|
|
@ -46,13 +46,77 @@ TEST_CASE(sorts_without_copy)
|
||||||
};
|
};
|
||||||
|
|
||||||
Array<NoCopy, 64> array;
|
Array<NoCopy, 64> array;
|
||||||
|
|
||||||
|
// Test the dual pivot quick sort.
|
||||||
for (size_t i = 0; i < 64; ++i)
|
for (size_t i = 0; i < 64; ++i)
|
||||||
array[i].value = (64 - i) % 32 + 32;
|
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)
|
for (size_t i = 0; i < 63; ++i)
|
||||||
EXPECT(array[i].value <= array[i + 1].value);
|
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)
|
TEST_MAIN(QuickSort)
|
||||||
|
|
Loading…
Reference in a new issue