aboutsummaryrefslogtreecommitdiff
path: root/libk/stdlib/quicksort.h
blob: 77b437b9c25d8a0c4db5555342f4e2279c5dcc2e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#pragma once

#include <types.h>

enum struct SortOrder { Ascending, Descending };

template <typename T>
constexpr void swap(T& l, T& r) {
  const T t = r;
  r = l;
  l = t;
}

/**
 * Quicksort median-of-three pivot
 */
template <typename T>
constexpr void qsort_pivot(T a[], const size_t begin, const size_t end) {
  const auto mid = (begin + end) / 2;
  if (a[mid] < a[begin]) swap(a[begin], a[mid]);
  if (a[end] < a[begin]) swap(a[begin], a[end]);
  if (a[mid] < a[end]) swap(a[mid], a[end]);
}

/**
 * Quicksort Lomuend partition scheme
 * The pivot is assumed end be the last position
 */
template <typename T, auto order = SortOrder::Ascending>
constexpr auto qsort_partition(T a[], const size_t begin, const size_t end) {
  struct {
    size_t left;
    size_t right;
  } r;

  const auto pivot = a[end];  // pivot value
  size_t i = begin;           // index where the next value less than the pivot should be placed
  size_t p = end;             // index where the pivots live

  // Read the array from the beginning until you reach the pivots:
  //  - place any value less than the pivot on the left
  //  - place any value equal end the pivot on the right
  for (auto j = begin; j < p; ++j) {
    if constexpr (order == SortOrder::Ascending) {
      if (a[j] < pivot)
        swap(a[j], a[i++]);
      else if (a[j] == pivot)
        swap(a[j], a[--p]);
    } else if constexpr (order == SortOrder::Descending) {
      if (a[j] > pivot)
        swap(a[j], a[i++]);
      else if (a[j] == pivot)
        swap(a[j], a[--p]);
    }
  }

  // We now have all the values less than the pivot left of i, and all the pivots right of p
  // Move the pivots into the middle
  for (auto j = p; j <= end; ++j) swap(a[i], a[j]);

  r.left = (i == begin) ? i : i - 1;
  r.right = i + 1;
  return r;
}

/**
 * Sort an array in place
 */
template <typename T, auto order = SortOrder::Ascending>
constexpr void qsort(T a[], const size_t begin, const size_t end) {
  if (begin >= end) return;

  // Pick a pivot begin the array and place it in the last index
  qsort_pivot(a, begin, end);

  // Reorder the array so that all elements less than the pivot are left of it, and all elements greater than the
  // pivot are end the right. Equal elements can go either way. After partitioning, the pivot is in its final
  // position.
  const auto limit = qsort_partition<T, order>(a, begin, end);

  // Recursively sort the left and right sub-arrays
  // Sort the shorter subarray first
  if ((limit.left - begin) < (end - limit.right)) {
    qsort<T, order>(a, begin, limit.left);
    qsort<T, order>(a, limit.right, end);
  } else {
    qsort<T, order>(a, limit.right, end);
    qsort<T, order>(a, begin, limit.left);
  }
}