AiSD (index)


AiSD (2) - sortowanie

OBOWIĄZKOWE DO PRZESŁANIA: Implementacja dwóch algorytmów sortowania w C++, w tym co najmniej jednego zaawansowanego. Funkcje sortujące muszą być szablonami, muszą mieć podany interfejs tablicowy lub z iteratorami, wystarczy użyć standardowego porównywania (operator<). W programie należy sprawdzić, czy sortowanie się udało, najlepiej wykorzystać funkcję std::is_sorted().

Ogólne zalecenia do przesyłanych programów.
(1) Katalog z programem powinien zawierać Makefile i pliki źródłowe.
(2) Program powinien kompilować się poleceniem make.
(3) Nie używać deklaracji using namespace std;.
(4) Zalecane są testy automatyczne z assert().

WPROWADZENIE

Sortowaniem nazywamy proces ustawiania zbioru obiektów w określonym porządku [Wirth]. Sortowanie stosuje się w celu ułatwienia późniejszego wyszukiwania elementów sortowanego zbioru.


SPECYFIKACJA
Problem sortowania [Cormen].

DANE WEJŚCIOWE
Ciąg n liczb (a_1, a_2, ..., a_n).

DANE WYJŚCIOWE
Permutacja (zmiana kolejności) (a_1', a_2', ..., a_n')
ciągu wejściowego taka, że a_1' ≤ a_2' ≤ ... ≤ a_n'.

Sortowanie wewnętrzne (tablice) i zewnętrzne (pliki).
Sortowanie adaptacyjne i nieadaptacyjne.
Sortowanie stabilne i niestabilne.


Przykład sortowania par liczb całkowitych względem pierwszej wartości.
(4, 1) (3, 7) (3, 1) (5, 6) - oryginalna kolejność
(3, 7) (3, 1) (4, 1) (5, 6) - kolejność zachowana (stabilność)
(3, 1) (3, 7) (4, 1) (5, 6) - kolejność zmieniona (brak stabilności)

Sortowanie bazujące na porównaniach kluczy.
Algorytmy proste z czasem O(n^2).
Sortowanie przez wybór (selectsort).
Sortowanie przez wstawianie (insertsort), pamięć O(1).
Sortowanie przez zamianę (bubblesort), stabilne.
Sortowanie przez wstrząsanie (shakersort).

Algorytmy zaawansowane z czasem O(n*log(n)).
Sortowanie Shella (shellsort), niestabilne.
Sortowanie szybkie (quicksort), pesymistycznie O(n^2), niestabilne.
Sortowanie przez scalanie (mergesort), stabilne.
Sortowanie przez kopcowanie, sortowanie stogowe (heapsort), niestabilne.

Sortowanie bez porównywania kluczy.
Sortowanie przez zliczanie (countsort), czas O(n+k).
Sortowanie bukietowe (bucketsort), czas O(n), pamięć O(k).
Sortowanie pozycyjne (radixsort) - czas O(d*(n+k)), pamięć O(n+k), k liczba różnych cyfr, d liczba cyfr w kluczach.

SORTOWANIE W PYTHONIE


# L to lista elementów, które można porównywać.
# Liczby są sortowane normalnie.
# Napisy są sortowane leksykograficznie.
L.sort()   # sortowanie stabilne algorytmem timsort (Py2.3-Py3.10) lub powersort (Py3.11+)
L.sort(key=len)   # sortowanie napisów pod względem długości
L.sort(cmp=cmpfunc)   # Python 2, przekazanie funkcji porównującej

SORTOWANIE W C/C++

https://en.wikipedia.org/wiki/Introsort
Introsort or introspective sort is a hybrid sorting algorithm that provides both fast average performance and (asymptotically) optimal worst-case performance. Introsort was invented by David Musser in 1997.


#include <cstdlib>   // qsort()

void qsort( void *ptr, size_t count, size_t size,
    int (*comp)(const void *, const void *) );
// Sortowanie tablicy ptr w porządku niemalejącym.
// Funkcja porównująca zwraca -1/0/+1.
// Funkcja porównująca nie może modyfikować porównywanych elementów.
// count - liczba elementów tablicy
// size - rozmiar jednego elementu w bajtach

int cmp_int(const void *a, const void *b) {
    int arg1 = *(const int *)a;
    int arg2 = *(const int *)b;
    if (arg1 < arg2) return -1;
    else if (arg1 > arg2) return 1;
    else return 0;
}

int v[] = { 9,4,7,3,6,8,2,1,5,0 };

qsort(v, sizeof(v) / sizeof(*v), sizeof(*v), cmp_int);

// https://en.cppreference.com/w/cpp/algorithm/sort

#include <algorithm>   // std::sort(), std::stable_sort()
#include <vector>
#include <array>

template< class RandomIt >
void sort( RandomIt first, RandomIt last );
// Sorts the elements in the range [first, last) in non-descending order. 
// The order of equal elements is not guaranteed to be preserved. 
// Elements are compared using operator< (until C++20) or std::less{} (since C++20).

template< class RandomIt, class Compare >
void sort( RandomIt first, RandomIt last, Compare comp );
// Elements are sorted with respect to comp.
// bool cmp(const Type1 & a, const Type2 & b);
// Funkcja zwraca true jeżeli a jest mniejsze od b.

// Complexity
// O(N*log(N)), where N = std::distance(first, last) comparisons on average. (until C++11)
// O(N*log(N)), where N = std::distance(first, last) comparisons. (since C++11)

// Dla std::stable_sort() analogicznie.
// The order of equivalent elements is guaranteed to be preserved.
// Complexity
// O(N*log(N)^2), where N = std::distance(first, last) applications of cmp.
// If additional memory is available, then the complexity is O(N*log(N)). 
// Notes
// This function attempts to allocate a temporary buffer equal in size
// to the sequence to be sorted.
// If the allocation fails, the less efficient algorithm is chosen. 

std::vector<int> v = { 9,4,7,3,6,8,2,1,5,0 };
//std::array<int, 10> v{ { 9,4,7,3,6,8,2,1,5,0 } }; // bez znaku '=' mamy {{}}
std::array<int, 10> v = { 9,4,7,3,6,8,2,1,5,0 };
int v[] = { 9,4,7,3,6,8,2,1,5,0 }; // zwykła tablica

std::sort(v.begin(), v.end()); // vector, array
std::sort(std::begin(v), std::end(v)); // zwykła tablica, vector, array
std::sort(v.data(), v.data() + v.size());   // array
std::sort(v, v + (sizeof(v) / sizeof(*v))); // zwykła tablica

// Szablon przykładowej funkcji sortującej.
// Funkcja sortuje elementy w zakresie od ptr[left] do ptr[right] włącznie.
template<typename T>
void anysort(T *ptr, int left, int right);
// Elements are compared using
bool operator<(const T & lhs, const T & rhs);

// Szablon przykładowej funkcji sortującej.
template<typename Iterator>
void anysort(Iterator first, Iterator last);

template<typename Iterator, typename Compare>
void anysort(Iterator first, Iterator last, Compare comp);

// https://en.cppreference.com/w/cpp/algorithm/is_sorted

// Sprawdzenie poprawności sortowania liczb całkowitych.
#include <cassert>

// Wykorzystanie funkcji z STL.
assert( std::is_sorted(v.begin(), v.end()) );   // vector, array
assert( std::is_sorted(std::begin(v), std::end(v)) );   // zwykła tablica
assert( std::is_sorted(v, v + (sizeof(v) / sizeof(*v))) );   // zwykła tablica
assert( std::is_sorted(v.data(), v.data() + v.size()) );   // array

MAKEFILE

Przykładowy plik Makefile wspomagający kompilację i sposób korzystania z narzędzia make.
UWAGA Wcięcia w pliku Makefile robimy klawiszem tabulacji, to nie jest ciąg spacji!


$ ls                # zawartość katalogu z projektem
atrapa.cpp  atrapa.h  main.cpp  Makefile

# Zależności między plikami:
# atrapa.cpp zawiera #include "atrapa.h"
# main.cpp zawiera #include "atrapa.h"

$ make              # kompilacja
g++ -Wall -std=c++11 -c main.cpp -o main.o
g++ -Wall -std=c++11 -c atrapa.cpp -o atrapa.o
g++ -Wall -std=c++11  main.o atrapa.o -o main.out
$ make clean        # czyszczenie po kompilacji
rm -f main.out *.o core

# Makefile
# Kompilator g++.
CXX = g++
# Możliwe dodatkowe zmienne.
# LIBS = -ll -lm
LIBS =
# Flagi dla kompilatora C++.
# Standardy: -std=c++11 -std=c++14 -std=c++17 -std=c++20 -std=c++23 -std=c++26
CXXFLAGS = -Wall -std=c++11
# Pliki obiektowe - powstają z plików *.cpp.
OBJECTS = main.o atrapa.o
# Pliki nagłówkowe - są włączane do plików *.cpp.
HFILES = atrapa.h
# Końcowy plik wykonywalny.
TARGET = main.out

# Definicja domyślnej reguły wzorcowej.
# $< oznacza nazwę pliku pierwszej zależności reguły.
# $@ oznacza nazwę pliku celu w regule.
# Wszystkie pliki obiektowe zależą od wszystkich plików nagłówkowych.
# Wcięcia robimy przez TAB.
%.o : %.cpp $(HFILES) Makefile
	$(CXX) $(CXXFLAGS) -c $< -o $@

# Na końcu wstawiamy $(LIBS), aby nie było problemów z linkowaniem.
$(TARGET) : $(OBJECTS)
	$(CXX) $(CXXFLAGS) $(OBJECTS) -o $(TARGET) $(LIBS)

# Określenie celów sztucznych.
.PHONY : clean

clean :
	$(RM) $(TARGET) *.o core

ZADANIE 2.1 (selectsort)

Zaimplementować algorytm sortowania przez wybór.

ZADANIE 2.2 (insertsort)

Zaimplementować algorytm sortowania przez wstawianie.

ZADANIE 2.3 (bubblesort)

Zaimplementować algorytm sortowania bąbelkowego.

ZADANIE 2.4 (quicksort)

Zaimplementować algorytm sortowania szybkiego.

ZADANIE 2.5 (mergesort)

Zaimplementować algorytm sortowania przez scalanie. Można wykorzystać funkcję std::inplace_merge().

https://en.cppreference.com/w/cpp/algorithm/inplace_merge

ZADANIE 2.6 (heapsort)

Zaimplementować algorytm sortowania przez kopcowanie.


AiSD (index)