Matematiikassa ja tietojenkäsittelytieteessä Big O -merkintä on tapa verrata eri funktioiden kasvunopeuksia. Sitä käytetään usein eri algoritmien tehokkuuden vertailuun, mikä tehdään laskemalla, kuinka paljon muistia tarvitaan ja kuinka paljon aikaa algoritmin suorittamiseen kuluu.

Big O -merkintää käytetään usein ongelman monimutkaisuuden määrittelyssä, jota kutsutaan myös ongelman monimutkaisuusluokaksi. Matemaatikko Paul Bachmann (1837-1920) käytti tätä merkintätapaa ensimmäisenä vuonna 1896 julkaistussa kirjansa "Analytische Zahlentheorie" toisessa painoksessa. Edmund Landau (1877-1938) teki merkintätavasta suositun. Tästä syystä kun puhutaan Landaun symboleista, viitataan tähän merkintätapaan.

Big O -notaatio on saanut nimensä "funktion järjestyksen" mukaan, joka viittaa funktioiden kasvuun. Big O -merkintätapaa käytetään funktion kasvunopeuden ylärajan (suurimman mahdollisen määrän) löytämiseen, mikä tarkoittaa, että se laskee pisimmän ajan, joka tarvitaan syötteen muuttamiseen tulosteeksi. Tämä tarkoittaa, että algoritmi voidaan ryhmitellä sen mukaan, kuinka kauan se voi kestää pahimmassa tapauksessa, jossa pisin reitti otetaan joka kerta.

Tarkemmin sanottuna annetaan kaksi positiivista funktiota f ( x ) {\displaystyle f(x)}f(x) ja g ( x ) {\displaystyle g(x)}{\displaystyle g(x)} , sanomme, että f {\displaystyle f}f on g {\displaystyle g}g isossa O:ssa (kirjoitettuna f ∈ O ( g ) {\displaystyle f\in O(g)}{\displaystyle f\in O(g)} ), jos tarpeeksi suurella x {\displaystyle x}x , f ( x ) ≤ k g ( x ) {\displaystyle f(x)\leq k\cdot g(x)}{\displaystyle f(x)\leq k\cdot g(x)} jonkin vakion k {\displaystyle k}k tapauksessa.

Big O on lauseke, joka määrittää pahimman mahdollisen skenaarion suoritusajan ja osoittaa, kuinka tehokas algoritmi on ilman, että ohjelmaa tarvitsee ajaa tietokoneella. Tämä on hyödyllistä myös siksi, että eri tietokoneissa voi olla erilainen laitteisto, ja siksi ne tarvitsevat eri määrän aikaa ohjelman suorittamiseen. Koska Big O olettaa aina pahimman tapauksen, se voi näyttää johdonmukaisen nopeuden mittauksen: laitteistosta riippumatta O ( 1 ) {\displaystyle O(1)}{\displaystyle O(1)} valmistuu aina nopeammin kuin O ( n ! ) {\displaystyle O(n!)}. {\displaystyle O(n!)}, koska niiden tehokkuus on erilainen.


 

Määritelmän selitys ja todistaminen käytännössä

Formaalisti sanotaan: f(n) ∈ O(g(n)), jos löytyy positiivinen vakio k ja luku n0 siten, että kaikilla n ≥ n0 pätee f(n) ≤ k·g(n). Käytännössä tämä tarkoittaa, että g(n) toimii ylärajana f(n):lle, kun n kasvaa riittävän suureksi.

  • Valitse sopiva g(n) — yleensä samaa tai korkeampaa kasvuluokkaa oleva funktio (esim. n, n^2, 2^n).
  • Määritä vakio k ja leikkauspiste n0, jotka tekevät epäyhtälöstä totta kaikilla n ≥ n0.
  • Todista epäyhtälö yksinkertaisilla arvioilla (esim. pienempi termi ≤ suurempi termi, polynomien suurompia termejä verrataan).

