1. Zweck

  • Gegenüberstellung verschiedener Sortieralgorithmen in C++

    • Konkrete und generische Realisierungen vergleichen in

      1. Laufzeit

      2. Maschinencodegröße

2. Algorithmen

2.1. QuickSort

Beschreibung von Quicksort

Für die Auswahl des konkreten Algorithmus stehen die folgenden Tags zur Verfügung:

namespace Algorithm {
struct QuickNaiv {};
struct QuickBentleyMcIlroy {};
struct QuickSedgeWick {};
}

sowie ein Wrapper:

template <typename Algo = Algorithm::QuickBentleyMcIlroy, Util::Array A>
void quickSort(A& v) {
    if constexpr(v.size > 1) {
        if constexpr(std::is_same<Algo, Algorithm::QuickBentleyMcIlroy>::value) {
            detail::qsort::bentley::qsort(&v[0], v.size);
        } else if constexpr(std::is_same<Algo, Algorithm::QuickSedgeWick>::value) {
            detail::qsort::sedgewick::quickSort(v, 0, v.size - 1);
        } else if constexpr(std::is_same<Algo, Algorithm::QuickNaiv>::value) {
            detail::qsort::naiv::quickSort(v, 0, v.size - 1);
        } else {
            static_assert(Algo::isValid);
        }
    }
}

2.1.1. Naive Implementierung

Die naive Implementierung folgt direkt (s.a. Wikipedia):

template<Util::Array C>
auto pivot(C& v, typename C::size_type start,  typename C::size_type stop, typename C::size_type position) -> typename C::size_type {
    using std::swap;
    swap(v[start], v[position]);

    typename C::size_type low = start + 1;
    typename C::size_type high = stop;
    while (low < high)
        if (v[low] < v[start])
            ++low;
        else if (v[--high] < v[start])
            swap (v[low], v[high]);

    swap (v[start], v[--low]);
    return low;
}
template<Util::Array C>
void quickSort(C& v, typename C::size_type low, typename C::size_type high) {
    if (low >= high)
        return;

    typename C::size_type pivotIndex = (low + high) / 2;

    pivotIndex = pivot (v, low, high, pivotIndex);

    if (low < pivotIndex)
        quickSort(v, low, pivotIndex);
    if (pivotIndex < high)
        quickSort(v, pivotIndex + 1, high);
}

2.1.2. Nach Sedgewick

Aus dem Lehrbuch "Algorithmen in …​":

template<Util::Array C>
void quickSort(C& a, typename C::signed_size_type l, typename C::signed_size_type r) {
    using std::swap;
    typename C::signed_size_type i = l-1, j = r, p = l-1, q = r;
    if (r <= l) return;
    auto v = a[r];
    while(true) {
        while (a[++i] < v) ;
        while (v < a[--j]) if (j == l) break;
        if (i >= j) break;
        swap(a[i], a[j]);
        if (a[i] == v) {
            ++p;
            swap(a[p], a[i]);
        }
        if (v == a[j]) {
            --q;
            swap(a[j], a[q]);
        }
    }
    swap(a[i], a[r]);
    j = i-1;
    i = i+1;
    for (auto k = l; k < p; k++, j--) swap(a[k], a[j]);
    for (auto k = r-1; k > q; k--, i++) swap(a[i], a[k]);
    quickSort(a, l, j);
    quickSort(a, i, r);
}

2.1.3. Nach Bentley-McIlroy

Nach dem Paper von Bentley (wie auch die Version in der AVR-Libc):

