diff options
author | Marc Luqué <marc.luque@outlook.com> | 2022-03-25 17:06:11 +0100 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2022-12-12 15:03:57 +0000 |
commit | 22f472249d25a7d3885f281932dfb89619089577 (patch) | |
tree | 4da19f66951722806a100be5710703ba49e4d06a | |
parent | bbb256e8b50e38dd8b68f39827de5bad8c1d4a6e (diff) | |
download | serenity-22f472249d25a7d3885f281932dfb89619089577.zip |
AK: Introduce cutoff to insertion sort for Quicksort
Implement insertion sort in AK. The cutoff value 7 is a magic number
here, values [5, 15] should work well. Main idea of the cutoff is to
reduce recursion performed by quicksort to speed up sorting
of small partitions.
-rw-r--r-- | AK/InsertionSort.h | 45 | ||||
-rw-r--r-- | AK/QuickSort.h | 16 | ||||
-rw-r--r-- | Tests/AK/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Tests/AK/TestInsertionSort.cpp | 57 |
4 files changed, 119 insertions, 0 deletions
diff --git a/AK/InsertionSort.h b/AK/InsertionSort.h new file mode 100644 index 0000000000..2ed2e7cab4 --- /dev/null +++ b/AK/InsertionSort.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022, Marc Luqué <marc.luque@outlook.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <AK/Concepts.h> +#include <AK/StdLibExtras.h> + +namespace AK { + +// Standard Insertion Sort, with `end` inclusive! +template<typename Collection, typename Comparator, typename T = decltype(declval<Collection>()[declval<int>()])> +void insertion_sort(Collection& col, ssize_t start, ssize_t end, Comparator comparator) +requires(Indexable<Collection, T>) +{ + for (ssize_t i = start + 1; i <= end; ++i) { + for (ssize_t j = i; j > 0 && comparator(col[j], col[j - 1]); --j) + swap(col[j], col[j - 1]); + } +} + +template<typename Collection, typename Comparator, typename T = decltype(declval<Collection>()[declval<int>()])> +void insertion_sort(Collection& collection, Comparator comparator) +requires(Indexable<Collection, T>) +{ + if (collection.size() == 0) + return; + insertion_sort(collection, 0, collection.size() - 1, move(comparator)); +} + +template<typename Collection, typename T = decltype(declval<Collection>()[declval<int>()])> +void insertion_sort(Collection& collection) +requires(Indexable<Collection, T>) +{ + if (collection.size() == 0) + return; + insertion_sort(collection, 0, collection.size() - 1, [](auto& a, auto& b) { return a < b; }); +} + +} + +using AK::insertion_sort; diff --git a/AK/QuickSort.h b/AK/QuickSort.h index f5f3af060d..5824cd0d35 100644 --- a/AK/QuickSort.h +++ b/AK/QuickSort.h @@ -1,11 +1,13 @@ /* * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> + * Copyright (c) 2022, Marc Luqué <marc.luque@outlook.com> * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once +#include <AK/InsertionSort.h> #include <AK/StdLibExtras.h> namespace AK { @@ -14,10 +16,24 @@ namespace AK { * pivot quick_sort below. The other quick_sort below should only be used when * you are stuck with simple iterators to a container and you don't have access * to the container itself. + * + * We use a cutoff to insertion sort for partitions of size 7 or smaller. + * The idea is to avoid recursion for small partitions. + * The value 7 here is a magic number. According to princeton's CS algorithm class + * a value between 5 and 15 should work well in most situations: + * https://algs4.cs.princeton.edu/23quicksort/ */ + +static constexpr int INSERTION_SORT_CUTOFF = 7; + template<typename Collection, typename LessThan> void dual_pivot_quick_sort(Collection& col, int start, int end, LessThan less_than) { + if ((end + 1) - start <= INSERTION_SORT_CUTOFF) { + AK::insertion_sort(col, start, end, less_than); + return; + } + while (start < end) { int size = end - start + 1; if (size > 3) { diff --git a/Tests/AK/CMakeLists.txt b/Tests/AK/CMakeLists.txt index c0d5d18763..d09532f91a 100644 --- a/Tests/AK/CMakeLists.txt +++ b/Tests/AK/CMakeLists.txt @@ -38,6 +38,7 @@ set(AK_TEST_SOURCES TestIPv4Address.cpp TestIPv6Address.cpp TestIndexSequence.cpp + TestInsertionSort.cpp TestIntegerMath.cpp TestIntrusiveList.cpp TestIntrusiveRedBlackTree.cpp diff --git a/Tests/AK/TestInsertionSort.cpp b/Tests/AK/TestInsertionSort.cpp new file mode 100644 index 0000000000..6939859e90 --- /dev/null +++ b/Tests/AK/TestInsertionSort.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022, Marc Luqué <marc.luque@outlook.com> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <LibTest/TestCase.h> + +#include <AK/InsertionSort.h> +#include <AK/Random.h> +#include <AK/Vector.h> + +static constexpr int const n = 10; +static constexpr int const iterations = 10; +static constexpr int const seed = 1337; + +TEST_CASE(sorts_ascending) +{ + srand(seed); + + for (int i = 0; i < iterations; ++i) { + Vector<int, n> ints; + for (int j = 0; j < n; ++j) + ints.append(get_random<int>()); + Vector<int, n> ints_copy = ints; + + insertion_sort(ints); + insertion_sort(ints_copy); + + for (int j = 0; j < n - 1; ++j) { + EXPECT(ints[j] <= ints[j + 1]); + EXPECT_EQ(ints[j], ints_copy[j]); + EXPECT_EQ(ints[j + 1], ints_copy[j + 1]); + } + } +} + +TEST_CASE(sorts_decending) +{ + srand(seed); + + for (int i = 0; i < iterations; ++i) { + Vector<int, n> ints; + for (int j = 0; j < n; ++j) + ints.append(get_random<int>()); + Vector<int, n> ints_copy = ints; + + insertion_sort(ints, [](auto& a, auto& b) { return a > b; }); + insertion_sort(ints_copy, [](auto& a, auto& b) { return a > b; }); + + for (int j = 0; j < n - 1; ++j) { + EXPECT(ints[j] >= ints[j + 1]); + EXPECT_EQ(ints[j], ints_copy[j]); + EXPECT_EQ(ints[j + 1], ints_copy[j + 1]); + } + } +} |