Принципы анализа эффективности нерекурсивных алгоритмов
Общий план анализа эффективности нерекурсивных алгоритмов:
1. Выбрать параметр (или параметры), по которому будет оцениваться размер входных данных алгоритма (например, число элементов массива).
2. Определить основную операцию алгоритма. (Как правило, она находится в наиболее глубоко вложенном внутреннем цикле алгоритма.) В ряде источников рекомендуется выбрать базовое множество операций (назовем их основными элементарными операциями, так будем полагать, что каждая из них выполняется за 1 машинный такт, то есть её трудоемкость равна 1), число выполнений которых и будет оцениваться функцией роста трудоемкости алгоритма. К ним отнесем операции
– присваивания;
– арифметические операции (+, –, *, /, div (деление нацело), mod (получение остатка от деления и т.д.);
– операции сравнения (>, <, =, <>, <=, >= );
– логические связки (операции) (and, or, xor, not);
– взятие (получение, использование) адреса ячейки памяти: [] – (в массиве), @ (в паскале – получение адреса), ^ – (в паскале – переход по адресу, * (разыменовывание в Си), & (передача адреса в Си);
– в методических целях также будем полагать, что все библиотечные функции (и процедуры) также имеют трудоемкость, равную 1, если иное особо не оговаривается.
3. Проверить зависимость числа выполняемых основных операций только от размера входных данных. Если оно зависит и от других факторов, рассмотреть при необходимости изменение эффективности алгоритма для наихудшего, среднего и наилучшего случаев.
4. Записать сумму, выражающую количество выполняемых основных операций алгоритма в последовательно исполняемых фрагментах программы.
5. Используя стандартные формулы и правила суммирования (приводимые ниже), упростить полученную формулу для количества основных/элементарных операций алгоритма. Таким образом, получаем функцию роста. Если это невозможно, следует определить, хотя бы, порядок ее роста.
Теоретическая оценка временной сложности алгоритма осуществляется с использованием следующих базовых принципов оценки сложности выполнения основных алгоритмических конструкций:
1. Время выполнения операторов присваивания, чтения и записи обычно имеет порядок О(1), как всякий линейный оператор, кроме случаев использования внутри них функций и процедур. В этом случае оператор имеет порядок выполнения функции или процедуры.
2. Время выполнения последовательности операторов определяется с помощью правила сумм. Поэтому степень роста времени выполнения последовательности операторов без определения констант пропорциональности совпадает с наибольшим временем выполнения оператора в данной последовательности.
3. Время выполнения условных операторов состоит из времени выполнения условно исполняемых операторов и времени вычисления самого логического выражения. Время вычисления логического выражения обычно имеет порядок О(1), если условие не содержит вызов функций, трудоемкость которых зависит от n – в этом случае проверка условия есть последовательное выполнение элементарных операций, в том числе и используемой в проверке условия функции (см. пункт 2). Время для всей конструкции if-then-else состоит из времени вычисления логического выражения и наибольшего из времени, необходимого для выполнения операторов, исполняемых при значении логического выражения true (истина) и при значении false (ложь).
4. Время выполнения цикла является суммой времени всех исполняемых итераций цикла, в свою очередь состоящих из времени выполнения операторов тела цикла и времени вычисления условия прекращения цикла (обычно последнее имеет порядок О(1), если не содержит внутри себя функцию от n). Часто время выполнения цикла вычисляется, пренебрегая определением констант пропорциональности, как произведение количества выполненных итераций цикла на наибольшее возможное время выполнения операторов тела цикла. Однако, не следует для циклов перемножать число выполняемых итераций на трудоемкость отдельной итерации, так как в этом случае зачастую не верно определяется количество исполняемых операций (вид функции роста), хотя асимптотическая оценка может оказаться верной. Кроме того, время выполнения каждого цикла, если в программе их несколько, должно определяться отдельно.
5. Для программ, содержащих несколько процедур (функций) (среди которых нет рекурсивных), можно подсчитать общее время выполнения программы путем последовательного нахождения времени выполнения процедур, начиная с той, которая не имеет вызовов других процедур. Так как мы предположили, что все процедуры нерекурсивные, то должна существовать хотя бы одна процедура, не имеющая вызовов других процедур. Затем можно определить время выполнения процедур, вызывающих эту процедуру, используя уже вычисленное время выполнения вызываемой процедуры. Продолжая этот процесс, найдем время выполнения всех процедур и, наконец, время выполнения всей программы.
6. Если есть рекурсивные процедуры, то нельзя упорядочить все процедуры таким образом, чтобы каждая процедура вызывала только процедуры, время выполнения которых подсчитано на предыдущем шаге. В этом случае мы должны с каждой рекурсивной процедурой связать временную- функцию Т(п), где п определяет объем аргументов процедуры. Затем мы должны получить рекуррентное соотношение для Т(п), т.е. уравнение (или неравенство) для Т(п), где участвуют значения T(k)для различных значений k, то есть Т(п) = F (T(k1), T(k2), …), ki < n. После чего Т(п) выражается как функция только от n.
7. Программы с операторами безусловного перехода.При анализе времени выполнения программ мы неявно предполагали, что все ветвления в ходе выполнения процедур осуществлялись с помощью условных операторов и операторов циклов. Мы останавливаемся на этом факте, так как определяли время выполнения больших групп операторов путем применения правила сумм к этом группам. Однако операторы безусловного перехода (такие как goto, досрочный выход из цикла) могут порождать более сложную логическую групповую структуру. Безусловный переход, как правило, либо перемещает выполнение к следующему по порядку следования операторам (например, досрочное завершение цикла и переход к следующему за циклом оператору), либо возвращает к предыдущим, создавая петлю, либо вклинивается в середину группы операторов, запутывая последовательность операторов. Тогда оценка времени выполнения оператора безусловного перехода зависит от способа интерпретации логической структуры группы операторов:
А) Досрочный выход из цикла обычно связан с некоторым условием, тогда оценка определяется по более «длинной» ветви if/then/else, т.е. по полному выполнению цикла, что значительно ухудшает оценку, если существует большая вероятность досрочного завершения цикла.
Б) Возврат к предыдущему фрагменту порождает петлю, которая может быть рассмотрена как новая линейная последовательность операторов. Многократное выполнение петли сводится к циклам или рекурентному выполнению вложенных петель.
В) Отсутствие общего подхода для оценки времени выполнения программы, использующей не приводимое каноническому виду уонструкции с использованием оператора GOTO зачастую исключает возможность оценки времени выполнения такого фрагмента.
В принципе, операторы безусловного перехода можно исключить из программы, заменив их на канонические конструкции структурного программирования.
Дата добавления: 2022-02-05; просмотров: 250;