Анализ на алгоритми


Експериментални изследвания


Ограничения на експериментите

Време за изпълнение

Теоритичен анализ

Псевдокод

Псевдокод е програмен код, несвързан с хардуера на даден компютър и искващ пренаписване на кода за компютър, преди програмата да  може да се използва. Няма строги правила за синтаксиса, предназначен е за хората, не за компютрите.
Пример: Намиране на най-голям елемент на масив.

Algorithm arrayMax(A, n)
   Input: array A of n integers
   Output: maximum element of A
   currentMax <- A[0]
   for i <- 1 to n−1 do
      if A[i] > currentMax then currentMax <- A[i]
   return currentMax

Детайли на псевдокода

if…then…[else…]
while…do…
repeat…until…
for…do…

Отместването замества скобите
.
Algorithm method(arg[, arg…])
Input…
Output…
var.method (arg[, arg…])
return expression
<- Assignment (like = in C++)
= Equality testing (like == in C++)
n2 - степени и др. математически означения са позволени.

Машина с пряк достъп - (RAM модел)

Елементарни операции (примитиви)

Примери:

Броене на елементарните операции

Пример:
Algorithm arrayMax(A, n) Брой на операциите
  currentMax <- A[0] 2
  for i <- 1 to n−1 do 2 + n
    if A[i] > currentMax then 2(n − 1)
    currentMax <- A[i] 2(n − 1)
  {increment counter i} 2(n −1)
  return currentMax
1

Общо 7n − 1

Оценка на времето за изпълнение

            a = времето за изпълнение на най-бързата елементарна операция
            b = времето за изпълнение на най-бавната елементарна операция             a (7n − 1) ≤ T(n) ≤ b(7n − 1)

Темп на растеж (Growth Rate) на времето за изпълнение (Running Time)

Промяната на хардуера и софтуера
Линейната скорост на растеж на времето за работа на T(n) е присъщо (вътрешно) свойство на алгоритъма аrrayMax.

Темпове на растеж (скорост на нарастване)

Функция / n
1
2
10
100
1000
5
5
5
5
5
5
log n
0
1
3.32
6.64
9.96
n
1
2
10
100
1000
n log n
0
2
33.2
664
9996
n2
1
4
100
104
106
n3
1
8
1000
106
109
2n
2
4
1024
1030
10300
n!
1
2
3628800
10157
102567
nn
1
4
1010
10200
103000

Константни фактори

Темпът на растеж не се влияе от
Примери:

Асимптотична нотация

О-голямо (Big-Oh)

За функциите f(n) и g(n) казваме, че  f(n) е O(g(n)), ако съществува положителна константа c и N > 0 такива, че f(n) < c g(n) за всяко n > N.

Пример 1: 2n +10 е O(n)
2n +10 ≤ cn; (c − 2) n ≥10; n ≥ 10/(c − 2)
Избираме c = 3 и N = 10.

Пример 2: Функцията n2 не е O(n).
n2c n; nc
Горното неравенство не може да бъде изпълнено, тъй като c трябва да бъде константа.

Пример 3: 7n - 2 е O(n)
Необходимо е c > 0 и N ≥ 1 да са такива, че 7n - 2 ≤ c n за nN.
Това е вярно за c = 7 и N = 1 (не са единствени!).

Пример 4: 3n3+ 20n2+ 5 is O(n3)
Необходимо е c > 0 и N ≥ 1 да са такива, че 3n3+ 20n2+ 5 ≤ cn3 за nN.
Това е вярно за c = 4 и N = 21.

Пример 5: 3 log n + log log n е O(log n)
Необходимо е c > 0 и N ≥ 1 да са такива, че 3 log n + log log nc log n за nN.
Това е вярно за c = 4 и N = 2.

Правила за О-голямо

            1. премахни членовете от по-нисък ред и
            2. премахни константите (константните множители).
            “2n е O(n)” вместо  “2n е O(n2)”
            “3n + 5 е O(n)” вместо "3n + 5 е O(3n)"



