Принципи на анализа на алгоритми
Веселина Стоянова

1.     Илюстрация на процеса

2.     Описание на използваните математически понятия

3.     Сравняване на алгоритми

 

Емпиричен анализ

ü     Коректен и пълен код

ü     Входни данни – действителни, случайни, лоши данни

 

Защо анализираме алгоритмите математически?

ü     За да сравняваме различни алгоритми за една и съща задача

ü     За да предвиждаме производителността в нова среда

ü     За да задаваме стойности на параметрите на алгоритъма

 

Елементи на анализа на алгоритми

1.     Избор на абстрактни операции

2.     Моделиране на входа:

ü     Среден случай

ü     Най-лош случай

 

 

Нарастване на функции

Главен параметър N – пропорционален на размера на обработваните данни (степен на полином, размер на файл или масив, брой символи в низ и др.)

Най-често срещани функции при оценка времето на работа на алгоритъма:

ü     1 – времето за работа на алгоритъма е константа

ü     logN – логаритмично време при разделяне задачата на малки подзадачи

ü     N – линейно време – всеки входен елемент се обработва по малко

ü     NlogN – задачата се разделя на подзадачи, всяка от които се решава независимо, а после решението се обединява

ü     N2квадратично време – например обработване на всички двойки от данни (2 вложени цикъла)

ü     N3кубично време – аналогично с тройки от данни

ü     2Nекспоненциално време – решения с груба сила

 

секунди

 

102

1.7 мин.

104

2.8 часа

105

1.1 дни

106

1.6 седмици

107

3.8 месеца

108

3.1 години

109

3.1 десетилетия

1010

3.1 века

1011

Никога (3.1 хилядолетия)

 

Други функции:

ü     N3/2 - алгоритъм с вход N2 и време за изпълнение пропорционално на N3, се описва като алгоритъм от степен N3/2.

ü     Nlog2N – задачи с 2 етапа на разбиване на подзадачи

 

Ще означаваме двоичния логаритъм по следния начин: log2NºlgN

ëxû - закръгляне надолу

éхù - закръгляне нагоре

élgNù - брой на битовете в двоичното представяне на N

 - хармонични числа (дискретизирана версия на lnN)

 

Дефиниция 1. За функцията g(N) се казва, че е O(f(N)), ако съществуват константи c0 и N0 такива, че g(N)<c0f(N) за всяко N>N0.

Приложения на О:

ü     За ограничаване на грешката, която допускаме, пренебрегвайки малките членове във формулите

ü     За ограничаване на грешката, която допускаме, пренебрегвайки части от програмата, които имат малко значение за цялостния анализ

ü     За класифициране на алгоритмите по горната граница на времето им за работа

Пример:  (N+O(1)) (N+O(logN)+O(1))=

                   =N2+O(N)+O(N logN)+O(logN)+O(N)+O(1)»

                   » N2+O(N logN)асимптотичен израз (с 1 член О)

Пример: Даден е алгоритъм с 2 цикъла: вътрешен (изпълнява се средно по 2NHN пъти) и външен (изпълнява се N пъти) и инициализация (изпълнява се 1 път). Нека всяка итерация на вътрешния цикъл изисква а0 наносекунди, външния – а1 наносекунди, а инициализацията – а2 наносекунди. Тогава общо времето за работа е t=2a0NHN+a1N+a2= 2a0NHN+O(N)

HN=lnN+O(1)

t=2a0NlnN+O(N)

 

Основни рекурсии

1. CN=CN-1+N, за N³2 при С1=1 (програми, които циклят входа, за да премахнат 1 елемент)

CN=CN-1+N=

          =CN-2+(N-1)+N=

          = CN-3+(N-2)+(N-1)+N=

         

          =C1+2+…+(N-2)+ (N-1)+N=

          =1+2+…+(N-2)+ (N-1)+N=

         

 

2. CN=2CN/2+1, за N³2 при С1=1 (програми, които разполовяват входа на всяка стъпка)

CN=O(lgN). Предполагаме N=2n

         

         

         

          =n+1

 

3. CN=CN/2+N, за N³2 при С1=0 (програми, които разполовяват входа на всяка стъпка, но вероятно трябва да изследват всички елементи във входа)

CN=N+N/2+N/4+….=O(2N)

 

4. CN=2CN/2+N, за N³2 при С1=0 (програми, които обхождат линейно входа и го разполовяват на всяка стъпка), например алгоритми “разделяй и владей”

         

         

          =n

 

Примери за анализ на алгоритми:

 

1. Последователно търсене

int search (int a[], int v, int l, int r)

          { for (int i=l; i<=r; i++)

                   if (v==a[i]) return I;

           return –1;

          }

при неуспешно търсене – N елемента; при успешно – средно по N/2

 

2. Двоично търсене

int search (int a[], int v, int l, int r)

          { while (r>=l)

                   { int m=(l+r)/2;

                      if (v==a[m]) return m;

                      if (v<a[m]) r=m-1;

                             else l=m+1;

                   }

          return –1;

          }

изследват се не повече от ëlgNû+1 числа

 

3. Сортиране чрез селекция

const int size=100;

int min_position (int a[], int from, int to)

{ int min_pos=from;

   for (int i=from+1; i<=to; i++)

          if (a[i]<a[min_pos]) min_pos=i;

   return min_pos;

}

 

void selection_sort (int& a[])

{ for (int next=0; next<size; next++)

          { int min_pos=min_position (a, next, size-1);

             if (min_pos!=next)

                   { int temp=a[min_pos];

                      a[min_pos]=a[next];

                      a[next]=temp;

                   }

          }

}

 

Общ брой операции:

n+2+(n-1)+2+….+2+2=

          =n+(n-1)+…+2+(n-1)2=

          =-1+(n-1)2=

          ==O(n2)

 

4. Сортиране чрез сливане

void merge (int& a[], int from, int mid, int to)

{int n=to-from+1, b[size];

 int i1=from, i2=mid+1;

 int j=0;

while (i1<=mid and i2<=to)

          {if (a[i1]<a[i2])

                   {b[j]=a[i1];

                     i1++;

                   }

          else { b[j]=a[i2];

                   i2++;

                   }

while (i1<=mid)

          { b[j]=a[i1];

             i1++;

             j++;

          }

while (i2<=to)

          { b[j]=a[i2];

             i2++;

             j++;

          }

for (j=0, j<size; j++)

          a[from+j]=b[j];

}

void merge_sort (int& a[], int from, int to)

{ if (from==to) return;

   int mid=(from+to)/2;

   merge_sort(a, from, mid);

   merge_sort(a, mid+1, to);

   merge(a, from, mid, to);

}

Брой операции: O(n lgn)