Sortowanie bąbelkowe

Sortowanie bąbelkowe to jeden ze sposobów sortowania serii danych, które można zaimplementować w dowolnym języku programowania. W kolejnych cyklach działania algorytmu największe wartości zostają przesunięte w prawo, na koniec serii danych. Operacja ta powtarza się, aż wszystkie wartości zostaną ułożone w zadanej kolejności, tzn. nierosnąco lub niemalejąco. Po przeanalizowaniu złożoności tego algorytmu dowiesz się, że prostota jego zapisu nie idzie jednak w parze z szybkością działania, dlatego najlepiej używać go do sortowania niewielkich zestawów danych.

Przykład 1

Przedstaw działanie algorytmu sortowania bąbelkowego na nieuporządkowanym ciągu liczb. Dane powinny zostać posortowane niemalejąco. Ciąg do posortowania zapisany został w tablicy w następującej postaci: [6, 7, 3, 1, 4, 2, 8, 5].

Specyfikacja problemu:

Dane:

  • n – liczba elementów ciągu do posortowania (rozmiar tablicy)

  • tablica[] – tablica z nieposortowanym ciągiem wybranych liczb naturalnych

Wynik:

  • tablica[] – ciąg liczb przechowywany w tablicy, posortowany niemalejąco

Przykład realizacji algorytmu

Nauczyciel informatyki poprosił chętnych o zgłoszenie się do udziału w konkursie programowania. Uczniowie wpisywali się na listę, podając swój numer z dziennika szkolnego. Lista zawierała następujące liczby: 6, 7, 3, 1, 4, 2, 8, 5.

Naszym zadaniem jest posortowanie listy chętnych rosnąco. Użyjemy do tego celu sortowania bąbelkowego.

Zaczynamy od zapisania numerów w odpowiedniej strukturze danych, np. w tablicy lub liście.

R11uwPrczUYZ7
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Krok 1

Porównujemy ze sobą dwie pierwsze liczby – czyli 6 i 7.

RJ2hduvkBzFSp
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Liczba 6 jest mniejsza od 7, więc nie następuje zamiana.

Krok 2

Teraz porównujemy liczby 7 i 3.

RLvki8yhexyQs
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Liczba 7 jest większa od 3, więc następuje zamiana.

R1WgzuwNSrKdt
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Krok 3

Następnie porównujemy liczby 7 i 1.

RoT8kF2TwuLaO
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Liczba 7 jest większa od 1, więc następuje zamiana.

RPUWVMyAiqbCp
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Krok 4

Dalej porównujemy liczby 7 i 4.

R16KGV4Wqph5o
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Liczba 7 jest większa od 4, więc następuje zamiana.

RsGl2hU9zVN4d
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Krok 5

Teraz porównujemy liczby 7 i 2.

R15MYawMqgXZI
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Liczba 7 jest większa od 2, więc następuje zamiana.

R9ES2VN6rqhtX
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Krok 6

Następnie porównujemy liczby 7 i 8.

RjaMaX0QTbSob
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Liczba 7 jest mniejsza od 8, więc nie następuje zamiana.

Krok 7

Na koniec porównujemy dwie ostatnie liczby: 8 i 5.

RedSDr6ytNZ8e
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Liczba 8 jest większa od 5, więc następuje zamiana.

R8zSLndhA4L6a
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Po zakończeniu ostatniego kroku liczba 8, czyli największa liczba w rozważanym ciągu, znajdzie się na ostatnim miejscu w tablicy, a więc na swoim miejscu docelowym. Ponieważ pozostałe elementy tablicy (poza liczbą 8) wciąż nie są posortowane, należy wrócić do początku tablicy i ponownie wykonywać po kolei wszystkie porównania.

W każdym kolejnym cyklu przeglądany ciąg będzie krótszy, dlatego w poszczególnych cyklach zostanie wykonane n - i porównań, gdzie:

  • n – rozmiar tablicy, która ma zostać posortowana,

  • i – kolejny numer wykonywanego cyklu.

Po każdym cyklu algorytmu największy element obecnie analizowanego ciągu zostanie umieszczony na końcu, co oznacza, że w każdym przebiegu kolejna liczba znajdzie się na prawidłowej pozycji (elementy ciągu nierozważane w trakcie obiegu są już posortowane). Algorytm powinien wykonywać się do momentu, gdy wszystkie wartości zostaną posortowane.

