cin >> n;
sum = 0;
for (i=0; i<n; i++)
for (j=0; j<n; j++) sum++;
Колко бързо ще работи
горната програма,
т.е. какви са критериите по-които се определя бързината й? Това, което
можем да направим експериментално е да проверим за колко време ще се
изпълни
и ще завърши работата си. За да изследваме по-общо нейното поведение е
възможно да я изпълним за други стойности на n. Резултатите от последното са обобщени в следната таблица: |
|
Сравняване на двете
функции f (n)= 2n2 и g(n)=
200n,
които показват времето за изпълнение на два дадени алгоритъма А1
и A2, в зависимост от n. Асимптотично алгоритъмът A2 е по-бърз и неговата сложност е линейна, докато тази на A1 е квадратична. |
|
Нека е дадена задача, в която размерът на входните данни е определен от дадено цяло число n. Почти всички задачи, които ще разглеждаме, притежават това свойство. Ще поясним последното като разгледаме няколко примера:
Пример 1.
Да се сортира масив с n елемента.
Размерът на входните данни се определя от броя
n
на
елементите на масива .
Пример 2.
Да се намери най-големият общ делител на a
и b.
В този пример размерът на входните данни се
определя
от броя на двоичните цифри (битовете) на по-голямото от числата a
и b.
Пример 3.
Да се намери покриващо дърво на граф.
В този случай характеризираме размера на входа
с две числа: брой на върховете и брой на ребрата.
Когато се интересуваме от сложността на алгоритъм най-често се интересуваме как ще работи при достатъчно голям размер n на входните данни. При формалното оценяване на сложността на алгоритмите изследваме поведението им при "достатъчно голямо" n, т.е. клонящо към безкрайност.
1. O(F) определя множеството от всички функции f, които нарастват не по-бързо от F, т.е. съществува константа c > 0 такава, че f (n) <= cF(n), за всички достатъчно големи стойности на n.
2. Q (F) определя множеството от всички функции f, които нарастват толкова бързо, колкото и F (с точност до константен множител), т.е. съществуват константи c1 > 0 и c2 > 0 такава, че c1F(n) <= f (n) <= c2F(n), за всички достатъчно големи стойности на n.
3. W (F) определя множеството от всички функции f, които нарастват не по-бавно от F, т.е. съществува константа c > 0 такава, че f (n) >= cF(n), за всички достатъчно големи стойности на n.
O(F): Свойства и примери
Нотацията О(F) е най-често
използваната
при оценка на сложност на алгоритми и програми.
По-важни свойства на O(F) (с ~ тук
означаваме принадлежност):
Нарастване на най-често използваните функции:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Определяне на сложност на алгоритъм:
- елементарна операция - не зависи от размера
на обработваните данни - O(1) ;
- последователност от оператори - определя се
от асимтотично най-бавния - f + g ~
max(O(
f
),
O(g));
- композиция на оператори - произведение от
сложностите - f g ~ O(
f g);
- условни оператори - определя се от асимтотично
най-бавния между условието и различните случаи;
- цикли, вложени цикли - O(n),
O(np) .
Примери:
/* Брой цифри на число */
// digits.cpp
#include <iostream>
using namespace std;
int main()
{
unsigned n;
cin >> n;
unsigned d, m = n;
for (d = 0; m > 0; d++, m /= 10);
cout << d;
return 0;
}
/* Брой цифри на число */
// ldigit.cpp
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
unsigned n;
cin >> n;
cout << int(1 + log10(n));
return 0;
}
Оценка на сложността на следните цикли (колко
пъти ще се изпълни цикъла в най-лошия случай):
// 1
for (i = 0; i < n; i++)
for (j = 0; j < n; j++, sum++);
// 2
for (i = 0; i < n; i++)
for (j = 0; j < n; j++) if
(a[i] == b[j])
return;
// 3
for (i = 0; i < n; i++)
for (j = 0; j < n; j++) if
(a[i] != b[j])
return;
// 4
for (i = 0; i < n; i++)
for (j = 0; j < n; j++) if
(a[i] == a[j])
return;
// 5
for (i = 0; i < n; i++)
for (j = 0; j < i; j++) sum++;
// 6
for (i = 0; i < n; i++)
for (j = 0; j < n*n; j++) sum++;
// 7
for (i = 0; i < n; i++)
for (j = 0; j < i*i; j++) sum++;
// 8
for (i = 0; i < n; i++)
for (j = 0; j < i*i; j++)
for (k = 0; k < j*j; k++)
sum++;
Да разгледаме цикъла:
for (sum = 0, i = 0; i < n; i *= 2)
sum++;
Променливата i приема стойности 1, 2, 4, ..., 2k,
... докато надмине n. Цикълът се изпълнява [log n]
пъти. Сложността е O(log n).
Изчисляване на
сложност при рекурсия.
Пример: Двоично търсене -
рекурсивен алгоритъм.
Броим обръщенията към елементите на масива. В
рекурсивната функция
се разглежда средния елемент и се прави едно рекурсивно извикване с два
пъти по-малък масив. Следователно, ако T(n) е функцията,
която задава броя на обръщенията, то T(n) = T(n/2)
+ 1. От равенствата
T(n) = T(n/2) + 1 = T(n/4) + 2
=
T(n/8)
+ 3 = ... = T(n/2k) + k
получаваме за n = 2k, че T(n)
= T(1) + log n, т.е. сложността на алгоритъма е O(log
n).