Sobes.tech
Middle

Kaip įvertinti algoritmo sudėtingumą arba nustatyti jo asimptotą?

sobes.tech AI

Atsakymas iš AI

Algoritmų sudėtingumo įvertinimui naudojama asymptotinė analizė, leidžianti aprašyti algoritmo efektyvumą priklausomai nuo įvesties duomenų dydžio ($n$). Pagrindiniai žingsniai:

  1. Pagrindinių operacijų nustatymas: Išskiriamos operacijos, kurių vykdymo laikas žymiai priklauso nuo $n$ (pvz., palyginimai, priskyrimai, aritmetinės operacijos).
  2. Operacijų skaičiaus skaičiavimas: Apskaičiuojamas pagrindinių operacijų kiekis priklausomai nuo $n$. Tai gali būti tiksli formulė arba įvertinimas.
  3. Asymptotinės klasės nustatymas: Naudojami Didžiojo O ($O$), Omega ($\Omega$) ir Teta ($\Theta$) žymėjimai, apibūdinantys viršutinį, apatinį ir tikslų asymptotinį elgesį atitinkamai.
  • $O(f(n))$: Algoritmas vykdomas per laiką, neviršijantį konstantos, padaugintos iš $f(n)$, dideliems $n$. Naudojama apibūdinti ** blogiausiam ** atvejui.
  • $\Omega(f(n))$: Algoritmas vykdomas per laiką, ne mažiau kaip konstantos, padaugintos iš $f(n)$, dideliems $n$. Naudojama apibūdinti ** geriausiam ** atvejui.
  • $\Theta(f(n))$: Algoritmas vykdomas per laiką, proporcingą $f(n)$, dideliems $n$. Naudojama apibūdinti ** vidutiniam ** atvejui arba kai geriausias ir blogiausias atvejai turi tą patį asymptotinį tvarką.

Dažniausiai naudojamas Didžiojo $O$ žymėjimas apibūdinti viršutinę vykdymo laiko ribą, kas svarbu siekiant suprasti algoritmo mastelio keičiamumą blogiausiu atveju.

  1. Konstantų ir mažesnių narių ignoravimas: Nustatant asymptotiką ignoruojami konstantiniai koeficientai ir žemesnio tvarkos nariai, nes dideliems $n$ dominuoja didžiausio laipsnio funkcija. Pavyzdžiui, $3n^2 + 5n + 10$ asymptotika bus $O(n^2)$.

Tipinės asymptotinės klasės (mažėjimo tvarka pagal sudėtingumą):

  • $O(1)$: Konstanta (vykdymo laikas nepriklauso nuo $n$).
  • $O(\log n)$: Logaritminė sudėtingumas (vykdymo laikas labai lėtai auga su $n$, būdingas dvejetinio paieškos algoritmo sudėtingumui).
  • $O(n)$: Linijinis sudėtingumas (vykdymo laikas tiesiog proporcingas $n$), būdingas paprastam peržiūrai.
  • $O(n \log n)$: Linijinis-logaritminis sudėtingumas (efektyvių rūšiavimo algoritmų, tokių kaip Quick Sort ar Merge Sort, būdingas).
  • $O(n^2)$: Kvadratinis sudėtingumas (vykdymo laikas auga kvadratu $n$, būdingas paprastiems rūšiavimo algoritmams, pvz., Bubble Sort).
  • $O(n^c)$ (c > 1): Polinominis sudėtingumas.
  • $O(c^n)$ (c > 1): Eksponentinis sudėtingumas (vykdymo laikas labai greitai auga su $n$, būdingas visų galimų variantų peržiūrai).
  • $O(n!)$: Faktorialinis sudėtingumas (aukščiausia sudėtingumo klasė, labai greitai augantis).

Norint nustatyti ciklinių struktūrų asymptotiką:

  • Eiliški kodo blokai: Sudėtingumas sudedamas. $O(A+B) = O(\max(A, B))$.
  • Įdėti ciklai: Dauginama ciklų iteracijų skaičius. Ciklas su $n$ iteracijų, kurio viduje yra kitas ciklas su $m$ iteracijų, turi sudėtingumą $O(n \times m)$. Jei $m=n$, sudėtingumas $O(n^2)$.
  • Ciklai su duomenų dydžio mažinimu: Pvz., dalijimas iš 2 kiekvienoje iteracijoje lemia logaritminį sudėtingumą ($O(\log n)$).

Pavyzdys:

Paprastas masyvo peržiūrėjimas:

# Pagrindinių operacijų skaičiavimas (palyginimai, priskyrimai)
# Pagrindinė operacija - palyginimas cikle
def find_max(arr):
    if not arr:
        return None
    max_val = arr[0]  # 1 priskyrimas (už ciklo)
    for i in range(1, len(arr)): # Ciklas vyksta $len(arr) - 1$ kartų
        # Viduje ciklo:
        # 1 palyginimas (if arr[i] > max_val)
        # galimai 1 priskyrimas (max_val = arr[i])
        if arr[i] > max_val:
            max_val = arr[i]
    return max_val

Jei masyvo dydis $n = \texttt{len(arr)}$, ciklas vyksta $n-1$ kartą. Kiekvienoje iteracijoje vykdomas pastovus operacijų skaičius. Bendra operacijų skaičius proporcingas $n$. Asimptotinė sudėtingumas: $O(n)$.

Pavyzdys: įterpimo rūšiavimas (Insertion Sort)

# Pagrindinių operacijų skaičiavimas
# Pagrindinės operacijos: palyginimai ir priskyrimai/perkėlimai
def insertion_sort(arr):
    # Išorinis ciklas vyksta $n-1$ kartų
    for i in range(1, len(arr)):
        key = arr[i] # 1 priskyrimas
        j = i - 1 # 1 priskyrimas
        # Vidinis ciklas vyksta blogiausiu atveju $i$ kartų
        while j >= 0 and key < arr[j]:
            arr[j + 1] = arr[j] # 1 priskyrimas (perkėlimas)
            j -= 1 # 1 atėmimas ir 1 priskyrimas
        arr[j + 1] = key # 1 priskyrimas

Išorinis ciklas vyksta $n-1$ kartą. Vidinis ciklas blogiausiu atveju (masyvas atvirkščiai surūšiuotas) vyksta vidutiniškai $i/2$ kartų (arba iki $i$ kartų). Bendra operacijų skaičius visoms išorinio ciklo iteracijoms viduje: $1 + 2 + ... + (n-1) \approx n^2/2$. Asimptotinė sudėtingumas: $O(n^2)$ blogiausiu ir vidutiniu atveju, $O(n)$ geriausiu atveju (jei masyvas jau surūšiuotas). Vertinant sudėtingumą dažnai laikoma blogiausio atvejo (Didžiojo $O$).