- •Содержание
- •Введение.
- •1. Основы анализа алгоритмов
- •1.1. Сравнительные оценки алгоритмов
- •1.2. Элементарные операции в формальной системе
- •1.3. Классы входных данных
- •1.4. Классификация алгоритмов по виду функции трудоемкости
- •1.5. Классификация скоростей роста. Асимптотический анализ функций
- •1. Оценка (тета)
- •2. Оценка о (о большое)
- •3. Оценка (Омега)
- •1.6. Эффективность рекурсивных алгоритмов
- •1.7. Анализ программ
- •1.8. Вопросы для самоконтроля
- •Классификация алгоритмов по виду функции трудоемкости.
- •2. Алгоритмы поиска и выборки
- •2.1. Последовательный поиск
- •2.2. Двоичный поиск
- •2.3. Задача выборки
- •2.4. Вопросы для самоконтроля
- •3.Алгоритмы сортировки
- •3.1. Сортировка трех чисел по месту
- •3.2. Сортировка вставками
- •3.3. Пузырьковая сортировка
- •3.4. Сортировка Шелла.
- •3.5. Корневая сортировка
- •3.6. Сортировка методом индексов
- •3.7. Быстрая сортировка (алгоритм Хоара)
- •3.8. Вопросы для самоконтроля
- •Сортировка Шелла.
- •Сортировка методом индексов.
- •4. Алгоритмы на графах
- •4.1. Основные понятия теории графов
- •4.2. Структуры данных для представления графов
- •4.3. Алгоритмы обхода вершин графа
- •4.3.1. Обход в глубину
- •4.3.2. Обход в ширину
- •4.4. Поиск остовного дерева минимального веса
- •4.4.1. Алгоритм Дейкстры – Прима
- •4.4.2. Алгоритм Крускала
- •4.5. Алгоритм поиска кратчайшего пути
- •4.6. Вопросы для самоконтроля
- •Структуры данных для представления графов.
- •5. Численные методы
- •5.1. Вычисление значений многочленов
- •5.2. Умножение матриц
- •5.2.1 Стандартный алгоритм умножения матриц
- •5.2.2. Умножение матриц по Винограду
- •5.2.3. Умножение матриц по Штрассену
- •5.3. Вопросы для самоконтроля
- •Стандартный алгоритм умножения матриц.
- •6. Алгоритмы сравнения с образцами
- •6.1. Сравнение строк
- •6.2. Алгоритм Кнута – Морриса – Пратта
- •6.3. Алгоритм Бойера - Мура
- •6.4. Вопросы для самоконтроля
- •7. Вычислительная геометрия
- •7.1. Основные понятия
- •7.2. Векторное произведение векторов
- •7.2.1. Ориентированная площадь треугольника
- •7 .3. Задача о выпуклой оболочке
- •7.3.1. Алгоритм Грэхема
- •7.3.2. Алгоритм Джарвиса
- •7.3.3. Рекурсивный алгоритм
- •7.4. Вопросы для самоконтроля
- •8. Задачи класса np
- •8.1. Примеры np-полных задач
- •8.1.1. Задача о коммивояжере
- •8.1.2. Задача о раскраске графа
- •8.1.3. Раскладка по ящикам
- •8.1.4 Упаковка рюкзака
- •8.1.5. Задача о суммах элементов подмножества
- •8.1.6. Задача о планировании работ
- •8.2. Приближенные эвристические решения nр-полных задач.
- •8.2.1. Жадные приближенные алгоритмы
- •8.2.2. Приближения в задаче коммивояжера
- •8.2.3. Приближения в задаче о раскладке по ящикам
- •8.2.4. Приближения в задаче об упаковке рюкзака
- •8.3. Вопросы для самопроверки
- •Приближения в задаче об упаковке рюкзака.
- •9. Динамическое программирование
- •Часть1--------------------
- •10. Метод ветвей и границ
- •Вопросы к зачету
- •Классификация алгоритмов по виду функции трудоемкости.
- •Приближения в задаче об упаковке рюкзака.
- •Динамическое программирование
- •Метод ветвей и границ. Литература
1. Оценка (тета)
Пусть f(n) и g(n) – положительные функции положительного аргумента, n ≥ 1 (количество объектов на входе и количество операций – положительные числа), тогда:
f(n) = (g(n)), если существуют положительные с1, с2, n0, такие, что: с1 * g(n) f(n) c2 * g(n), при n > n0.
Обычно говорят, что при этом функция g(n) является асимптотически точной оценкой функции f(n), т.к. по определению функция f(n) не отличается от функции g(n) с точностью до постоянного множителя.
Отметим, что из f(n) = (g(n)) следует, что g(n) = (f(n)).
Примеры:
1) f(n)=4n2+nlnN+174= (n2);
2) f(n)=(1) – запись означает, что f(n) или равна константе, не равной нулю, или f(n) ограничена константой на : f(n) = 7+1/n = (1).
2. Оценка о (о большое)
В отличие от оценки , оценка О требует только, что бы функция f(n) не превышала g(n) начиная с n > n0, с точностью до постоянного множителя:
c > 0, n0 > 0 :
0 f(n) c * g(n), n n0
В ообще, запись O(g(n)) обозначает класс функций, таких, что все они растут не быстрее, чем функция g(n) с точностью до постоянного множителя, поэтому иногда говорят, что g(n) мажорирует функцию f(n).
Например, для всех функций:
f(n)=1/n, f(n)= 12, f(n)=3*n+17, f(n)=n*Ln(n), f(n)=6*n2 +24*n+77
будет справедлива оценка О(n2 ).
Указывая оценку О, есть смысл указывать наиболее «близкую» мажорирующую функцию, поскольку например для f(n)= n2 справедлива оценка О(2n), однако она не имеет практического смысла.
3. Оценка (Омега)
В отличие от оценки О, оценка является оценкой снизу – т.е. определяет класс функций, которые растут не медленнее, чем g(n) с точностью до постоянного множителя:
c > 0, n0 >0 :
0 c * g(n) f(n)
Например, запись (n*ln(n)) обозначает класс функций, которые растут не медленнее, чем g(n) = n*ln(n). В этот класс попадают все полиномы со степенью большей единицы, равно как и все степенные функции с основанием большим единицы.
Асимптотическое обозначение О восходит к учебнику Бахмана по теории простых чисел (Bachman, 1892), обозначения , введены Д. Кнутом- (Donald Knuth) [1].
Отметим, что не всегда для пары функций справедливо одно из асимптотических соотношений, например для f(n)=n1+sin(n) и g(n)=n не выполняется ни одно из асимптотических соотношений.
В асимптотическом анализе алгоритмов разработаны специальные методы получения асимптотических оценок, особенно для класса рекурсивных алгоритмов. Очевидно, что оценка является более предпочтительной, чем оценка О. Знание асимптотики поведения функции трудоемкости алгоритма - его сложности, дает возможность делать прогнозы по выбору более рационального с точки зрения трудоемкости алгоритма для больших размерностей исходных данных.
1.6. Эффективность рекурсивных алгоритмов
Под рекурсией понимается метод определения функции через её предыдущие и ранее определенные значения, а так же способ организации вычислений, при котором функция вызывает сама себя с другим аргументом.
Большинство современных языков высокого уровня поддерживают механизм рекурсивного вызова, когда функция, как элемент структуры языка программирования, возвращающая вычисленное значение по своему имени, может вызывать сама себя с другим аргументом. Эта возможность позволяет напрямую реализовывать вычисление рекурсивно определенных функций.
Анализ трудоемкости рекурсивных реализаций алгоритмов, очевидно, связан как с количеством операций, выполняемых при одном вызове функции, так и с количеством таких вызовов (рекурсий).
Подсчет итераций рекурсивных алгоритмов не очень прост, так как число итераций зависит от внешних параметров. Обычно неочевидно, сколько раз выполнится та или иная функция при рекурсивных вызовах. Но большинство рекурсивных алгоритмов содержит следующие типичные фрагменты:
- разбиение ввода на более мелкие части;
- рекурсивный вызов для обработки полученных частей;
- объединение решений.
Например, рекурсивная функция вычисления факториала
Factorial (n)
// n - число, факториал которого нам нужен;
// Factorial возвращает целое число
if n=1 then
return (1)
else
small n-1
answ Factorial (small)
return (n*answ)
endif
end
Предельный размер данных n=1, и в этом случае никаких арифметических операций не выполняется.
Во всех остальных случаях (else):
1 шаг – разбиение ввода на более мелкие части – вычисление переменной small, требуется одно вычитание;
2 шаг – рекурсивный вызов процедуры обработки более мелких данных и размер задачи – на 1 меньше предыдущего;
3 шаг – объединение решений – это умножение в последнем операторе return.
Д ля определения сложности рекурсивного алгоритма (РА) используют следующую рекуррентную формулу:
где
fА НР (N) – сложность непосредственного решения для предельного значения N;
fА РВ (N) – сложность разбиения ввода (N) на некоторое число малых частей;
fА i (i) сложность алгоритма для соответствующего разбиения i;
fА ОР (N) – сложность объединения решений.
В общую формулу необходимо подставить известные сложности каждого шага. Получаем рекуррентное равенство, так как значение функции выражено в терминах самого себя. Как исключать рекурсию из такого равенства?
Приведение к замкнутому виду выполняется посредством последовательных подстановок, позволяющих уловить общий принцип. Например, для вышеприведенной функции вычисления факториала Factorial:
fА НР (1) = 0 для n=1
fА РВ (n) = 2 (вычисление переменной small)
fА ОР (n) = 1 (умножение в операторе return)
Таким образом, fА i (n) = 0 для n=1, и fА i (n) = 2+ fА i (n-1)+1=3+ fА i (n-1) для n>1.
1 шаг. Исключение n (спуск вниз)
fА (n – 1) = 3+ fА (n – 2)
fА (n – 2) = 3 + fА (n – 3)
fА (n – 3) = 3 + fА (n – 4) ….
2 шаг. Подставим результаты вычислений обратно в исходное уравнение. При этом результат не будем упрощать до конца, чтобы не упустить общую схему.
fА (n) = 3 + fА (n – 1) = 3 + 3 + fА (n – 2) = 3 + 3 + 3 + fА (n – 3) = 3 + 3 + 3 + 3 + + fА (n – 4) …
До каких пор будем уменьшать аргумент у fА? Очевидно, до fА(1) = 0. Значит, число шагов равно (n – 1), следовательно:
fА (n) = 3 + 3 + 3 + … + 3 + fА (n – (n – 1)) = (n – 1) 3 = 3 (n – 1)
Оценка рекуррентного алгоритма вычисления факториала 3(n – 1).
Задание. Привести рекуррентное соотношение к замкнутому виду
fА (1 ) = 8 ; fА (n ) = 3 fА ( n – 1 ) – 15 при n>1;