Sortowanie przez porównania: BubbleSort, SelectionSort, InsertionSort
Ten wykład poświęcimy prostym algorytmom sortowania. Zanalizujemy ich wady i zalety, co przygotuje nas do poszukiwania algorytmów lepszych. Zacznijmy od zdefiniowania problemu.
Definicja problemu
Niech \( (U,\le) \) będzie zbiorem liniowo uporządkowanym z relacją porządkującą \( \le \) i niech \( c_1, c_2, \ldots, c_n \) będzie ciągiem \( n \) elementów z \( U \), dla pewnego całkowitego \( n > 0 \). Należy znaleźć permutację \( c_{i_1}, c_{i_2},\ldots,c_{i_n} \) taką, że \( c_{i_1}\le c_{i_2}\le \ldots \le c_{i_n} \).
Uwaga: dla pary elementów \( x, y \in U \) będziemy pisali \( x < y \) (\( x > y \)), gdy \( x \le y \) (\( y \le x \)) oraz \( x \ne y \).
W tym wykładzie przyjmujemy, że elementy ciągu \( c \) znajdują się w tablicy \( a[1..n] \), tzn. \( a[1] = c_1, a[2] = c_2, \ldots, a[n] = c_n \). Będziemy także chcieli, żeby posortowany ciąg \( c \) znajdował się nadal w tablicy \( a \), tzn. wynikiem sortowania ma być \( a[1] = c_{i_1} \le a[2] = c_{i_2} \le \ldots \le a[n] = c_{i_n} \). Dla wygody dalszych rozważań przyjmijmy, że tablica \( a \) jest indeksowana od \( 0 \), i że \( a[0] \) zawiera element, który nie jest większy od żadnego elementu z \( a[1..n] \), tzn. dla każdego \( i = 1,2, \ldots, n \), \( a[0] \le a[i] \).
Zauważmy, że w tak sformułowanym problemie sortowania nic nie wiemy o naturze elementów z \( U \). Na \( U \) mogą składać się zarówno liczby całkowite lub rzeczywiste, jak i \( U \) może być zbiorem rekordów, które należy posortować według ich kluczy. Jedynym sposobem ustalenie porządku w tablicy \( a \) jest porównywanie jej elementów parami. Operacja porównania będzie operacją dominującą w naszych algorytmach. Ponieważ będziemy chcieli ustalić wynik także w tablicy \( a \), potrzebna nam jest jeszcze operacja zamiany dwóch elementów w tablicy. Operacją tą będzie operacja \( Exchange(i,j) \) polegająca na zamianie elementów w tablicy \( a \) z pozycji \( i \) oraz \( j \), \( 1\le i, j \le n \).
Sortowanie bąbelkowe (BubbleSort)
Sortowanie przez wybór (SelectionSort)
Na sortowanie bąbelkowe można spojrzeć jeszcze inaczej. W każdej fazie przepychamy na pierwsze miejsce w nieuporządkowanej części tablicy element najmniejszy. Symetrycznie moglibyśmy sortować poczynając od elementów nawiększych. W sortowaniu przez wybór nie przepychamy elementu największego (najmniejszego) na jego miejsce w ciągu uporządkowanym, tylko znajdujemy jego pozycję, a następnie znaleziony element - największy w części nieuporządkowanej - zamieniamy z elementem, który znajduje się na docelowej pozycji elementu największego. Niech \( Max(i) \) będzie funkcją, której wartością jest pozycja największego elementu w podtablicy \( a[1..i] \). Oto schemat algorytmu sortowania przez wybór:
Algorytm Schemat algorytmu sortowania przez wybór
schemat SelectionSort
for i := n downto2dobegin
j := Max(i);
Exchange(i,j)end;
Dlaczego użyliśmy słowa schemat? Zauważmy, że do pełnego zdefinowania algorytmu, a także dla jego analizy, niezbędne jest zdefiniowanie funkcji \( Max \). W klasycznym sortowaniu przez wybór pozycję elementu największego w podtablicy \( a[1..i] \) wyznaczamy przeszukując podtablicę od lewej do prawej, pamiętając w zmiennej pomocniczej pozycję dotychczas największego elementu i modyfikując wartość tej zmiennej każdorazowo po napotkaniu elementu większego od dotychczas największego. Oto pełny zapis algorytmu sortowania przez wybór - algorytmu SelectionSort.
Algorytm Sortowanie przez wybór
SelectionSort
for i := n downto2dobegin
j := 1;
for k := 2to i doif a[k] > a[j]then j := k;
Exchange(i,j)end;
Nietrudno zauważyć, że algorytm SelectionSort wykonuje taką samą liczbę porównań, co algorytm bąbelkowy. Jednak w tym przypadku maksymalna liczba zamian wynosi tylko \( n-1 \).
Należy podkreślić, że w implementacji schematu sortowania przez wybór mamy swobodę w realizacji funkcji \( Max \). Wykorzystamy to w algorytmie sortowania z użyciem struktury danych zwanej kopcem.