- •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. Хеширование с открытой адресацией. Способы разрешения коллизий. Анализ эффективности.
5. Анализ рекурсивных алгоритмов. Анализ вставки элемента в бинарное дерево поиска.
5.Анализ сложности рекурсивных алгоритмов
Одним из основных методов построения рекурсивных алгоритмов является метод декомпозиции. Идея метода состоит в разделении задачи на части меньшей размерности, получении решений для выделенных частей и объединении решений при возврате рекурсивных вызовов. Если в алгоритме происходит разделение задачи на b подзадач, которое приводит к необходимости решения а подзадач размерностью п/Ь, то функцию трудоемкости (см. [3]) можно представить в виде FA (n)=aF A (n/b) + d(n) + U (п),где d(n) — трудоемкость алгоритма деления задачи на подзадачи, U(n) — трудоемкость алгоритма объединения полученных решений.Рассмотрим, например, известный алгоритм сортировки слиянием, принадлежащий Дж. Фон Нейману (см. 3.6. основного текста). На каждом рекурсивном вызове переданный массив делится пополам, что дает оценку для d(n) — 0(1), далее рекурсивно вызывается сортировка полученных массивов половинной длины (до тех пор,пока длина массива не станет равной единице), и возвращенные отсортированные массивы объединяются с трудоемкостью 0(п). Тогда ожидаемая трудоемкость на сортировку составит: FА (п) = 2 •FA(п/2) + 0 (1) + 0 (п). Тем самым возникает вопрос о получении оценки сложности функции трудоемкости, заданной в виде (Д.2.4), для произвольных целых значений а и Ь. Ответ на этот вопрос можно получить на основе теоремы о рекуррентных соотношениях, авторами которой являются Дж. Бентли, Д.Хакен и Дж. Сакс (статья 1980 г.), приведем ее формулировку в соответствии с [3]. Теорема Д.2.1. Пусть а > 1, b > 1 — константы, д(п) — функция, пусть далее
/ (п) = а • f (n/b) + д (п), где п/Ь = [п/Ъ] или п/Ь — [п/Ь], тогда
(1) Если д(п) = О (n^ log b (a-£)), £ > 0, то f(п) = 0 (n log b (a) ).
Пример: / (п) = 8 • f (га/2) + п 2 , тогда / (п) = 0 (п 3 ).
(2) Если g(n)=Q (n^ log b a ), то / (n) = 0 (n^ log b a • log n).
Пример: / (n) = 2 • / (n/2) + 0 (n), тогда / (n) = 0 (n • logn).
(3) Если д (n) = 0 (n ^log b a+£ ) , e > 0, то / (n) = 0 (g (n)).
Пример: / (n) = 2 • / (n/2) + n 2 , здесь подходит случай (3), поскольку
n^log b a = П ^1 , И ; следовательно / (п) = 0 (п ^2 ).
Данная теорема является мощным средством анализа асимптотической сложности рекурсивных алгоритмов, использующих метод декомпозиции, но, к сожалению,она не дает возможности получить в явном виде коэффициенты функции трудоемкости. Для решения этой задачи необходим более детальный анализ рекурсивного дерева.На основании этой теоремы получим оценку сложности функции трудоемкости для алгоритма сортировки слиянием. Поскольку в этом случае д (п) — 0 (n :log2 2 ),то /д (п) = 0 (п • logn).
Анализ вставки элемента в бинарное дерево поиска.
Операции вставки и удаления приводят к внесению изменений в динамическое множество, представленное бинарным деревом поиска. Структура данных должна быть изменена таким образом, чтобы отражать эти изменения, но при этом сохранить свойство бинарных деревьев поиска. Как мы увидим в этом разделе, вставка нового элемента в бинарное дерево поиска выполняется относительно просто, однако с удалением придется повозиться.
Вставка
Для вставки нового значения v в бинарное дерево поиска Т мы воспользуемся процедурой Tree_Insert. Процедура получает в качестве параметра узел z, у которого key [z] = v, left [z] = NIL и right [z] = NIL, после чего она таким образом изменяет Т и некоторые поля z, что z оказывается вставленным в соответствующую позицию в дереве.
TreeJnsert(T, z)
1 у *- NIL
2 х *- root[T]
3 while x ф nil
4 do у <— x
5 if key[z] < key[x]
6 then x <— left[x]
7 else x <— right[x]
8 p[z]<r-y
9 if у = NIL
10 then root[T] <— z > Дерево Г — пустое
11 else if key[z] < key[y]
12 then left[y] <- z
13 else right[y] <— z
На рис. 12.3 показана работа процедуры Tree_Insert. Подобно процедурам Tree_Search и Iterative_Tree_Search, процедура TreeInsert начинает работу с корневого узла дерева и проходит по нисходящему пути. Указатель х отмечает проходимый путь, а указатель у указывает на родительский по отношению к х деревья поиска 325
Рис. 12.3. Вставка элемента с ключом 13 в бинарное дерево поиска. Светлые узлы указывают путь от корня к позиции вставки; пунктиром указана связь, добавляемая при вставке нового элемента узел. После инициализации цикл while в строках 3-7 перемещает эти указатели вниз по дереву, перемещаясь влево или вправо в зависимости от результата сравнения ключей key [x] и key [z)9 до тех пор пока х не станет равным NIL. Это значение находится именно в той позиции, куда следует поместить элемент z. В строках 8-13 выполняется установка значений указателей для вставки z. Так же, как и другие примитивные операции над бинарным деревом поиска, процедура Tree_Insert выполняется за время О (h) в дереве высотой h.