// ...
template<typename T>
T* med3(T* a, T* b, T* c) {
    return (*a < *b) ?
           ((*b < *c) ? b : ((*a < *c) ? c : a ))
           :((*b > *c) ? b : ((*a < *c) ? a : c ));
}
template<typename T, typename SizeType>
void qsort(T* a, SizeType n) {
    using std::swap;

    T *pa, *pb, *pc, *pd, *pl, *pm, *pn;
    SizeType d, r, swap_cnt;
loop:
    swap_cnt = 0;
    if (n < 7) {
        for (pm = a + 1; pm < (a + n); ++pm)
            for (pl = pm; pl > a && (*(pl - 1) > *pl);
                    --pl)
                swap(*pl, *(pl - 1));
        return;
    }
    pm = a + (n / 2);
    if (n > 7) {
        pl = a;
        pn = a + (n - 1);
        if (n > 40) {
            d = (n / 8);
            pl = med3(pl, pl + d, pl + 2 * d);
            pm = med3(pm - d, pm, pm + d);
            pn = med3(pn - 2 * d, pn - d, pn);
        }
        pm = med3(pl, pm, pn);
    }
    swap(*a, *pm);
    pa = pb = a + 1;

    pc = pd = a + (n - 1);
    for (;;) {
        while ((pb <= pc) && (*pb <= *a)) {
            if (*pb == *a) {
                swap_cnt = 1;
                swap(*pa, *pb);
                pa += 1;
            }
            pb += 1;
        }
        while (pb <= pc && (*pc >= *a)) {
            if (*pc == *a) {
                swap_cnt = 1;
                swap(*pc, *pd);
                pd -= 1;
            }
            pc -= 1;
        }
        if (pb > pc)
            break;
        swap(*pb, *pc);
        swap_cnt = 1;
        pb += 1;
        pc -= 1;
    }
    if (swap_cnt == 0) {  /* Switch to insertion sort */
        for (pm = a + 1; pm < a + n; ++pm)
            for (pl = pm; pl > a && (*(pl - 1) > *pl);
                    --pl)
                swap(*pl, *(pl - 1));
        return;
    }

    pn = a + n;
    r = std::min(pa - a, pb - pa);
    vecswap(a, pb - r, r);
    r = std::min(pd - pc, pn - pd - 1);
    vecswap(pb, pn - r, r);
    if ((r = pb - pa) > 1)
        qsort(a, r);
    if ((r = pd - pc) > 1) {
        a = pn - r;
        n = r;
        goto loop;
    }
}

2.2. BubbleSort

template<Util::Array C>
void bubbleSort(C& a) {
    typedef typename C::size_type size_type;
    using std::swap;
    for(size_type i = 1; i < C::size;) {
        if ((a[i - 1] > a[i])) {
            swap(a[i - 1], a[i]);
            i = 0;
        } else {
            ++i;
        }
    }
}

3. Ergebnisse

Ergebnisse wurden ermittelt für:

  1. AVR Mega @ 20MHz mit simavr

  2. …​

Deswegen sind nur die relativen Zeiten miteinander zu vergleichen.

3.1. Laufzeiten

Arraygröße: 191 Iterationen: 100

Tabelle 1. Laufzeiten mit -Os in ms und Set1

Algorithmus

Elementtyp uint8_t

Elementyp uint16_t

Elementtyp uint32_t

BubbleSort

12561

AVR-Libc

503

580

673

Quicksort Naiv

206

328

501

Quicksort Sedgewick

217

375

591

Quicksort Bentley

166

240

407

Tabelle 2. Laufzeiten mit -Os in ms und Set2

Algorithmus

Elementtyp uint8_t

Elementyp uint16_t

Elementtyp uint32_t

BubbleSort

18067

AVR-Libc

322

381

464

Quicksort Naiv

225

369

561

Quicksort Sedgewick

179

325

512

Quicksort Bentley

115

169

287

Tabelle 3. Laufzeiten mit -O3 in ms und Set1

Algorithmus

Elementtyp uint8_t

Elementyp uint16_t

Elementtyp uint32_t

BubbleSort

12158

AVR-Libc

503

580

673

Quicksort Naiv

stack overfl

Quicksort Sedgewick

258

372

599

Quicksort Bentley

165

256

311

Tabelle 4. Laufzeiten mit -O3 in ms und Set2

Algorithmus

Elementtyp uint8_t

Elementyp uint16_t

Elementtyp uint32_t

BubbleSort

17629

AVR-Libc

322

537

621

Quicksort Naiv

193

465

659

Quicksort Sedgewick

213

458

577

Quicksort Bentley

118

338

443

3.2. Codegrößen

Tabelle 5. Size mit -Os in Bytes

Algorithmus

Elementtyp uint8_t

Elementyp uint16_t

Elementtyp uint32_t

BubbleSort

44 (aber inlined)

AVR-Libc

930

934

950

Quicksort Naiv

170

234

338

Quicksort Sedgewick

316

550

732

Quicksort Bentley

600

788

1158

Tabelle 6. Size mit -O3 in Bytes

Algorithmus

Elementtyp uint8_t

Elementyp uint16_t

Elementtyp uint32_t

BubbleSort

80 (aber inlined)

AVR-Libc

930

934

950

Quicksort Naiv

502

662

1040

Quicksort Sedgewick

440

594

794

Quicksort Bentley

830

1206

2378