Poniżej zostały przedstawione dwie wersje algorytmu sortowania bąbelkowego zapisane przy użyciu pseudokodu: podstawowa (nieuwzględniająca wcześniejszego zakończenia wykonywania algorytmu) i usprawniona (mogąca zakończyć się wcześniej w przypadku posortowania ciągu).

Pseudokod algorytmu sortowania bąbelkowego

Proces sortowania metodą bąbelkową można zapisać za pomocą następującego pseudokodu:

Linia 1. Sortowanie podkreślnik babelkowe otwórz nawias okrągły tablica przecinek n zamknij nawias okrągły. Linia 2. Dla i znak równości 0 przecinek 1 przecinek 2 przecinek kropka kropka kropka n minus 2 przecinek wykonuj. Linia 3. Dla j znak równości 0 przecinek 1 przecinek 2 przecinek kropka kropka kropka n minus i minus 2 przecinek wykonuj. Linia 4. Jeżeli tablica otwórz nawias kwadratowy j zamknij nawias kwadratowy zamknij nawias ostrokątny tablica otwórz nawias kwadratowy j plus 1 zamknij nawias kwadratowy przecinek to. Linia 5. pomocnicza ← tablica otwórz nawias kwadratowy j plus 1 zamknij nawias kwadratowy. Linia 6. tablica otwórz nawias kwadratowy j plus 1 zamknij nawias kwadratowy ← tablica otwórz nawias kwadratowy j zamknij nawias kwadratowy. Linia 7. tablica otwórz nawias kwadratowy j zamknij nawias kwadratowy ← pomocnicza.
  • Pętla zewnętrzna odpowiada za wykonanie kolejnych cykli sortowania. Pętla ta zostanie wykonana n - 1 razy, gdzie n to liczba elementów sortowanego ciągu. W ostatnim cyklu sortowania elementy ciągu będą już posortowane.

  • W pętli wewnętrznej porównywane są kolejne wartości, począwszy od lewej strony (mniejszych indeksów tablicy). Pętla ta zaczyna się od indeksu 0 (pierwszego elementu sortowanej tablicy) i kończy na indeksie n i 2 (przedostatnim elemencie nieposortowanej jeszcze części tablicy). Jest to spowodowane tym, że porównujemy obecny i następny element tablicy. W ograniczeniu pętli odejmujemy 2 od wartości n  i, żeby pętla wewnętrzna mogła się wykonać we wszystkich iteracjach pętli zewnętrznej (dla j = n i 2 zawartość również się wykona, w wyniku czego otrzymamy n i 1 wykonań kodu zawartego w pętli wewnętrznej, na obieg pętli zewnętrznej). Następnie w pętli wewnętrznej porównywane są kolejne pary elementów. Jeśli poprzedni element jest większy od następującego po nim, zostaje wykonana zamiana.

Ważne!

Algorytm można usprawnić, dodając do niego flagę, która będzie informowała, czy w danym cyklu dokonała się choć jedna zamiana. Jeżeli flaga poinformuje nas, że nie została dokonana żadna zmiana, będzie to sygnał, że ciąg jest już posortowany i możemy przerwać dalsze wykonywanie algorytmu. Przekłada się to na skrócenie jego czasu działania.

Linia 1. Sortowanie podkreślnik babelkowe otwórz nawias okrągły tablica przecinek n zamknij nawias okrągły. Linia 2. Dla i znak równości 0 przecinek 1 przecinek 2 przecinek kropka kropka kropka n minus 2 przecinek wykonuj. Linia 3. flaga ← fałsz. Linia 4. Dla j znak równości 0 przecinek 1 przecinek 2 przecinek kropka kropka kropka n minus i minus 2 przecinek wykonuj. Linia 5. Jeżeli tablica otwórz nawias kwadratowy j zamknij nawias kwadratowy zamknij nawias ostrokątny tablica otwórz nawias kwadratowy j plus 1 zamknij nawias kwadratowy przecinek to. Linia 6. pomocnicza ← tablica otwórz nawias kwadratowy j plus 1 zamknij nawias kwadratowy. Linia 7. tablica otwórz nawias kwadratowy j plus 1 zamknij nawias kwadratowy ← tablica otwórz nawias kwadratowy j zamknij nawias kwadratowy. Linia 8. tablica otwórz nawias kwadratowy j zamknij nawias kwadratowy ← pomocnicza. Linia 9. flaga ← prawda. Linia 10. Jeżeli flaga znak równości fałsz. Linia 11. zakończ wykonywanie pętli.

