- •Структуры и алгоритмы обработки данных
- •Алгоритмы. Основные определения
- •От задачи к программе
- •Написание программы.
- •Типы данных, структуры данных и абстрактные типы данных
- •Указатели и курсоры
- •Время выполнения программ
- •Измерение времени выполнения программы
- •Степень (порядок) роста
- •Вычисление времени выполнения программ
- •Линейные абстрактные типы данных атд «Список»
- •Реализация списков посредством массивов
- •Реализация списков с помощью указателей
- •Атд «Стек»
- •Атд «Очередь»
- •Реализация очередей с помощью циклических массивов
- •Нелинейные абстрактные типы данных Деревья
- •Порядок узлов
- •Прямой, обратный и симметричный обходы дерева
- •Помеченные деревья и деревья выражений
- •Представление деревьев с помощью массивов
- •Специальные виды множеств атд “Дерево двоичного поиска”
- •Атд "Словарь", основанный
- •Представление ориентированных графов
- •Атд для ориентированных графов
- •Задача нахождения кратчайшего пути
- •Int **c, // массив стоимостей
- •Int *d) // массив кратчайших
- •Остовные деревья минимальной стоимости
- •Обход графов
- •Поиск в ширину
- •Поиск в глубину
- •Сортировка
- •Простые схемы сортировки Метод «пузырька»
- •Сортировка вставками
- •Быстрая сортировка
- •Пирамидальная сортировка
- •«Карманная» сортировка
- •Порядковые статистики
- •Методы разработки алгоритмов. Типы алгоритмов
- •Алгоритмы «разделяй и властвуй» (метод декомпозиции)
- •Баланс подзадач
- •Динамическое программирование
- •Перемножение нескольких матриц
- •Шаг 1: строение оптимальной расстановки скобок
- •Шаг 2: рекуррентное соотношение
- •Шаг 3: вычисление оптимальной стоимости
- •Void MatrixChainOrder(int n, // кол-во матриц
- •Int p[], // размеры матриц
- •Int **s) // оптимальное k
- •Шаг 4: построение оптимального решения
- •Int **s, // таблица, полученная
- •Int I, // индексы
- •Когда применимо динамическое программирование?
- •Оптимальность для подзадач
- •Перекрывающиеся подзадачи
- •«Жадные» алгоритмы
- •"Жадные" алгоритмы как эвристики
- •Когда применим жадный алгоритм?
- •Принцип жадного выбора
- •Оптимальность для подзадач
- •Поиск с возвратом
- •Функции выигрыша
- •Метод ветвей и границ
- •Структуры данных и алгоритмы для внешней памяти
- •Внешняя сортировка
- •Хранение данных в файлах
- •Простая организация данных
- •Хешированные файлы
- •Индексированные файлы
- •Содержание
- •Глава I. Линейные абстрактные типы данных 31
- •Глава II. Сортировка 108
Вычисление времени выполнения программ
На практике для определения времени выполнения программы пользуются несколькими базовыми принципами:
-
Время выполнения операторов присваивания, чтения и записи обычно имеет порядок О(1). Исключение составляют операторы присваивания с вызовом функции в правой части. Y=fact(x).
-
Время выполнения последовательности операторов определяется с помощью правила сумм. Степень роста времени выполнения последовательности операторов без определения констант пропорциональности совпадает с наибольшим временем выполнения оператора в данной последовательности.
Пусть T1(n) и T2(n) – время выполнения двух программных фрагментов Р1 и Р2, T1(n) имеет степень роста O(f(n)), а T2(n) – O(g(n)). Тогда T1(n)+ T2(n), т.е. время последовательного выполнения фрагментов Р1 и Р2 , имеет степень роста O(max(f(n),g(n))).
-
Время выполнения условного оператора (всей конструкции if-then-else) состоит из времени вычисления логического выражения и наибольшего из времени, необходимого для выполнения операторов при значении логического выражения true и false.
-
Время выполнения цикла является суммой времени всех исполняемых итераций цикла. Время выполнения каждой итерации цикла состоит из времени выполнения операторов тела цикла и времени вычисления условия прекращения цикла. Часто время выполнения цикла вычисляется, пренебрегая определением констант пропорциональности, по правилу произведений как произведение количества выполненных итераций цикла на наибольшее возможное время выполнения операторов тела цикла.
-
Для программ, содержащих вызовы функций (среди которых нет рекурсивных), общее время выполнения программы находится путем последовательного нахождения времени выполнения функций, начиная с той, которая не имеет вызовов других функций. Затем можно определить время выполнения функций, вызывающих эту функцию и действуя таким образом мы найдем время выполнения всей программы.
-
Для подсчета времени T(n) выполнения рекурсивной процедуры выводится рекуррентное соотношение (уравнение или неравенство) для T(n), где в правой части соотношения присутствуют значения T(k), k=1,…,n .
Анализ сложных рекурсивных программ мы рассмматривать не будем. Проанализируем простую рекурсивную программу.
Программа вычисления факториала.
-
(1)
(2)
(3)
long int fact(int n)
{
if (n<=1)
return 1;
else
return ( n * fact(n-1) );
}
На практике эту программу нельзя использовать для вычисления факториала при больших значениях n, так как размер получаемых в процессе вычисления целых чисел может превышать длину машинного слова.
Обозначим через T(n) время выполнения программы с объемом входных данных n. Время выполнения строк (1) и (2) имеет порядок О(1), а для строки (3) – О(1)+Т(n-1). Таким образом для некоторых констант с1 и с2 имеем
Полагая, что n>2 получим T(n)=c1+T(n-1)=c1+(c1+T(n-2))=2c1+T(n-2).
Полагая, что n>3 получим T(n)=c1+T(n-1)=c1+(c1+T(n-2))=2c1+T(n-2) =
2c1+(c1+T(n-3))=3c1+T(n-3).
По индукции для n>i получим T(n)=i*c1+T(n-i), полагая i=n-1 получим
T(n)=(n-1)*c1+T(1)=(n-1)*c1+c2.
Отсюда следует, что T(n) имеет степень роста О(n).
Общий метод решения рекуррентных соотношений состоит в последовательном раскрытии T(k) в правой части до тех пор, пока не получится формула, у которой в правой части отсутствует Т. При этом часто приходится находить суммы последовательностей. Если значения таких сумм нельзя вычислить точно, то для сумм находятся верхние границы, что позволяет получить верхние границы для T(n).
-
При анализе времени выполнения программ мы неявно предполагали, что все ветвления в ходе выполнения процедур осуществлялись с помощью условных операторов и операторов циклов. Однако операторы безусловного перехода (goto) могут порождать более сложную логическую групповую структуру. В принципе, их можно исключить из программы.
В случае использования оператора безусловного перехода для досрочного окончания цикла, предполагается, что досрочное окончание цикла никогда не выполняется, т.е. мы предполагаем полное выполнение цикла. Для программ, где досрочный выход из цикла не исключение, а правило, такой консервативный подход значительно переоценивает степень роста времени выполнения в наихудшем случае.
Если же переход осуществляется к ранее выполненным операторам, то в этом случае вообще нельзя игнорировать оператор безусловного перехода, поскольку такой оператор создает петлю в выполнении программы, что приводит к нарастанию времени выполнения всей программы. В этом случае необходимо анализировать структуру самой петли.