- •1. Вычислительные модели. Показатели эффективности алгоритмов, задача анализа алгоритмов. Время выполнения алгоритма для худшего и среднего случая. Вычислительные модели
- •Задача анализа алгоритмов
- •Время работы алгоритма
- •Время выполнения в худшем и среднем случае
- •5. Анализ рекурсивных алгоритмов. Анализ вставки элемента в бинарное дерево поиска.
- •5.Анализ сложности рекурсивных алгоритмов
- •6. Связные списки: однонаправленные, двунаправленные, кольцевые. Примеры реализации.
- •Однонаправленные (односвязные) списки
- •Двунаправленные (двусвязные) списки
- •7. Структура данных "стек". Реализация с помощью массива и связного списка, смешанная реализация. Пример использования (кроме задачи о скобках)
- •8.Структура данных "очередь". Реализация с помощью массива, циклического массива, односвязного списка.
- •Очереди
- •Бинарные деревья
- •11. Обходы бинарных деревьев (клп - "Корень-Левое-Правое" ,лкп,лпк) с примерами программной реализации.
- •12. Удаление элемента из бинарного дерева поиска.
- •13.Рандомизированные деревья. Вставка элемента в рандомизированное бинарное дерево поиска.
- •Включение элемента в рандомизированное дерево
- •Рекурсивный алгоритм вставки в рандомизированное дерево:
- •14.Быстрая сортировка Хоара (QuickSort)
- •Краткое описание алгоритма
- •Алгоритм
- •Оценка эффективности
- •15. Сортировка слиянием (MergeSort)
- •16.Полный перебор подмножеств
- •17. Полный перебор перестановок
- •18. Перебор с возвратом и отсечением вариантов
- •3.1 Использование рекурсии для записи алгоритма
- •3.2 Примеры решения задач при помощи перебора с возвратом
- •.1 Отсечение лишних вариантов
- •19. Динамическое программирование. Разбиение задачи на подзадачи, принцип оптимальности. Задача о маршруте из верхнего угла таблицы в нижний
- •20. Пример использования динамического программирования для подсчета количеств комбинаторных объектов - найти количество разбиений числа на слагаемые
- •21. Жадные алгоритмы. Принцип жадного выбора, свойство оптимальности для подзадач, схема доказательства корректности жадного алгоритма. Задача о заявках.
- •Принцип жадного выбора
- •Оптимальность для подзадач
- •Примеры Размен монет
- •Выбор заявок
- •22. Хеширование с цепочками. Эффективность хеширования с цепочками. Хеш-функции, качество хеш-функций, подходы к построению хеш-функций.
- •23. Хеширование с открытой адресацией. Способы разрешения коллизий. Анализ эффективности.
19. Динамическое программирование. Разбиение задачи на подзадачи, принцип оптимальности. Задача о маршруте из верхнего угла таблицы в нижний
Динамическое программирование в теории управления и теории вычислительных систем — способ решения сложных задач путём разбиения их на более простые подзадачи. Он применим к задачам с оптимальной подструктурой (англ.), выглядящим как набор перекрывающихся подзадач, сложность которых чуть меньше исходной. В этом случае время вычислений, по сравнению с «наивными» методами, можно значительно сократить.
Ключевая идея в динамическом программировании достаточно проста. Как правило, чтобы решить поставленную задачу, требуется решить отдельные части задачи (подзадачи), после чего объединить решения подзадач в одно общее решение. Часто многие из этих подзадач одинаковы. Подход динамического программирования состоит в том, чтобы решить каждую подзадачу только один раз, сократив тем самым количество вычислений. Это особенно полезно в случаях, когда число повторяющихся подзадач экспоненциально велико.
Метод динамического программирования сверху — это простое запоминание результатов решения тех подзадач, которые могут повторно встретиться в дальнейшем. Динамическое программирование снизу включает в себя переформулирование сложной задачи в виде рекурсивной последовательности более простых подзадач.
В типичном случае динамическое программирование применяется к задачам оптимизации, которые не решаются простым перебором (из-за временных ограничений или слишком большого объема необходимой памяти) и жадными алгоритмами (из-за того, что они не дают оптимального результата), а более простой путь решения если и существует, то его нахождение неочевидно. У таких задач может быть много решений и в общем случае это определяет некий показатель (назовем его качеством решения), и требуется выбрать оптимальное, при котором данный показатель становится либо минимумом либо максимумом (или еще чем-то :) - в зависимости от условия задачи). По своему принципу динамическое программирование напоминает метод разделяй и властвуй задача делится на подзадачи, которые либо являются очевидными, либо сводятся к своим подзадачам и т.д. Но есть и некоторые отличия например, таких подзадач обычно относительно немного, что позволяет решить каждую только один раз, а результат (то самое качество) занести в некий массив такой подход называется memoization (не бейте меня, это не я такое придумал! :))). Небольшое кол-во подзадач, многие из которых приходится решать много раз называется перекрытием подзадач и является характерным признаком динамического программирования. Вторым характерным признаком излагаемого метода является оптимальность для подзадач. Говорят, что задача обладает таким свойством, если оптимальное решение задачи содержит оптимальные решения ее подзадач. Общий рецепт построения алгоритмов по принципу динамического программирования следующий:
Хорошо понять условие :)
Найти такое разбиение задачи на две или более подзадач, чтобы оптимальное решение задачи содержало оптимальное решение всех подзадач, которые в нее входят.
Написать рекуррентное соотношение (если мы разбиваем задачу на подзадачи, значит, можем выразить качество решения задачи либо через ее подзадачи, либо элементарным образом)
Вычислить оптимальное значение параметра для всей задачи. Тут возможно два варианта если задачу решаем рекурсивно, значит процедура делит заданную ей задачу на подзадачи (предварительно выяснив, не является ли задача тривиальной и проверив, не решалась ли она ранее тогда нужно лишь взять уже готовое решение из соответствующего массива) и получив решение ставит флаг, что эта подзадача уже решена. Такое решение называется решением сверху вниз т.е. берем глобальную задачу, потом решаем только необходимые для нее подзадачи. Если же рекурсия невозможна по техническим или еще каким-нить причинам (может просто не любит какой программер рекурсию :)), то применяется такой подход: решаются сначала элементарные подзадачи, потом только те, которые требуют результатов уже решенных подзадач и т. д., пока не будет решена общая задача. Такой метод называется решением снизу вверх в большинстве случаев он оказывается быстрее, но менее понятным и простым, хотя есть и исключения
Если необходимо получить не только значение качества оптимального решения, но и найти само решение, то на шаге 3 нужно также запоминать некоторую дополнительную информацию о ходе решения решение каждой подзадачи. Этот шаг иногда еще называют обратным ходом
Var fc: array [1..Nmax, 1.. Kmax] of integer; //перед вызовом f, f c-1;
Function f(n,k:integer):integer;
Var
i:integer; res:integer;
begin
if k<=0 then begin
f:=0
exit;
end;
if N=1 then begin
if (k>0) and (k<=9) then
f:=1
else
f:=0;
exit;
end;
if fc[N,K]<0 then begin
res:=0;
for i:=0 to 9 do
begin
res:=res+f(n-1;k-i);
end;
f:=res;
end;
fc[N,K]:res
end;
f:=fc[N,K]
end;