Sortowanie binarne – algorytm i implementacje w C++, Java i Python
Zaczyna się ulubiony dzień każdego z nas, a wraz z nim pojawia się kolejna część „Piątku z sortowaniem”. Tym razem weźmiemy pod lupę sortowanie binarne (binary sort), które jest sprawą odrobinę bardziej skomplikowaną, od wcześniej prezentowanych algorytmów. Nie bójcie się jednak. Razem przeanalizujemy jego działanie krok po kroku.
Poszukiwania
Zanim przejdziemy do algorytmu sortowania, musimy zadać sobie ważne pytanie. Jak ma się wyszukiwanie binarne do algorytmu sortowania binarnego?
Sortowanie binarne, jest tak naprawdę modyfikacją sortowania przez wstawianie. Jeśli jeszcze go nie znacie, jego opis oraz implementacje znajdziecie w poprzedniej części „Piątku z sortowaniem”. Algorytm ten został ulepszony przez dodanie wyszukiwania binarnego, więc ujmując rzecz precyzyjnie, sortowanie binarne, to tak naprawdę sortowanie przez wstawianie z wyszukiwaniem binarnym (binary insertion sort). Nic dziwnego, że potocznie przyjęto krótszą nazwę .
By więc w pełni zrozumieć sortowanie binarne, najpierw musimy poznać jak dokładnie działa wyszukiwanie binarne.
Dziel i zwyciężaj
Algorytm wyszukiwania binarnego służy do wyszukiwania
elementów w posortowanym zbiorze liczbowym. Opiera się on na, bardzo dobrze
znanej w programowaniu, zasadzie „dziel i zwyciężaj”. Algorytm ten dzieli
tablice na mniejsze części, aż do momentu znalezienia szukanego elementu. Omówmy
jego działanie krok po kroku.
1.Na początku wyznaczamy środkową pozycję tablicy, korzystając z wzoru:
sr = (l + p)/2
gdzie:
sr – indeks środka tablicy
l – indeks lewego krańca tablicy
p – indeks prawego krańca tablicy
Jeśli tablica ma parzystą liczbę elementów,
to wynik dzielenia zaokrąglamy w dół. Zakładamy, że do obliczeń używamy liczb
całkowitych (int), w przypadku których, reszta z dzielenia jest
zwyczajnie odrzucana.
2.Gdy nasze zmienne są już wyznaczone, sprawdzamy czy liczba, kryjąca się pod indeksem sr, jest szukaną liczbą. Jeśli nie, sprawdzamy czy szukany element jest większy, czy mniejszy od elementu pod indeksem sr. Mamy wtedy dwa możliwe scenariusze:
Element szukany jest większy – Jako, iż
jest to posortowany zbiór, logicznym jest, że nasz element powinien znajdować
się na prawo od środkowego indeksu, wśród większych liczb. Wyznaczamy więc
podtablicę, odrzucając wszystkie elementy znajdujące się przed indeksem sr+1.
Dokonujemy tego, przez zmianę wartości lewego indeksu tablicy:
l = sr + 1
Element szukany jest mniejszy – Analogicznie
do powyższej sytuacji, wyznaczamy podtablicę, odrzucając wszystkie elementy
znajdujące się na prawo od indeksu sr-1:
p = sr -1
Ponownie wykonujemy wszystkie kroki, zaczynając od
wyznaczenia środkowego indeksu sr. Robimy to, aż do momentu gdy natrafimy
na szukany element.
Co jeśli szukanego elementu nie ma w zbiorze? Algorytm ten
jest również przygotowany na tą możliwość. Przed każdym wyznaczeniem nowego
indeksu sr, wystarczy sprawdzić czy l>r. Jeśli tak, oznacza
to, że naszego elementu nie ma w zbiorze.
Autor:
Więcej ciekawych algorytmów znajdziesz w tej książce
By zobrazować sobie działanie algorytmu spójrzcie na poniższą animację:
Zauważcie, że w powyższym przypadku, korzystając z
wyszukiwania liniowego (przechodzenia przez wszystkie elementy po kolei),
znalezienie indeksu szukanego elementu, zajęłoby nam aż pięć kroków.
Korzystając z wyszukiwania binarnego, znaleźliśmy element w trzech. Łatwo
wyobrazić sobie ile kroków „zaoszczędzilibyśmy”, w przypadku przeszukiwania,
nie siedmioelementowego zbioru, lecz tablicy z siedmioma tysiącami elementów.
Znając już w pełni zasadę działa wyszukiwania binarnego, możemy przejść do sortowania binarnego.
Ulepszenie
Sortowanie binarne nie różni się znacząco, od omawianego w poprzedniej części, sortowania przez wstawianie. Nic dziwnego, skoro jest to jego usprawniona wersja. Tak samo jak w podstawowej wersji algorytmu, wybieramy kolejne elementy i wstawiamy je w miejsca docelowe. Jedyną różnicą jest fakt, że do wyszukania pozycji, w której ma znaleźć się wybrany przez nas element, skorzystamy z wyszukiwania binarnego.
Jak jednak wyznaczyć warunek końca algorytmu wyszukiwania binarnego, skoro wcześniej szukaliśmy konkretnego elementu, a nie dogodnej pozycji? Otóż jego działanie wyglądać będzie następująco.
Wybierając element, który mamy wstawić w miejsce docelowe,
wyznaczamy środek już uporządkowanego zbioru liczb, gdzie indeksy l i r,
są jego krańcami. Następnie porównujemy wybrany element, z elementem pod
środkowym indeksem. Jeśli jest mniejszy – odrzucamy całą prawą część podtablicy.
Jeśli będzie większy – odrzucamy lewą.
Są dwa główne warunki końca algorytmu wyszukiwania pozycji:
Wartość indeksów l,r i sr będą
sobie równe – Jeśli element pod indeksem sr, będzie większy od
elementu wstawianego, szukana pozycja to sr-1, w przypadku kiedy będzie
mniejszy, szukaną pozycją będzie sr+1 (można także skorzystać z notacji l+1).
Wartość
indeksu l, jest większa od r – należy sprawdzić czy wartość
elementu znajdującego się pod indeksem l, jest większa od wstawianego
elementu. Jeśli tak, wstawiamy nasz elementu na l+1, jeśli nie, na
pozycję wskazywaną przez indeks l.
Jeśli sortujemy tablicę, w której wartości poszczególnych elementów powtarzają się, podczas porównywania elementów dodajemy jeden prosty warunek. Jeżeli wartość elementu pod indeksem sr, będzie równa wartości wstawianego elementu, to nowa pozycja tego elementu wynosić będzie sr+1, kończąc przy tym wyszukiwanie.
Trochę praktyki
Algorytmy najlepiej poznawać na przykładach, tak wykonajmy
kilka pierwszych kroków sortowania binarnego.
Weźmy zbiór [15, 63, 1, 81, 9]. Zaczynamy od wstawienia
elementu o wartości 63, na miejsce docelowe. Wyznaczamy środek posortowanego
już zbioru – w tym przypadku zbioru zawierającego jedynie element 15, a co za
tym idzie jego środek znajduje się pod indeksem 0. Indeksy l,r i sr,
są sobie równe, więc mamy element zbioru uporządkowanego, mający wartość
najbliższą elementowi wstawianemu. Jako, że 63 jest większe od 15, jego
docelowe miejsce znajduje się pod indeksem równym 1, czyli element ten nie
zmienia pozycji.
Przechodzimy do kolejnego kroku. Tym razem wykonujemy
porównanie dla elementu o wartości 1. Wyznaczamy wartość indeksu sr, dla
l = 0 i r=1. Wartość indeksu środkowego ponownie wynosi 0. Wykonujemy
porównanie. 1 jest mniejsze od 15. Jako, że indeksy l i r nie są
równe, musimy wyznaczyć nową podtablicę, gdzie r = sr – 1. W kolejnym
kroku wyszukiwania zauważamy, że wartość indeksu l jest większa od r (l
= 0, r = -1), oznacza to, że trafiliśmy na jeden z warunków zakończenia
algorytmu wyszukiwania. By otrzymać miejsce na które mamy wstawić nasz element,
musimy jedynie sprawdzić, czy element wstawiany jest większy, od elementu pod
indeksem l. Nie jest tak (15 > 1), więc element o wartości 1,
wstawiony zostaje na pozycję 0, a pozostałe elementy przesunięte w głąb
tablicy.
Na wykonanie pojedynczych kroków algorytmu, potrzebowaliśmy wykonać dość dużo porównań. Aby więc ułatwić zrozumienie algorytmu, spójrzmy na animację obrazującą sortowanie binarne całego powyższego zbioru:
Właściwości
Optymistyczna złożoność sortowania binarnego wynosi O(nlogn), więc teoretycznie, powinien być on szybszy od zwykłego sortowania przez wstawianie. Zauważcie jednak ile porównań musieliśmy wykonać, już w drugim kroku tego algorytmu, gdzie przy przeszukiwaniu liniowym, byłoby ich znacznie mniej.
Puenta jest więc następująca – owszem sortowanie binarne jest szybsze od sortowania przez wstawianie, ale jedynie dla większych zbiorów. W przypadku małych zbiorów, jak ten z przykładu powyżej, jest on wolniejszy, lub ledwo porównywalnie szybszy.
Sortowanie binarne – implementacje
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//program do sortowania n-elementowej tablicy
//przy pomocy algorytmu sortowania binarnego
//Jan Żwirek
#include <iostream>
using namespacestd;
voidprintTab(int*tab,intn);
//Funkcja służąca do binarnego wyszukiwania pozycji
arr[i]=sc.nextInt();//wczytujemy kolejne elementy tablicy
}
System.out.println("Oto wprowadzona tablica:");
printArray(arr);
binarySort(arr);
System.out.println("Oto wprowadzona tablica po przesortowaniu:");
printArray(arr);
sc.close();//zwalniamy zasoby
}
}
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def wypisz(tab):# tworzymy metode do wypiswania zawartosci naszej tablicy
forel intab:
print(el,end=" | ")
def binarySearch(tab,element,l,r):
print("Podtablica o indeksach l=",l," i r=",r)
ifl>=r:# warunek zakanczajacy rekurencje
ifelement>=tab[l]:# jezeli wybrany element jest wiekszy lub rowny lewemu krancowi
print("ZNALEZIENIE NOWEJ POZYCJI = ",(l+1))# zwroc pozycje o jeden wieksza
returnl+1
else:
print("ZNALEZIENIE NOWEJ POZYCJI = ",l)# w przeciwnym wypadku zwroc ta pozycje
returnl
sr=int((l+r)/2)# wyznaczamy srodek
iftab[sr]==element:# warunek zakanczajacy rekurencje, w przypadku gdy znajdziemy element rowny
print("ZNALEZIENIE NOWEJ POZYCJI = ",(l+1))# zwroc pozycje o jeden wieksza
returnl+1
iftab[sr]<element:
returnbinarySearch(tab,element,sr+1,r)
else:
returnbinarySearch(tab,element,l,sr-1)
def sortuj_bin(tab,n):
forxinrange(1,n):# iterujemy poprzez wszsytkei elementy tablicy, poza pierwszym
element=tab[x]
y=x-1
print("Rozpoczecie wyszukiwania binarnego nowej pozycji ")
print("dla elementu o wartosci = ",element)
pos=binarySearch(tab,element,0,y)
whiley>=pos:
tab[y+1]=tab[y]
y-=1
ifx!=y+1:
print("Krok ",(x-1),": Przenoszenie elementu o wartosci ",element," z pozycji ",x," na pozycje ",
pos)
else:
print("Krok ",(x-1),": Element o wartosci ",element,"pozostaje na pozycji ",x)
tab[pos]=element
wypisz(tab)
print("Podaj ilosc elementow tablicy:")
a=int(input())
tablica=[]
foriinrange(a):
print("Podaj element nr. ",(i+1))
tablica.append(int(input()))
print("Oto twoja tablica:")
wypisz(tablica)
sortuj_bin(tablica,a)
print("Oto twoja tablica po sortowaniu:")
wypisz(tablica)
Podsumowanie
Rozgryzanie sortownia binarnego właśnie za nami. Mimo tego, że był to, w porównaniu do poprzednich, nieco cięższy kawałek algorytmu, dobrnęliście ze mną do końca. Jeśli jesteście ciekawi działania kolejnych algorytmów, serdecznie zapraszam was za tydzień, na kolejną część „Piątku z sortowaniem” .
Witam
Algorytm (przynajmniej w pythonie) ma błąd ponieważ dla listy [0, 1, 2, 2, 2, 2] zwraca [0, 2, 1, 2, 2, 2]
Wydaje mi się że jest to kwestia 18 linijki w której powinno być return sr + 1
Awksi
says:Witam
Algorytm (przynajmniej w pythonie) ma błąd ponieważ dla listy [0, 1, 2, 2, 2, 2] zwraca [0, 2, 1, 2, 2, 2]
Wydaje mi się że jest to kwestia 18 linijki w której powinno być return sr + 1