Przeczytaj
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.
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.
Krok 1
Porównujemy ze sobą dwie pierwsze liczby – czyli 6 i 7.
Liczba 6 jest mniejsza od 7, więc nie następuje zamiana.
Krok 2
Teraz porównujemy liczby 7 i 3.
Liczba 7 jest większa od 3, więc następuje zamiana.
Krok 3
Następnie porównujemy liczby 7 i 1.
Liczba 7 jest większa od 1, więc następuje zamiana.
Krok 4
Dalej porównujemy liczby 7 i 4.
Liczba 7 jest większa od 4, więc następuje zamiana.
Krok 5
Teraz porównujemy liczby 7 i 2.
Liczba 7 jest większa od 2, więc następuje zamiana.
Krok 6
Następnie porównujemy liczby 7 i 8.
Liczba 7 jest mniejsza od 8, więc nie następuje zamiana.
Krok 7
Na koniec porównujemy dwie ostatnie liczby: 8 i 5.
Liczba 8 jest większa od 5, więc następuje zamiana.
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 porównań, gdzie:
– rozmiar tablicy, która ma zostać posortowana,
– 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:
Pętla zewnętrzna odpowiada za wykonanie kolejnych cykli sortowania. Pętla ta zostanie wykonana razy, gdzie 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 (pierwszego elementu sortowanej tablicy) i kończy na indeksie (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 , żeby pętla wewnętrzna mogła się wykonać we wszystkich iteracjach pętli zewnętrznej (dla zawartość również się wykona, w wyniku czego otrzymamy 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.
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.
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.
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.
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 cykli po porównań, gdzie to numer cyklu.
Przybliżona liczba operacji porównania (operacji dominujących) wykonanych przez algorytm będzie wynosić: . Zatem złożoność czasowazłożoność czasowa wyniesie: .
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 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ć: , czyli jego złożoność czasowa to: .
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 . Wynika to z faktu, że algorytm, mimo że nie dokonuje zamian i tak zrealizuje wszystkie cykle. Wykona się więc cykli po 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ęciowa tego algorytmu jest stała i zależna od liczby sortowanych elementów. Taka złożoność pamięciowa oznaczana jest symbolem . Jest to tzw. algorytm sortowania w miejscu.
Słownik
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
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