Wyszukiwanie binarne

Niektóre proste techniki znacznie przyspieszające algorytmy są stosowane tak powszechnie, jak drożdże przy pieczeniu ciasteczek. W tym artykule poznasz najbardziej kultową z nich – wyszukiwanie binarne (ang. binary search).

Zadanie - Szukanie liczby w ciągu

Dany jest ciąg nn liczb aia_i i tt zapytań (1n,ai,t106).(1 \leq n, a_i, t \leq 10^6). Każde z nich jest postaci: określ czy liczba xx występuje w ciągu.

przykład

Wolne rozwiązanie

Dla każdego z tt zapytań w czasie O(n)O(n) przeglądamy cały ciąg w poszukiwaniu x.x. Oznacza to, że wykonamy O(tn)O(tn) operacji, co zdecydowanie nie jest satysfakcjonujące. Przyspieszmy ten algorytm korzystając z pewnej obserwacji.

Ułatwienie - szukanie liczby w posortowanym ciągu

Posortujmy rosnąco wartości aia_i w jakikolwiek szybki sposób.

posortowany ciąg

Zauważmy, że jeśli ai<xa_i < x to tym bardziej aj<xa_j < x dla każdego j<i.j < i.

przykład

Analogicznie, jeśli aixa_i \geqslant x to tym bardziej ajxa_j \geqslant x dla każdego j>i.j > i.

przykład

Wykorzystując powyższy fakt, dochodzimy do wniosku, że jeśli aixa_i \neq x to w zależności od tego czy aix,a_i \geq x, czy ai<xa_i < x wartość xx na pewno nie będzie występować na pozycjach większych lub mniejszych od i.i.

Rozwiązanie - wyszukanie binarne

Niech pp i kk będą odpowiednio początkiem i końcem przedziału, na którym może wystąpić x.x. Jako, że na początku nie mamy żadnych informacji na temat naszego ciągu, to potencjalnie może ono wystąpić wszędzie: p=1,p = 1, k=n.k = n. Niech srsr będzie środkiem przedziału: sr=p+k2sr = \frac{p + k}{2} (zaokrąglone w dół). Sprawdzimy teraz relację między asra_{sr} a xx: - asr<xa_{sr} < x: xx-a na pewno nie ma na przedziale [p,sr],[p, sr], ale może wystąpić w [sr+1,k].[sr + 1,k]. Przyjmujemy p=sr+1.p = sr + 1.

x w prawym przedziale

  • asrxa_{sr} \geqslant x: xx-a na pewno nie ma na przedziale [sr+1,k],[sr + 1,k], ale może wystąpić w [p,sr].[p,sr]. Przyjmujemy k=sr.k = sr.

x w lewym przedziale

Po skończonej liczbie wywołań takiego algorytmu p=k,p = k, ponieważ z każdym krokiem zmniejszamy długość naszego przedziału. Oznacza to, że jedyne miejsce, w którym potencjalnie może znaleźć się xx to ap.a_p. Wystarczy sprawdzić czy x=apx = a_p i możemy odpowiedzieć na zapytanie.

Binsearch - złożoność czasowa

Warto zastanowić się, jaka dokładnie jest ta 'skończona liczba wywołań'. Za każdym razem zmniejszamy długość naszego przedziału dwukrotnie. Oznacza to, że po pierwszym wykonaniu algorytmu nasz przedział będzie dwa razy krótszy, po drugim: cztery, po trzecim: osiem, a po ii-tym: 2i.2^i. Długość przedziału wyniesie 11 po kk krokach dla takiego k,k, że 2kn.2^k \approx n.

Takie kk nazywamy logarytmem dwójkowym i w informatyce oznaczamy jako log n.log~n. Złożoność czasowa odpowiedzi na jedno zapytanie wynosi O(log n).O(log~n). Ze względu na to, że potęgi dwójki bardzo szybko rosną, logarytm rośnie powoli. Zauważmy, że dla n=106n = 10^6 zachodzi log n20.log~n \approx 20. Dla komputera jest to tyle, co nic. Mając dane tt zapytań, koszt czasowy całego programu można oszacować jako O(n log n+t log n),O(n~log~n + t~log~n), gdyż sortowanie ciągu kosztuje O(n log n).O(n~log~n). Opisaną wyżej technikę nazywamy wyszukiwaniem binarnym.

Zastosowanie wyszukiwania binarnego - obliczanie pierwiastka

Znajdź najmniejszą liczbę całkowitą większą lub równą pierwiastkowi nn (1n1018).(1 \leqslant n \leqslant 10^{18}).

Rozwiązanie

Podobnie jak w powyższym problemie, będziemy zmniejszać przedział możliwości. Na początku znajdźmy górne i dolne ograniczenia. Wiemy, że pierwiastek jest na pewno dodatni: możemy przyjąć p=1.p = 1. Tak samo żadna liczba całkowita nie ma pierwiastka większego od samej siebie: ustawmy k=n.k = n.

„Strzelamy” w środek przedziału – srsr i sprawdzać, relację sr2sr^2 z n.n. Jeżeli sr2<n,sr^2 < n, to sr<nsr < \sqrt{n} – szukana wartość będzie większa niż sr.sr. To znaczy, że będzie znajdować się na przedziale [sr+1,k].[sr + 1, k]. W przeciwnym wypadku pozostaje do rozważenia przedział [p,sr].[p, sr].

Tak samo jak ostatnio po O(log n)O(log~n) ruchach p=kp = k oraz jest to rozwiązanie naszego pierwiatka. W przypadkach takich, jak to zadanie, gdy wyszukujemy wartość, będącą rozwiązaniem danego problemu, mówimy o „wyszukiwaniu binarnym po wyniku”.

Zadania