Ten artykuł został stworzony dla starszych wersji GameMakera i może nie być aktualny.

Wyszukiwanie binarne

Czwartek, 20 Stycznia 2011, 19:33
Czas czytania 7 minut, 1 sekunda
Artykuł opisuje bardzo prosty, aczkolwiek pożyteczny algorytm zwany wyszukiwaniem binarnym. Wykorzystuje się go głównie w problemach optymalizacyjnych w celu przyspieszenia pewnych obliczeń. Pozwala poprawić złożoność czasową pewnych algorytmów.
    Wyobraźmy sobie, że chcemy znaleźć najmniejszą (lub największą) wartość spełniającą pewien warunek. Załóżmy, że nie istnieje szybki sposób na wyliczenie tej wartości, jednak dla zadanej wartości x potrafimy w prosty sposób odpowiedzieć czy wartość ta spełnia nasze wymagania czy nie. Wiemy również, że dla dowolnej liczby x jeśli spełnia ona nasz warunek to również wszystkie liczby większe (lub mniejsze) go spełniają, a jeśli nasz x nie spełnia wymagań to wszystkie liczby mniejsze (lub większe) też ich nie spełniają. Brzmi to jak jakiś teoretyczny bełkot matematyka więc by sytuacja stała się jaśniejsza posłużę się prostym przykładem:

    Nasz kolega wymyśla sobie pewną liczbę a do 1000 i nie mówi nam jaka to liczba. My możemy strzelić w dowolną liczbę i uzyskamy odpowiedź czy nasza liczba jest większa od a czy nie. I to zgadza się z wypowiedzianym wcześniej bełkotem. Bo jeśli liczba w którą strzeliliśmy spełnia warunek (jest większa) to również wszystkie liczby większe też go spełniają. Działa to również w drugą stronę. Jeśli nasz liczba nie spełnia warunku (kolega powiedział, że jest mniejsza) to również wszystkie liczby mniejsze też warunku nie spełniają.

    Teraz wyobraźmy sobie, że chcemy jak najszybciej odgadnąć o jakiej liczbie pomyślał nasz kolega. I tu w grę wchodzi właśnie wyszukiwanie binarne. Naszym warunkiem by liczba była "fajna" jest to, że ma być większa lub równa a. I tak jak mówiłem na samym początku, szukamy najmniejszej liczby spełniającej warunek, czyli właśnie liczby a. Bo a jest najmniejszą liczbą większą lub równą a. Gdybyśmy rzeczywiście próbowali odgadnąć liczbę kolegi nieświadomie zastosowalibyśmy właśnie algorytm wyszukiwania binarnego. Moglibyśmy strzelać po kolei wszystkie liczby od 1 do a, aż w końcu byśmy trafili. Pesymistycznie wykonalibyśmy n strzałów jeśli liczba pochodzi z przedziału od 1 do n. Stosując wyszukiwanie binarne zwykle liczbę odgadniemy znacznie szybciej. Wystarczy nam jedynie logarytm o podstawie 2 z a strzałów. Czyli dla przedziału od 1 do 1000000 pesymistycznie będzie to zaledwie 20 strzałów! Jak to działa?

    Załóżmy, że liczba jest z przedziału od 1 do 100. Strzelamy w połowę (czyli w 50) i już wiem czy jest ona z przedziału od 1 do 50 czy od 51 do 100. Zmniejszyliśmy przeszukiwany przedział o połowę. Załóżmy, że kolega powiedział, że 50 nie spełnia warunku (nie jest większe lub równe). Znów strzelamy w połowę (czyli w 75) i zostaje nam już tylko 25 możliwości. Po zaledwie 7 takich strzałach odgadniemy liczbę.

    Za pewne powiecie, że to nic nadzwyczajnego. Że taka strategia jest oczywista. To prawda. W tym konkretnym przykładzie. Jednak istnieją problemy w których nie widać na pierwszy rzut oka, że dobrym pomysłem będzie zastosowanie wyszukiwania binarnego. Posłużę się tutaj przykładem bardziej praktycznym i bardziej związanym z tym o czym jest ta strona czyli z grami.

    Wyobraźmy sobie, że mamy jakieś działko laserowe. Chcemy narysować od niego promień lasera o pewnej długości. Mianowicie promień ma się urwać gdy natrafi na jakąś przeszkodę. Nie potrafimy w żaden łatwy i szybki sposób obliczyć jaką długość powinien mieć promień i gdzie się kończyć. Ale powiedzmy, że mamy pewną funkcję, która otrzymuje odcinek i mówi nam czy na długości tego odcinka jest jakaś jakaś przeszkoda. W Game Makerze taką funkcją jest collision_line(). Możemy więc sprawdzać coraz dłuższe odcinki za każdym razem zwiększając je o 1 piksel, aż w końcu odcinek pewnej długości natrafi na przeszkodę. Rozwiązanie jest może i poprawne, ale bardzo wolne. Ale zauważmy, że jeśli odcinek długości x nie natrafi na przeszkodę to żaden, krótszy odcinek również nie. I to samo w drugą stronę. Jeśli odcinek długości x przetnie jakąś przeszkodę to każdy dłuższy też ją przetnie! Co nam to przypomina? Wyszukiwanie binarne! Świetny przykład, ale już nie taki oczywisty. Nie możemy łatwo obliczyć długości promienia, ale możemy łatwo sprawdzić czy promień o danej długości jest za krótki czy za długi. Gdybyśmy mięli mapę o długości 10000 to czasochłonne byłoby sprawdzanie kolizji z 10000 coraz dłuższych odcinków. A tak sprawdzimy ich zaledwie 14 (bo tyle wynosi logarytm dwójkowy z 10000).