Асимптотичен анализ на алгоритми

            1. Намираме в най-лошия случай броят на елементарните операции като функция на входния размер.
            2. Изразяваме тази функция с О-голямо нотация.
Пример:
    Определихме, че алгоритъмът
arrayMax изпълнява най-много 7n−1 елементарни операции.
    Казваме, че алгоритъмът
arrayMax “работи за време O(n)”.

Изчисляване на префиксни средни

        A[i]= (X[0] +X[1] +… +X[i])/(i+1)

Префиксни средни  (квадратен)

Следният алгоритъм изчислява префиксни средни за квадратично време, като прилага определението.

Algorithm prefixAverages1(X, n)
Input array X of n integers
Output array A of prefix averages of X
A <- new array of n integers
for i
<- 0 to n−1 do
   s
<- X[0]
   for j
<- 1 to i do
      s
<- s+X[j]
   A[i]
<- s/(i+1)
return A



#operations
n
n
n

1 + 2 + …+(n−1)
1 + 2 + …+(n−1)
n
1

Аритметична прогресия

        O(1 + 2 + …+ n)

Префиксни средни  (линеен)

Следният алгоритъм изчислява префиксни средни за линейно време, като поддържа текущите суми.

Algorithm prefixAverages2(X, n)
Input array X of n integers
Output array A of prefix averages of X
A <- new array of n integers
s <- 0
for i <- 0 to n−1 do
   s <- s + X[i]
   A[i] <- s/(i + 1)
return A



#operations
n
1
n
n
n

1

Така алгоритъмът prefixAverages1 се изпълнява за време O(n).

Роднини на О-голямо

Омега-голямо (Big-Omega)
f(n) е
Ω(g(n)), ако съществува константа c > 0 и цяла константа N > 0 такива, че f(n) > c g(n) за всяко n > N.

Тита-голямо (Big-Theta)
f(n) е
Θ(g(n)), ако съществуват константи c' > 0 и c" > 0 и цяла константа N > 0 такива, че c'g(n) <  f(n) < c"g(n) за всяко n > N.

о-малко (little-oh)
f(n) е o(g(n)), ако за всяка константа c > 0, съществува цяла константа N > 0 такава, че f(n) < c g(n) за всяко n > N.

омега-малко (little-omega)
f(n) е
ω(g(n)), ако за всяка константа c > 0, съществува цяла константа N > 0 такава, че f(n) < c g(n) за всяко n > N.

Интуиция за асимптотичната нотация

Big-Oh
f(n) е O(g(n)), ако f(n) е асимпотично по-малка или равна на g(n).

big-Omega
f(n) е Ω(g(n)),  ако f(n) е асимпотично по-голяма или равна на g(n).

big-Theta
f(n) е Θ(g(n)), ако f(n) е асимпотично равна на g(n).

little-oh
f(n) е o(g(n)), ако f(n) е асимпотично строго по-малка от g(n).

little-omega
f(n)) е ω(g(n)), ако f(n) е асимпотично строго по-голяма от g(n).

Примери за използване на роднините на О-голямо

Пример 1: 5n2 е Ω(n2)
f(n) е Ω(g(n)), ако съществува константа c > 0 и цяла константа N ≥ 1 такива, че f(n) ≥ c g(n) за всяко nN.
Нека c = 5 и N = 1.

Пример 2: 5n2 е Ω(n)
f(n) е Ω(g(n)), ако съществува константа c > 0  и цяла константа N ≥ 1 такива, че f(n) ≥c g(n) за всяко nN.
Нека c = 1 и N = 1,
5n n.

Пример 3: 5n2 е ω(n)
f(n) е ω(g(n)), ако за всяка константа c > 0 съществува цяла константа N ≥ 0 такава, че f(n) ≥c g(n) за всяко nN.
Необходимо е 5N2cN - вземаме c, след това избираме N такова, че да изпълнява неравенството Nc/5 ≥ 0.