A* algoritmas: heuristinė trumpiausio kelio paieška
A* algoritmas – efektyvi heuristinė trumpiausio kelio paieška: greitas sprendimas žemėlapiams, žaidimams ir robotikai, palyginimas su Dijkstra ir praktinės taikymo gairės.
A* algoritmas — tai heuristinė trumpiausio kelio paieškos technika, skirta rasti efektyviausią kelią iš pradinio taško į tikslą grafuose ar tinkleliuose. Jei turite vietovių (viršūnių) sąrašą ir žinote, kiek kainuoja pereiti tarp jų (kraštų svoriai), A* greitai suranda optimalią ar arti optimalaus sprendimą, naudodamas papildomą spėjimą apie likusią atstumą. A* glaudžiai susijęs su Dijkstros algoritmu, tačiau naudoja heuristiką, todėl dažnai ieško žymiai mažiau viršūnių nei Dijkstra. Tai ypač tinkamas algoritmas, kai reikia rasti vieną kelią tarp dviejų vietų. Jei norite rasti daug kelių iš to paties žemėlapio arba spręsti problemas su daugeliu tikslų (pavyzdžiui, keliaujančio pardavėjo uždavinys), yra kitų metodų, kurie gali būti geresni.
Pagrindinės sąvokos:
- g(n) — faktinė kaina nuo pradinio taško iki dabartinės viršūnės n.
- h(n) — heuristinis spėjimas (įvertis) apie likusią kainą nuo n iki tikslo; turi būti greitai apskaičiuojamas.
- f(n) = g(n) + h(n) — bendra funkcija, pagal kurią pasirenkami prioritetiniai viršūnės.
- Atvirasis rinkinys (open set) — viršūnių rinkinys, kuriuos dar reikia išnarplioti (dažnai realizuojamas kaip prioritetinė eilė).
- Uždarytasis rinkinys (closed set) — jau apdorotų viršūnių rinkinys.
Algoritmo eiga (santrauka):
- Pradėkite įdėdami pradinę viršūnę į atvirą rinkinį su g(start)=0 ir f=start.g + h(start).
- Kol atviras rinkinys nėra tuščias: paimkite iš jo viršūnę n su mažiausiu f(n).
- Jei n yra tikslas — rekonstruokite kelią (naudojant tėvų nuorodas) ir baigkite.
- Priešingu atveju peržiūrėkite visus n kaimynus m: skaičiuokite galimą naują g(m) per n; jei tai geriau už ankstesnį g(m), atnaujinkite g, f ir tėvą, įdėkite m į atvirą rinkinį (jei dar nebuvo).
- Perkelkite n į uždarytą rinkinį, kad vėliau jo neapdorotumėte iš naujo (jei heuristika nuosekli, node reopening dažnai nereikalingas).
Heuristikos savybės:
- Leistina (admissible): heuristika niekada nepervertina tikros likusios kainos (h(n) ≤ tikrai likusi kaina). Tai užtikrina, kad A* randa optimalų sprendimą (jei visi kraštų svoriai yra neigiami arba nuliai—reikalingi papildomi apribojimai; paprastai prielaidą darome, kad svoriai yra neigiami).
- Nuosekli (consistent arba monotoniška): heuristika tenkina h(n) ≤ c(n,m) + h(m) visiems kraštams (n→m). Nuoseklumas garantuoja, kad kai viršūnė uždaroma, jos rasti kelių kainos jau yra optimalios ir jos nebūtina vėliau atidaryti iš naujo.
Dažnai naudojamos heuristikos (pavyzdžiai):
- Manhatano atstumas — tinka tinkleliams su judėjimu tik keturiomis kryptimis (up/down/left/right).
- Euclidinis atstumas — tinka laisvam erdviniam judėjimui arba 8 kryptims, kai diagonalės kainos proporcingos tiesiam atstumui.
- Čebyševo (Chebyshev) atstumas — tinka 8 kryptims, kai diagonalės ir tiesios judėjimo kainos vienodos.
- Specifinės heuristikos, pvz., octile distance (grid su skirtinga diagonalės kaina), arba domenu pagrįstos heuristikos robotikos maršrutams.
Santykis su Dijkstra: A* su heuristika h(n)=0 tampa lygiaverčiu Dijkstros algoritmui. Kitaip sakant, Dijkstra yra A* specialus atvejis be informacijos apie likusį atstumą.
Savybės ir apribojimai:
- Jei heuristika yra leistina, A* garantuoja optimalią sprendimo kainą. Jei heuristika dar ir nuosekli, algoritmo įgyvendinimas yra efektyvesnis (nereikia atidaryti viršūnių iš naujo).
- Laiko sudėtingumas priklauso nuo heuristikos kokybės: kuo informatyvesnė heuristika (artimesnė tikrajam likusiam atstumui), tuo mažiau viršūnių bus ištirta. Blogiausiu atveju (nekokybiška heuristika) A* gali elgtis eksponentiškai.
- A* naudoja daug atminties — saugomi atviras ir uždaras rinkiniai kartu su tėvų nuorodomis; dėl to didelių grafų atveju atminties poreikis gali būti lemiamas.
- A* skirtas vieno tikslo paieškai; problemoms, kur reikia aplankyti daug tikslų (pavyzdžiui, keliaujančio pardavėjo uždavinys), tiesioginis A* nebus tinkamiausias sprendimas.
- Realiuose dinamiškuose aplinkose (kai grafas keičiasi) reikalingi variantai kaip D* arba realiu laiku veikiantys perplanuotojai.
Variantai ir pagerinimai:
- Weighted A* (svoriuota): f(n)=g(n)+w·h(n), su w>1 greičiau suranda artimą sprendimą, bet praranda garantuotą optimalumą (naudinga realaus laiko reikaluose).
- IDA* (iterative deepening A*): naudoja ribotą atmintį, atliekant iteracinį gilumo ribojimą f-verte; tinkamas, kai atmintis ribota.
- Bidirekcinis A*: ieško nuo pradžios ir nuo tikslo tuo pačiu metu, gali sutrumpinti paiešką, bet reikalauja sudėtingesnės heuristikos ir sinchronizacijos.
Praktiniai patarimai įgyvendinant:
- Naudokite efektyvią prioritetinę eilę (pvz., dvitėšinį krūvį, binary heap arba Fibonacci heap), kad paimti mažiausią f(n) būtų greita.
- Laikykite tikslias g reikšmes (geriau naudoti skaičius su pakankamu tikslumu), kad būtų patikimi sprendimai.
- Tie‑breaking: jei keli mazgai turi tą pačią f vertę, geriau rinktis tą, kurio h mažesnė (arba didesnė g), kad sumažinti ištiriamų mazgų skaičių ir pagerinti rezultatą praktikoje.
- Jei atmintis problema — apsvarstykite IDA* arba hierarchinį paieškos planavimą (abstrakcijas / multi-level grids).
Panaudojimo sritys: žaidimų dirbtinis intelektas (pathfinding), robotika ir maršrutų planavimas, GPS navigacija, grafų analizė, dirbtinio intelekto paieškos problemos ir kt.
Išvada: A* yra galingas ir lankstus algoritmas trumpiausio kelio uždaviniams, kai galima pateikti gerą heuristiką. Teisingai parinkta heuristika ir efektyvi implementacija leidžia A* veikti žymiai greičiau už neheuristinius metodus, pvz., Dijkstrą, ir dažnai yra pirmasis pasirinkimas praktinėse maršruto paieškose.
Žingsniai
A* pirmiausia reikia visų vietų, į kurias galite nuvykti, sąrašo, o tada reikia nurodyti, koks atstumas iki kiekvienos iš jų. Tada jis nurodys greičiausią kelią iš vietos A į vietą Z.
Pavyzdžiui, sakysime, kad A yra sujungta su vietomis B ir C, o B ir C yra sujungtos su D ir E. D ir E yra sujungtos su Z. Yra 4 galimi keliai iš A į Z. Galima eiti A-B-D-Z, A-C-D-Z, A-B-E-Z arba A-C-E-Z. Kompiuteris, naudojantis A*, pirmiausia žiūri, kaip sunku nuvykti iš A į B ir iš A į C. Tai yra tų vietų "kaina". Vietos kaina reiškia, kaip sunku nuvykti iš A į tą vietą. Užrašęs abi sąnaudas, kompiuteris pažiūri, kaip sunku nuvykti iš B į D, ir prideda tai prie B sąnaudų. Jis tai užrašo kaip D kainą. Tada kompiuteris pažiūri, kaip sunku nuvykti iš C į D, ir prideda tai prie C išlaidų. Tai yra kitos D išlaidos, ir jei jos yra mažesnės už jau turimas, jis pakeis senąsias. Kompiuteris nori žinoti tik geriausią kelią, todėl jis ignoruoja kelią, kurio sąnaudos yra didesnės. Jis įsimins tik vieną iš A-B-D ir A-C-D, priklausomai nuo to, kuris yra greitesnis.
Kompiuteris toliau ieško greičiausio kelio į E. Galiausiai jis eina iš D į Z ir randa sąnaudas, o iš E į Z ir randa sąnaudas. Jis gauna galutinę Z kainą, ir tai yra mažiausia kaina, kurią jis gali gauti. Dabar kompiuteris žino, kuris kelias yra greičiausias, ir turi atsakymą. Kompiuteris gali atlikti panašią veiksmų seriją, tačiau su daug daug daugiau vietų. Kiekvieną kartą jis ieškos vietos, kuri yra arčiausiai A, ir sudės tos vietos kaimynų išlaidas.
Pirmiau aprašyta veiksmų seka vadinama Dijkstros algoritmu. Dijkstros algoritmas gali būti lėtas, nes jis ieškos daugybės vietų, kurios gali būti nukreiptos ne į tą pusę nuo Z. Jei paklausėte kompiuterio, kaip nuvykti iš vieno miesto į šalia esantį, Dijkstros algoritmas gali baigti ieškoti kitoje valstybėje.
A* išsprendžia šią problemą. A* leidžia kompiuteriui atspėti, koks bus atstumas nuo kiekvienos vietos iki galo. Kompiuteris, remdamasis spėjimu, gali apytiksliai pasakyti, kiek reikės nueiti nuo tam tikros vietos iki Z. Užuot rinkęsis artimiausią A vietą, kompiuteris pažiūrės į tą vietą, kurios bendra suma tikriausiai bus mažiausia. Šią bendrą sumą jis randa prie tikėtino likusio atstumo pridėdamas išlaidas. Tokiu būdu jis gali ieškoti tik ta kryptimi, kur tikriausiai bus geriau. Nieko baisaus, jei spėjimas nėra tobulas, tačiau net ir paprastas blogas spėjimas gali priversti programą veikti daug greičiau. Jei bandote rasti kelią tarp dviejų vietų realiame pasaulyje, geras spėjimas yra tiesiog atstumas tarp jų tiesia linija. Tikrasis kelias per kelius bus ilgesnis, bet tai leidžia programai jį atspėti ir ji nenueis klaidinga kryptimi.
Matematikos ar informatikos literatūroje šis spėjimas dažnai būna vietos funkcija ir vadinamas euristika. Kiekviena vieta yra viršūnė, o kiekvienas kelias tarp dviejų vietų - briauna. Tai žodžiai iš grafų teorijos.
Ieškoti