Oto przykładowy kod algorytmu wyszukiwania binarnego w GML:

kod
p=0; //początek przedziału w którym wyszukujemy wyniku
k=10000000; //koniec przedziału
while(p!=k) //w pewnym momencie nasz przedział będzie tak krótki, że będzie zawierał jeden element (p==k). To będzie nasz wynik
{
    q=floor((p+k)/2); //Strzelamy w połowę przedziału i zapisujemy do zmiennej q
    if(WARUNEK(q)) //jeśli q spełnia nasz warunek...
        k=q; //ucinamy przedział o wszystkie liczby większe od q
    else //w przeciwnym wypadku...
        p=q+1; //ucinamy przedział o wszystkie liczby mniejsze i równe q (bo q też jest złe więc je też ucinamy, stąd q+1)
}
return p; //zwracamy wynik

    Zależnie od tego czy zadowalają nas liczby większe czy mniejsze od tej w którą strzelamy nasz kod będzie wyglądał trochę inaczej. Ale wierzę w waszą wrodzoną inteligencję. Jeśli rozumiecie działanie tego kodu to sobie poradzicie.

    Na koniec wypadałoby jeszcze napomknąć, że wyszukiwanie binarne wcale nie musi dotyczyć tylko liczb całkowitych. Mając już przedział o długości 1 wciąż możemy dzielić go na pół. Możemy robić to w nieskończoność aż do osiągnięcia satysfakcjonującej nas dokładności. Świetnym przykładem będzie funkcja obliczająca wartość pierwiastka danej liczby. Następujący kod zwróci nam ją z dokładnością do 3 miejsc po przecinku:
kod
p=0; //początek przedziału w którym wyszukujemy wyniku
k=argument0; //jako argument otrzymujemy liczbę której pierwiastek chcemy znaleźć
while(k-p>0.001) //W pewnym momencie nasz przedział wyszukiwania będzie bardzo mały. Taka dokładność nas zadowala.
{
    q=((p+k)/2); //Strzelamy w połowę przedziału i zapisujemy do zmiennej q (zauważ, że tu nie zaokrąglamy już q)
    if(q*q>=argument0) //jeśli q jest większe lub równe szukanemu pierwiastkowi
        k=q; //ucinamy przedział o wszystkie liczby większe od q (ustawiamy koniec przedziału wyszukiwania na q)
    else //w przeciwnym wypadku...
        p=q; //ucinamy przedział o wszystkie liczby mniejsze od q (zauważ, że brak tu +1 z poprzedniego kodu)
}
return p; //zwracamy wynik

Inne przykłady zastosowania wyszukiwania binarnego
    1. Mamy posortowaną tablicę liczb i chcemy sprawdzić czy znajduje się w niej jakaś liczba x. Strzelamy w środek tablicy. Jeśli liczba, która się tam znajduje jest za mała to szukamy na prawo od niej, a jeśli za duża to na lewo. Jeśli zostanie nam przedział jednoelementowy to gdy ten element jest równy x to znaleźliśmy szukaną liczbę. Jeśli jest inny to x nie występuje w tej tablicy.
    2. Mamy graf w którym krawędzie mają różne koszty przejścia. Chcemy znaleźć taką ścieżkę między wierzchołkami A i B, w której najdroższa krawędź ma możliwie najmniejszy koszt. Widzimy, że możemy strzelić w dowolny koszt x i sprawdzić czy istnieje ścieżka w której żadna krawędź nie przekracza x.

I to koniec mojego algorytmicznego bełkotu. Może później wymyślę i dodam jeszcze jakieś ciekawsze przykłady. Wyszukiwanie binarne ma ogromne zastosowanie w algorytmice, ale w praktyce raczej rzadko się przydaje. Ale zawsze staniecie się trochę mądrzejsi o te kilka słów. :)
Komentarze (łącznie 22, wyświetlam 1 - 15):
Shocker51374 (Sob., 22 Sty. 11, 14:05)
#1

Grah, miałem to w "Magicznych Bloczkach" w technikum ostatnio, ale chodziło o szukanie miejsca zerowego nieistniejącej funkcji (przez co nie szło sprawdzić czy działa) :/.

