Sobes.tech
Middle

Чӣ гуна арзёбӣ кардани душвории алгоритм ё муайян кардани асимптотикаи он?

sobes.tech AI

Ҷавоб аз AI

За оценка на сложността на алгоритъма се използва асимптотичен анализ, който позволява да се опише ефективността на алгоритъма в зависимост от размера на входните данни ($n$). Основните стъпки:

  1. Определяне на основните операции: Избира се операции, чиято времева сложност значително зависи от $n$ (например сравнения, присвоявания, аритметични операции).
  2. Броене на броя на операциите: Изчислява се броят на основните операции в зависимост от $n$. Това може да бъде точна формула или оценка.
  3. Определяне на асимптотичния клас: Използват се обозначенията Голямо О ($O$), Омега ($\Omega$) и Тета ($\Theta$) за описание на горната, долната и точната асимптотична стойност съответно.
  • $O(f(n))$: Алгоритъмът се изпълнява за време, което не превишава константа, умножена по $f(n)$, при големи $n$. Използва се за описание на най-лошия случай.
  • $\Omega(f(n))$: Алгоритъмът се изпълнява за време, което не е по-малко от константа, умножена по $f(n)$, при големи $n$. Използва се за описание на най-добрия случай.
  • $\Theta(f(n))$: Алгоритъмът се изпълнява за време, пропорционално на $f(n)$, при големи $n$. Използва се за описание на средния случай или когато най-добрият и най-лошият случаи имат еднакъв асимптотичен ред.

Най-често се използва Голямо $O$ за описание на горната граница на времето за изпълнение, което е важно за разбиране на мащабируемостта на алгоритъма в най-лошия сценарий.

  1. Игнориране на константи и по-малки членове: При определяне на асимптотиката се игнорират константните множители и по-малките членове, тъй като при големи $n$ доминира функцията с най-голям показател. Например, за $3n^2 + 5n + 10$, асимптотиката ще бъде $O(n^2)$.

Типични асимптотични класове (в ред на нарастване на сложността):

  • $O(1)$: Константна сложност (времето за изпълнение не зависи от $n$).
  • $O(\log n)$: Логаритмична сложност (времето за изпълнение расте много бавно с нарастването на $n$, характерно за алгоритми за двоично търсене).
  • $O(n)$: Линейна сложност (времето за изпълнение е пропорционално на $n$, характерно за простия обхват).
  • $O(n \log n)$: Линейно-логаритмична сложност (характерно за ефективни алгоритми за сортиране, като Quick Sort или Merge Sort).
  • $O(n^2)$: Квадратична сложност (времето за изпълнение расте с квадрата на $n$, характерно за прости алгоритми за сортиране, като Bubble Sort).
  • $O(n^c)$ (за $c > 1$): Полиномиална сложност.
  • $O(c^n)$ (за $c > 1$): Експоненциална сложност (времето за изпълнение расте много бързо с нарастването на $n$, характерно за обхождане на всички възможни варианти).
  • $O(n!)$: Факториална сложност (самият висок клас сложност, расте изключително бързо).

За определяне на асимптотиката циклични структури:

  • Последователни блокове кода: Складывается сложността блоков. $O(A+B) = O(\max(A, B))$.
  • Вложени цикли: Броят итераций се умножава. Цикъл с $n$ итерации, вътре в който има друг цикъл с $m$ итерации, има сложност $O(n \times m)$. Ако $m=n$, сложността е $O(n^2)$.
  • Цикли с намаляване на размера на входните данни: Например, деление на 2 при всяка итерация води до логаритмична сложност ($O(\log n)$).

Пример:

Прост обхождане на масив:

# Броене на основните операции (сравнения, присвоявания)
# В този случай, основната операция е сравнение в цикъла
def find_max(arr):
    if not arr:
        return None
    max_val = arr[0]  # 1 присвояване (вън от цикъла)
    for i in range(1, len(arr)): # Цикъл се изпълнява len(arr) - 1 пъти
        # Вътре в цикъла:
        # 1 сравнение (if arr[i] > max_val)
        # потенциално 1 присвояване (max_val = arr[i])
        if arr[i] > max_val:
            max_val = arr[i]
    return max_val

Ако размерът на масива е $n = \texttt{len(arr)}$, цикълът се изпълнява $n-1$ пъти. Във всяка итерация се изпълнява константно число операции. Общо число операции пропорционално на $n$. Асимптотична сложност: $O(n)$.

Пример: Сортировка чрез вмъкване (Insertion Sort)

# Броене на основните операции
# Основни операции: сравнения и присвоявания/премествания на елементи
def insertion_sort(arr):
    # Външен цикъл се изпълнява $n-1$ пъти
    for i in range(1, len(arr)):
        key = arr[i] # 1 присвояване
        j = i - 1 # 1 присвояване
        # Вътрешен цикъл се изпълнява в най-лошия случай $i$ пъти
        while j >= 0 and key < arr[j]:
            arr[j + 1] = arr[j] # 1 присвояване (преместване)
            j -= 1 # 1 изваждане и 1 присвояване
        arr[j + 1] = key # 1 присвояване

Външният цикъл се изпълнява $n-1$ пъти. В най-лошия случай (масивът е сортиран в обратен ред) вътрешният цикъл се изпълнява средно $i/2$ пъти (или до $i$ пъти). Общият брой операции във вътрешния цикъл за всички итерации на външния цикъл е: $1 + 2 + ... + (n-1) \approx n^2/2$. Асимптотична сложност: $O(n^2)$ в най-лошия и средния случаи, $O(n)$ в най-добрия случай (масивът вече е сортиран). При оценка на сложността обикновено се разглежда най-горният случай (Голямо $O$).