
Вычисление времени выполнения программ
Лемма 1. (правило сумм) Пусть T1(n) и T2(n) – время выполнения двух программных фрагментов P1 и P2, T1(n) имеет степень роста O(f(n)), T2(n) имеет степень роста O(g(n)). Тогда T1(n)+T2(n), т.е. время последовательного выполнения фрагментов P1 и P2 имеет степень роста O(max(f(n),g(n)).
Доказательство.
По условию T1(n)
имеет степень роста O(f(n)),
что по определению означает существование
констант c1 и n1
таких, что при nn1
выполняется неравенство:
.
T2(n)
имеет степень роста O(g(n)),
что по определению означает существование
констант c2 и n2
таких, что при nn2
выполняется неравенство:
.
Положим n0=max(n1,n2). Тогда для nn0 выполняется неравенство:
,
что по определению и означает верность данной леммы.
Следствие. Если g(n)f(n) для всех nn0, то выражение O(f(n)+g(n)) эквивалентно O(f(n)). Например, O(n2+n) эквивалентно O(n2).
Итак, правило сумм используется для вычисления времени последовательного выполнения программных фрагментов. В общем случае, время выполнения конечной последовательности программных фрагментов, без учета констант, имеет порядок фрагмента с наибольшим временем выполнения.
Лемма 2 (правило произведений). Пусть T1(n) имеет степень роста O(f(n)), T2(n) имеет степень роста O(g(n)). Тогда T1(n)T2(n) имеет степень роста O(f(n)g(n)).
Доказательство.
По условию T1(n) имеет степень роста O(f(n)), что по определению означает существование констант c1 и n1 таких, что при nn1 выполняется неравенство: . T2(n) имеет степень роста O(g(n)), что по определению означает существование констант c2 и n2 таких, что при nn2 выполняется неравенство: .
Положим n0=max(n1,n2). Тогда для nn0 выполняется неравенство:
,
что по определению и означает верность данной леммы.
Следствие. Если c – положительная константа, то O(cf(n)) эквивалентно O(f(n)). Например, O(n2/2) эквивалентно O(n2).
Правила анализа программ.
Замечание. Вообще говоря, нахождение верхней границы времени выполнения программ зачастую является нелегкой задачей. Поэтому не существует исчерпывающего множества правил анализа программ. Можно сформулировать только некоторые из них.
1. Время выполнения операторов присваивания, чтения и записи обычно имеет порядок О(1). Есть несколько исключений из этого правила, например, в языках, где можно присваивать большие массивы или в случае вызова функций в операторах присваивания.
2. Время выполнения последовательности операторов определяется с помощью правила сумм. Поэтому степень роста времени выполнения последовательности операторов без определения констант пропорциональности совпадает с наибольшим временем выполнения оператора в данной последовательности.
3. Время выполнения условных операторов состоит из времени выполнения условно исполняемых операторов и времени вычисления самого логического выражения. Время вычисления логического выражения обычно имеет порядок О(1). Время для всей конструкции if-then-else состоит из времени вычисления логического выражения и наибольшего из времени, необходимого для выполнения операторов, исполняемых при значении логического выражения true (истина) и при значении false (ложь).
4. Время выполнения цикла является суммой времени всех исполняемых итераций цикла, в свою очередь состоящих из времени выполнения операторов тела цикла и времени вычисления условия прекращения цикла (обычно последнее имеет порядок О(1)). Часто время выполнения цикла вычисляется, пренебрегая определением констант пропорциональности, как произведение количества выполненных итераций цикла на наибольшее возможное время выполнения операторов тела цикла. Если в программе несколько циклов, то время выполнения каждого из них определяется отдельно.
Пример.
Рассмотрим процедуру, которая упорядочивает массив целых чисел в возрастающем порядке методом «пузырька».
procedure bubble(var A:array[1..n] of integer);
var i,j,temp:integer;
begin
(1) for i:=1 to n-1 do
(2) for j:=n downto i+1 do
(3) if A[j-1]>A[j] then
begin
(4) temp:=A[j-1];
(5) A[j-1]:=A[j];
(6) A[j]:=temp;
end
end;
Мерой объема входных данных может служить n – число элементов, подлежащих сортировке. Все операторы присваивания имеют некоторое постоянное время выполнения, независящее от размера входных данных. Таким образом, время выполнения операторов (4)-(6) имеет порядок О(1). В соответствии с правилом сумм время выполнения этого программного фрагмента равно O(max(1,1,1))=O(1).
Операторы if и for вложены друг в друга, поэтому, чтобы подсчитать время выполнения, необходимо идти от внутренних операторов к внешним. Для оператора if проверка логического выражения занимает время порядка О(1). Мы не знаем, будут ли выполняться операторы в теле условного оператора (строки (4)-(6)), но поскольку мы ищем наихудшее время выполнения, то предполагаем, что они выполняются. Таким образом, время выполнения группы операторов (3)-(6) имеет порядок О(1).
Далее рассмотрим группу операторов (2)-(6) внутреннего цикла. Как уже говорилось, вычисление времени выполнения цикла заключается в суммировании времени выполнения каждой итерации цикла. Для операторов (2)-(6) время выполнения на каждой итерации имеет порядок О(1). Цикл выполняется n-i раз, поэтому, по правилу произведений, общее время выполнения цикла имеет порядок О((n-i)1), что равно O(n-i).
Далее перейдем к внешнему циклу, который содержит все исполняемые операторы программы. Оператор (1) выполняется n-1 раз, поэтому суммарное время выполнения программы ограничено сверху выражением
,
которое имеет порядок O(n2).
Таким образом, данная процедура имеет время выполнения, пропорциональное квадрату числа элементов, подлежащих упорядочиванию.
5. Для программ, содержащих несколько нерекурсивных процедур, можно подсчитать общее время выполнения программы путем последовательного нахождения времени выполнения процедур, начиная с той, которая не имеет вызовов других процедур. (Так как мы предположили, что все процедуры нерекурсивные, то должна существовать хотя бы одна такая процедура). Затем можно определить время выполнения процедур, вызывающих эту процедуру, используя уже вычисленное время выполнения вызываемой процедуры. Продолжая этот процесс, найдем время выполнения всех процедур.