Tymon (Sob., 22 Sty. 11, 16:11)
#2

Wiesz Platyna jaki jest problem ówczesnych młodych twórców gier? Zbyt szybkie komputery - przez to nikomu nie chce uczyć się algorytmiki.

baca (Sob., 22 Sty. 11, 18:11)
#3

Algorytm jest swietny i moze sie przydac w wielu kwestiach, ai gry.

Platyna (Sob., 22 Sty. 11, 21:25)
#4

Dodałem jeszcze na końcu co nieco na temat wyszukiwania binarnego z większą dokładnością niż do liczb całkowitych. Przykład zastosowania podsunął mi kt1117.

kt1117 (Sob., 22 Sty. 11, 21:48)
#5

Uwielbiam uczucie jak się na coś przydaje. ;)

Muuuuczek567 (Pon., 24 Sty. 11, 19:45)
#6

Lol. Wygląda na to, że odkryłem wyszukiwanie binarne zanim się o nim w ogóle dowiedziałem. Przykład wykrywania punktu zderzenia z przeszkodą też jest mój, powstał ok. 2 m-ce temu. Ale numer.

Platyna (Pon., 24 Sty. 11, 21:07)
#7

Nie zerżnąłem. Nie widziałem tego przykładu. =)
Ale to było do przewidzenia, że ktoś mógł na to sam wpaść. To jeden z najprostszych algorytmów.

kt1117 (Pon., 24 Sty. 11, 21:27)
#8

Na najprostsze rzeczy czasem najtrudniej wpaść.

gnysek (Pon., 24 Sty. 11, 22:03)
#9

Skojarzyły mi się metody interpolacji liniowych - siecznych i Newtona, które pozwalają z minimalnym błędem znaleźć oczekiwane liczby do równań.
pl.wikipedia.org/wiki/Metoda_Newtona
pl.wikipedia.org/wiki/Metoda_siecznych
code.gnysek.pl/.../metoda-siecznych-metoda-newtona

Kopytkopedia (Wto., 25 Sty. 11, 06:57)
#10

Ja nie będę oceniał, bo nic z tego nie kapuję :D Ja dopiero w 5-tej klasie...

gnysek (Wto., 25 Sty. 11, 09:50)
#11

Kopyciak, ale dzielić potrafisz ? :D

S
Snake (Wto., 25 Sty. 11, 14:59)
#12

Fajne. Aż sobie tą metodą zrobiłem prosty "system oświetlenia" dla jajec :P
gmclan.org/up541_12_binary_light.html

Platyna (Wto., 25 Sty. 11, 19:46)
#13

No to to już jest zaszczyt. :D

Kopytkopedia (Wto., 25 Sty. 11, 20:33)
#14

@gnysek - no a jak :D

p
pablo1517 (Nie., 27 Mar. 11, 01:53)
#15

Ja mam teraz interpolacje na studiach... boze co za cep to wymyslil... nolife jakis xD. A ów wyszukiwanie znałem bodajże z jakiegoś sortowania, bo na wiki przy sortowaniu bąbelkowym, jest link także do tego sposobu.

Najnowsze wersje GameMakera:

Stabilna
2024.2.0.132 • 2024.2.0.163
wydana 15 dni temu
LTS
2022.0.2.51 • 2022.0.2.49
wydana 154 dni temu
Beta
2024.400.0.516 • 2024.400.0.537
wydana  5 dni temu
= IDE, = Runtime
Użytkownicy online
1 użytkownik aktywny:
gości: 1,
(~ostatnie 15 minut)
Discord
Shoutbox
I am Lord (19:15, 17.03.24)
6h mam na to hmmm
I am Lord (19:06, 17.03.24)
Ale temat fajny
gnysek (01:33, 13.03.24)
Powinno działać, jest w kodzie sortowanie wg. najbliższego startu :)
Uzjel (21:59, 11.03.24)
Nie, ale za pierwszym razem zrobiłem fuckup, że było "Tura testowa" X_X
I am Lord (16:58, 11.03.24)
A co Uzjel już masz nawymyślane 100 tematów? 😅
Uzjel (20:08, 10.03.24)
@gnysek a jak bym dodał kilka lig na raz to walnie?
Uzjel (20:08, 10.03.24)
Liga będzie zawsze od piątku 16:00 do poniedziałku 23:59, zawsze w środku miesiąca.
gnysek (08:48, 10.03.24)
Tak, to też jest do poprawy X_X
Adriann (18:22, 09.03.24)
Tylko myślę czy nie leiej gdyby mówiło że zostało tyle i tyle dni i ileś godzin a nie tylko w godzinach ;d Albo konkretna data obok, byłoby czytelniej
I am Lord (15:08, 08.03.24)
o super z tą ligą :)
Starsze wpisy znajdziesz w Archiwum.
Ankieta
Ile zarobiłeś do tej pory na grach stworzonych w GM?