Do przedstawionego wcześniej algorytmu dodaliśmy flagę, która na początku każdego obiegu pętli zewnętrznej przyjmuje wartość fałsz. Ustawiając taką wartość flagi, zakładamy, że ciąg jest już posortowany, a następnie sprawdzamy prawdziwość tego stwierdzenia. Jeżeli okaże się, że elementy ciągu nie są posortowane i istnieje potrzeba ich zamiany miejscami, zmieniamy wartość flagi na prawda. Sygnalizujemy w ten sposób, że w tym obiegu pętli zewnętrznej została zmieniona kolejność elementów ciągu. Jeżeli po przejściu całej pętli wewnętrznej domyślna wartość flagi nie zmieniła się, oznacza to, że elementy ciągu są posortowane i możemy zakończyć wykonywanie algorytmu.

Dla zainteresowanych

Omówione zastosowanie sortowania bąbelkowego bazujące na porównywaniu wybranego elementu z elementem występującym za nim nie jest jedyną możliwą implementacją tego algorytmu.

Innym rozwiązaniem jest porównanie w obiegu pętli wewnętrznej wybranego elementu z elementem występującym przed nim. Wymaga to jednak wprowadzenia pewnych zmian w stworzonym wcześniej kodzie.

Jedną z wymaganych modyfikacji jest zmiana indeksów, na których operuje pętla wewnętrzna.  Ponieważ porównujemy element pod indeksem wyznaczonym przez iterator pętli, to powinna się ona zaczynać od wartości 1, by wskazywać na drugi z sortowanych elementów (żeby możliwe było porównanie go z pierwszym).

Kolejną modyfikacją jest zmiana indeksów elementów we fragmencie kodu odpowiedzialnym za porównanie i zamianę elementów miejscami. W tej implementacji algorytmu sortowania bąbelkowego porównujemy element wyznaczony przez iterator pętli z elementem go poprzedzającym. Jeśli warunek zostanie spełniony, zamieniamy ze sobą porównywane elementy, używając elementu pomocniczego w sposób analogiczny do zastosowanego we wcześniejszej implementacji. Reszta mechanizmów algorytmu pozostaje bez zmian.

Linia 1. Sortowanie podkreślnik babelkowe otwórz nawias okrągły tablica przecinek n zamknij nawias okrągły. Linia 2. Dla i znak równości 0 przecinek 1 przecinek 2 przecinek kropka kropka kropka n minus 2 przecinek wykonuj. Linia 3. flaga ← fałsz. Linia 4. Dla j znak równości 1 przecinek 2 przecinek 3 kropka kropka kropka n minus i minus 1 przecinek wykonuj. Linia 5. Jeżeli tablica otwórz nawias kwadratowy j minus 1 zamknij nawias kwadratowy zamknij nawias ostrokątny tablica otwórz nawias kwadratowy j zamknij nawias kwadratowy przecinek to. Linia 6. pomocnicza ← tablica otwórz nawias kwadratowy j minus 1 zamknij nawias kwadratowy. Linia 7. tablica otwórz nawias kwadratowy j minus 1 zamknij nawias kwadratowy ← tablica otwórz nawias kwadratowy j zamknij nawias kwadratowy. Linia 8. tablica otwórz nawias kwadratowy j zamknij nawias kwadratowy ← pomocnicza. Linia 9. flaga ← prawda. Linia 10. Jeżeli flaga znak równości fałsz. Linia 11. zakończ wykonywanie pętli.

Złożoność czasowa i pamięciowa algorytmu

W rozważanym algorytmie operacją dominującą (elementarną) jest porównanie wartości sąsiednich elementów.

Złożoność czasowa przypadku pesymistycznego

