KoodiMaailman vaikein algoritmiMoni koodari on yrittänyt toteuttaa binäärihaun, mutta tuskin kukaan on onnistunutsiinä. Tämän jutun luettuasi voit liittyä harvojen ja valittujen joukkoon.Teksti: Antti Laaksonen Kuva: Mitol BerschewskyBinäärihaku on ohjelmoinnin teoriantunnetuimpia algoritmeja. Sen tehokkaammin, jos taulukon alkiot voivatTarkistusta ei voi tehdä lineaarihakuaavulla voi esimerkiksi tarkistaa olla sekaisin. Tällöin etsittävä alkio voitehokkaasti, esiintyykö annettu luku järjestetyssätaulukossa. Toisaalta binäärihakuon tullut tunnetuksi myös siitä, ettäalgoritmin toteuttaminen virheettömästion lähestulkoon mahdotonta. Näin asiaonkin, jos algoritmin toteuttaa perinteiselläolla missä tahansa taulukon kohdassa,ja algoritmin täytyy pahimmassa tapauksessakäydä läpi kaikki alkiot. Tilannekuitenkin muuttuu, jos taulukon sisältöon järjestetty. Tällöin binäärihaku on ylivertaisentehokas algoritmi etsimiseen.menetelmällä. Toteuttaminen muut-Perinteinen binäärihakuun liittyvätuu kuitenkin lastenleikiksi, kun valitaantoisenlainen lähestymistapa.vertauskuva on nimen etsiminen puhelinluettelosta.Aluksi puhelinluetteloavataan keskeltä ja katsotaan, mikä nimiBinäärihaulla olisit jo perilläOletetaan, että taulukko T sisältää n lukuaja että tehtävänä on tarkistaa, esiintyyköluku k taulukossa. Yksinkertaisinalgoritmi tähän tehtävään on lineaarihaku,joka käy läpi taulukon luvut yksikerrallaan. C-sukuisella kielellä lineaarihaunvoisi toteuttaa seuraavasti:for (int i = 0; i < n; i++)if (T[i] == k)return true;return false;siinä kohdassa on. Nyt haun voi rajoittaaluettelon alkuosaan tai loppuosaan riippuensiitä, mikä nimi on luettelon keskellä.Hakualuetta puolitetaan tällä tavallayhä uudestaan, kunnes alue on niin pieni,että nimi löytyy helposti. Idea on hauska,mutta ikävä kyllä binäärihaun koodaaminenbugittomasti on tämän pohjalta vaikeaa.Helpompi lähestymistapa on ajatellabinäärihakua lineaarihaun tehostuksena.Ideana on siirtyä taulukossa eteenpäinhyppäyksin. Ensin hypyn pituus on n/2indeksiä, sitten n/4 indeksiä, sitten n/8indeksiä jne. Haun alussa voidaan tehdäpitkiä loikkia, mutta vauhti hidastuu, kuntullaan lähemmäksi etsittävää lukua. Tuloksenaon seuraava koodi:int i = 0;for (int b = n / 2; b >= 1; b /= 2)while (i + b < n && T[i + b]
Muuttuja b määrittää, kuinka suuriahyppyjä haussa tehdään. Jokaisen hypynpituuden kohdalla liikutaan niin montahyppyä eteenpäin kuin mahdollista menemättätaulukon oikean reunan yli taisellaiseen lukuun, joka on etsittävää lukuasuurempi. Kuten aiemminkin, muuttujai on taulukkoon osoittava indeksi.Haun päätteeksi i osoittaa kohtaan, jossahaettava luku esiintyy taulukossa, mikälisellainen kohta on olemassa.Matemaattisesti ilmaistuna lineaarihakutarkistaa pahimmillaan n taulukonkohtaa, kun taas binäärihaku tarkistaavain noin log 2n kohtaa. Binäärihaun askeltenlogaritminen määrä johtuu siitä,että hypyn pituus puolittuu jokaisessavaiheessa. Lisäksi tiettyä hypyn pituuttavastaavia askelia tehdään käytännössäkorkeintaan kaksi. Ero n:n ja log 2n:n tarkistuksenvälillä on todella merkittävä,kun n on suuri luku. Esimerkiksi jos n onmiljoona, log 2n on vain noin 20.Mitä sillä tekee?Osaamme nyt koodata toimivan binäärihaun,mutta mihin sitä voisi käyttää?Yllättävää kyllä, binäärihakua käytetäännykyään harvoin järjestetystä taulukostahakemiseen, sillä useimmissaohjelmointikielissä on valmiina paljonmonipuolisempia tietorakenteita. EsimerkiksiC++:ssa voi käyttää std::setluokkaa,joka tukee tehokkaan haun lisäksimyös tehokasta alkion lisäämistä japoistamista.Binäärihaulla on kuitenkin muitakinsovelluksia. Tutkitaan vaikkapa tieverkkoa,jossa on kaupunkeja sekä kahdenkaupungin välisiä teitä. Jokaisesta tienpätkästätiedetään, paljonko polttoainettasillä ajaminen kuluttaa. Autot voivattankata kaupungeissa mutta eivät tieosuuksilla.Minkä kokoinen polttoainesäiliö autossatäytyy vähintään olla, jotta sillä voiajaa mistä vain kaupungista mihin tahansatoiseen kaupunkiin?2AC38BD5Kuva 1. Polttoaineongelman lähtöasetelma.Kuvassa 1 on esimerkki tieverkosta.Jokainen kuvan ympyrä vastaa kaupunkia,jotka on merkitty tunnuksin A, B, C jaD. Kaupunkien väliset tiet on merkitty viivoin.Esimerkiksi kaupungista B pääseekaupunkiin C kuluttamalla kolme litraapolttoainetta.Tehtävän ratkaisussa kannattaa ensinmiettiä yksinkertaisempaa ongelmaa: jospolttoainesäiliön koko on x litraa, onkomahdollista ajaa minkä tahansa kahdenkaupungin välillä?Ongelma ratkeaa näin: Valitaan mikätahansa kaupunki lähtökaupungiksi jamerkitään se ruksilla. Sitten merkitäänkaikki kaupungit, joihin lähtökaupungistapääsee x litralla polttoainetta. Tämänjälkeen merkitään vastaavasti kaikkikaupungit, joihin pääsee viimeksi merkityistäkaupungeista. Samaa toistetaanniin kauan kuin uusia kaupunkeja tuleemukaan. Jos lopussa kaikki kaupungit onmerkitty, x litraa riittää minkä tahansakahden kaupungin välillä ajamiseen.2AC38BDKuva 2. Mihin pääsee C:stä kolmen litran säiliöllä?2AC38BD55Kuva 3. Viiden litran säiliö riittää kaikkialle.Luodaan riittaa(x)-funktio, joka palauttaatrue, jos x litran säiliö riittää kaikkienkaupunkien välillä, ja muuten false. Jostehtävän ratkaisu on r litraa, riittaa(x)palauttaa false aina, kun x on r–1 tai vähemmän,ja true aina, kun x on r taienemmän.Funktion arvoissa on siis yksi muutoskohta,jonka vasemmalla puolella jokainenarvo on false ja oikealla puolellajokainen arvo on true. Lisäksi tiedämme,että kaupungista toiseen pääsee ainakinsilloin, kun polttoainesäiliö riittää kaikilletieosuuksille. Näiden ominaisuuksienansiosta tehtävän voi ratkaista tehokkaastibinäärihaulla funktiota riittaa(x)käyttäen:int v = 0;for (int b = s / 2; b >= 1; b /= 2)while (!riittaa(v + b))v += b;return v + 1;Muuttujassa s on tieverkon vaativimmantieosuuden polttoainekulutus. Tämäarvo on luonteva valinta binäärihaun ylärajaksi.Koodi laskee muuttujaan v suurimmanpolttoainemäärän, joka ei riitäminkä tahansa kahden kaupungin välilläajamiseen. Tehtävän vastaus on sitenyhden suurempi kuin tämä arvo. Binäärihaunansiosta funktiota riittaa(x) täytyykutsua vain noin log 2s kertaa.Vastaavaa menetelmää voi soveltaaaina, kun etsitään lukua ja osataan tunnistaa,onko jokin annettu luku pienempivai suurempi kuin etsitty luku. Binäärihaunansiosta ei tarvitse käydä läpi kaikkiamahdollisia arvoja vaan logaritminenmäärä riittää. Algoritmista tulee siis tehokas.21