Esimerkki: f(n)=3n+2. Valitaan g(n)=n. Silloin 3n+2 ≤ 5n aina kun n ≥ 2. Voimme siis valita k=5 ja n0=2, joten 3n+2 ∈ O(n).

Toinen esimerkki: f(n)=n^2+n. Valitaan g(n)=n^2. Silloin n^2+n ≤ n^2 + n^2 = 2n^2 aina kun n ≥ 1. Voimme siis valita k=2 ja n0=1, joten n^2+n ∈ O(n^2).

Yleisimpiä kasvuluokkia ja käytännön esimerkit

  • O(1) — vakioaika: esimerkiksi arvon hakeminen taulukon indeksistä.
  • O(log n) — logaritminen: esimerkiksi binäärihaku järjestetyssä taulukossa.
  • O(n) — lineaarinen: esimerkiksi läpikäynti listan kaikille alkioille.
  • O(n log n) — yleinen tehokas lajittelun aikavaativuus, esim. mergesort tai heapsort.
  • O(n^2) — kvadraattinen: esimerkiksi yksinkertainen kaksinkertainen silmukka (bubble sort, insertion sort huonossa tapauksessa).
  • O(2^n) — eksponentiaalinen: esim. kaikki osajoukot generointi ilman optimointeja.
  • O(n!) — faktoriaalinen: täydellinen permutaatioiden läpikäynti (esim. bruteforce Travelling Salesman -ongelma).

Sääntöjä ja nyrkkisääntöjä

  • Pienemmät termit voi jättää: f(n)=n^2 + 100n + 50 ∈ O(n^2).
  • Vakioita voidaan jättää: f(n)=3n ∈ O(n).
  • Summien ja tuotteiden käsittely: f(n)+h(n) ∈ O(max(g(n),q(n))) jos f∈O(g) ja h∈O(q). Jos algoritmi koostuu peräkkäisistä vaiheista, kompleksiteetit summautuvat (mutta asymptoottisesti dominoiva termi määrää luokan).
  • Nistetyt silmukat: jos sisäkkäisiä silmukoita ajetaan n kertaa kumpikin, tulos on O(n·n)=O(n^2).

Esimerkkikoodia ja analyysi

Yksinkertainen lineaarinen haku:

 for i in 1..n:     if A[i] == x:         return i 

Tässä parhaassa tapauksessa (alkio löytyi heti) aika on O(1), pahimmassa tai odotusarvoisesti O(n).

Kaksi sisäkkäistä silmukkaa:

 for i in 1..n:     for j in 1..n:         do_constant_work() 

Toiminto suoritetaan n·n kertaa, eli aikavaativuus on O(n^2).

Pahoin vs. keskimäärin ja parhain tapaus

Big O kuvaa ylärajaa (pahin tapaus tai asettelu) — se ei kerro, kuinka usein pahin tapaus toteutuu. On myös muita merkintöjä:

  • Θ (Theta) kuvaa tiukkaa rajaa (sekä ylä- että alaraja).
  • Ω (Omega) kuvaa alarajaa (parhain tai vähimmäisteho).

Kun kiinnostaa odotusarvoinen suorituskyky (keskiarvo), käytetään usein keskimääräistä analyysiä — se ei korvaa Big O -ylärajaa mutta antaa täsmentävää tietoa käytännöstä.

Tilavaativuus

Big O käytetään myös muistinkulutuksen arvioimiseen (tilavaativuus). Esimerkiksi algoritmi, joka tarvitsee lisämuistia vain muutaman muuttujan verran, on O(1) tilavaativuudeltaan, kun taas algoritmi joka varaa uuden taulukon koossa n on O(n).

Yhteenveto

  • Big O on kätevä tapa verrata algoritmeja riippumatta laitteistosta, koska se kuvaa kasvunopeutta asyymptoottisesti.
  • Se antaa ylärajan suoritusajalle (pahin tapaus) ja helpottaa algoritmien luokittelua.
  • Kun opit muutaman perussäännön (poista vakioita, jätä pienemmät termit, yhdistä monimutkaisuuksia), analysointi helpottuu merkittävästi.