Przypadek pesymistyczny to najgorszy z punktu widzenia programu zestaw danych, czyli taki, dla którego program wykona najwięcej porównań.

W przypadku pesymistycznym sortować będziemy tablicę uporządkowaną odwrotnie, czyli np. posortujemy niemalejąco tablicę zawierająca liczby uporządkowane nierosnąco. W takiej sytuacji wykonamy najwięcej zamian i maksymalną liczbę porównań.

Wykona się więc n - 1 cykli po n - i porównań, gdzie i to numer cyklu.

Przybliżona liczba operacji porównania (operacji dominujących) wykonanych przez algorytm będzie wynosić: n 2 . Zatem złożoność czasowazłożoność czasowazłożoność czasowa wyniesie: O ( n 2 ) .

Złożoność czasowa przypadku optymistycznego

Przypadek optymistyczny to najlepszy z punktu widzenia programu zestaw danych, czyli taki, dla którego program wykona najmniej porównań.

Jeżeli poprawimy algorytm tak, żeby kończył działanie, gdy w ostatnim przebiegu (iteracji pętli zewnętrznej) nie została wykonana żadna zamiana, to będziemy w stanie ograniczyć liczbę wykonywanych operacji elementarnych. Rozwiązanie to pozwoli usprawnić działanie algorytmu, a tym samym zaoszczędzić czas poprzez ograniczenie liczby iteracji. Przykładowo, przy rozważaniu przypadku optymistycznego (czyli od razu posortowanej tablicy) będziemy mogli zakończyć działanie algorytmu po pierwszym obiegu zewnętrznej pętli. Najczęściej rozwiązanie to implementowane jest w postaci zmiennej pełniącej rolę flagi, która jest podnoszona przy dokonywaniu zamiany. Jeżeli pod koniec obiegu pętli zewnętrznej flaga nie jest podniesiona, oznacza to, że nie została dokonana żadna zamiana i można zakończyć działanie algorytmu.

Wykona się więc 1 cykl wykonujący n - 1 porównań (liczba elementów sortowanych zmniejszona o 1, czyli liczba kolejnych par elementów sortowanych). Jeśli przy sortowaniu tablicy, na której przykładzie omówiliśmy algorytm, zaistniałby przypadek optymistyczny, liczba porównań wynosiłaby 7.

Zatem przybliżona liczba operacji porównania (operacji dominujących) wykonana przez algorytm będzie wynosić: n , czyli jego złożoność czasowa to: O ( n ) .

Można zauważyć, że w tej sytuacji algorytm pesymistyczny wykona się n razy wolniej niż optymistyczny.

Bez flagi zaprezentowany algorytm będzie miał złożoność czasową kwadratową, czyli O ( n 2 ) . Wynika to z faktu, że algorytm, mimo że nie dokonuje zamian i tak zrealizuje wszystkie cykle. Wykona się więc n - 1 cykli po n - i porównań.

Złożoność pamięciowa algorytmu

Sortowanie bąbelkowe wykonywane jest w jednej tablicy poprzez przestawianie jej elementów, więc potrzebuje tylko tyle miejsca w pamięci, ile zajmą elementy tablicy. Algorytm ten nie zajmuje dodatkowej pamięci, zatem złożoność pamięciowazłożoność pamięciowazłożoność pamięciowa tego algorytmu jest stała i zależna od liczby sortowanych elementów. Taka złożoność pamięciowa oznaczana jest symbolem O ( 1) . Jest to tzw. algorytm sortowania w miejscu.

Słownik

złożoność czasowa
złożoność czasowa

czas, jaki zajmuje rozwiązanie danego problemu; zazwyczaj dla jego określenia podaje się liczbę operacji dominujących (elementarnych) potrzebnych do wykonania algorytmu; zależy od ilości wprowadzonych danych wejściowych

złożoność pamięciowa
złożoność pamięciowa

ilość pamięci potrzebnej do wykonania danego algorytmu; zwyczajowo określona w zależności od rozmiaru danych wejściowych lub innych parametrów mogących mieć wpływ na działanie algorytmu; mierzona jest w liczbie komórek pamięci komputera zajmowanej przez program; jest to jedna z miar wykorzystywanych do określania użyteczności i funkcjonalności algorytmu