- •Предназначено студентам очной формы обучения.
- •Введение
- •1. Оценки сложности алгоритмов
- •1.1. Определение понятия алгоритм
- •1.2. Оценка сложности алгоритмов
- •2. Линейные структуры данных
- •2.1. Методы организации и хранения линейных списков
- •2.2. Операции со списками при последовательном хранении
- •2.3. Операции со списками при связном хранении
- •2.4. Организация двусвязных списков
- •Задания для самоконтроля
- •2.5. Стеки и очереди
- •2.6. Сжатое и индексное хранение линейных списков
- •3. Сортировка и слияние
- •3.1. Пузырьковая сортировка
- •3.2. Сортировка вставкой
- •3.3. Сортировка посредством выбора
- •3.4. Сортировка квадратичной выборкой
- •3.5. Слияние списков
- •3.6. Сортировка списков путем слияния
- •3.7. Быстрая и распределяющая сортировки
- •4. Поиск
- •4.1. Последовательный поиск
- •4.2. Бинарный поиск
- •4.4. Методы вычисления адреса
- •4.5. Выбор в линейных списках
- •5. Рекурсия
- •6. Алгоритмы на графах и сетях
- •6.1. Исходные понятия
- •6.2. Матричные представления графов
- •6.3. Другие представления графов
- •6.4. Поиск в глубину как система исследования графа
- •6.5. Перебор цепей поиском в глубину
- •6.6. Взвешенные графы. Ориентированные графы
- •6.7. Нахождение фундаментального множества циклов
- •6.8. Выявление мостов и точек сочленений
- •6.9. Поиск в ширину как система исследования графа
- •6.10. Кратчайшие пути, ведущие от заданного узла X ко всем прочим
- •6.11. Алгоритм Дейкстры нахождения кратчайших путей во взвешенном графе
- •6.12. Улучшения алгоритма Дейкстры
- •6.13. Построение кратчайшего каркаса
- •6.14. Нахождение всевозможных кратчайших путей в графе
- •6.15. Потоковые задачи
- •6.16. Пример практической задачи на графах
- •7. Генераторы случайных и псевдослучайных последовательностей
- •7.1. Общая постановка задачи
- •7.3. Генератор случайных чисел, поставляемый с системой
- •7.4. Генератор с малым кодом
- •7.5. Генератор Парка-Миллера
- •7.6. Улучшенная версия генератора Парка-Миллера
- •7.7. Быстрый генератор для 32-битового представления целых и действительных чисел
- •7.8. Алгоритм л'Экюера, комбинирующий две последовательности
- •Оглавление
- •7. Генераторы случайных и псевдослучайных последовательностей 144
- •394026 Воронеж, Московский просп., 14
1.2. Оценка сложности алгоритмов
Алгоритму решения задачи предъявляются два противоречивых требования:
- быть простым для понимания, отладки и сопровождения;
- быть эффективным в смысле затрат времени и памяти ЭВМ.
Несмотря на то, что объемы памяти и быстродействие ЭВМ выросли колоссально, такой подход к оценке эффективности продолжает оставаться справедливым.
Какому из этих требований отдать предпочтение, определяется характером решаемой задачи. Если программа выполняется несколько раз, а время выполнения для пользователя несущественно, то разумнее отдать предпочтение первому требованию, т.к. стоимость создания программы намного дороже стоимости машинного времени, затрачиваемого на решение задачи.
Если же задача для своего решения требует значительных вычислительных ресурсов, выполняется многократно либо время решения ограничено внешними событиями как в системах реального времени, то требование эффективности становится более важным. Обычно эффективные алгоритмы реализуются более сложными программами, поэтому желательно, чтобы такие программы по возможности были простыми в сопровождении.
С одной стороны, целью анализа алгоритма является получение оценок (границ) для объема памяти или времени работы, которое потребуется алгоритму для обработки конкретных данных. С другой стороны, для решения одной и той же задачи могут быть использованы различные по эффективности алгоритмы. Тогда по результатам анализа выбирается наиболее эффективный из них.
При анализе с задачей связывается некоторое число, называемое размером задачи, которое выражает меру количества (объема) исходных данных. В качестве размера могут выступать размеры массивов, число вершин или дуг графа, число записей в файле и т.п. время выполнения программы T(n) зависит от размера n, единица измерения T(n) не определена. Традиционно определяют время выполнения T(n) в наихудшем случае как меру сложности алгоритма.
Время, затрачиваемое алгоритмом на его выполнение как функция размера задачи, называется временной сложностью этого алгоритма. Поведение этой сложности в пределе при увеличении размера задачи n называется асимптотической временной сложностью. Аналогично определяются емкостная сложность и асимптотическая емкостная сложность. Асимптотическая сложность алгоритма в конечном счете и определяет размер задач, которые можно решить с помощью этого алгоритма.
Если алгоритм А имеет время выполнения TA(n)=cn2, а алгоритм B – TB(n)=kd2, где c, d, k – константы, то в зависимости от их соотношения может оказаться, что для малых n более эффективным является алгоритм В, в то же время очевидно, что для больших n более эффективным будет алгоритм А. это связано с тем, что функции TA(n) и TВ(n) имеют различные скорости роста.
Для описания скорости роста функции используется О-символика. Так, T(n)=О(n2) означает, что время выполнения программы T(n) имеет порядок О(n2) и подразумевается, что существуют положительные константы с и n0, такие, что для всех n≥n0, выполняется неравенство T(n)≤cn2. Аналогично T(n) имеет порядок О(f(n)), если существуют константы с и n0, такие, что для всех n=n0 выполняется неравенство T(n)≤cf(n), тогда f(n) определяет степень роста. Говорят, что f(n) является верхней границей сложной задачи. Если мы пытаемся улучшить алгоритм с целью снизить время выполнения задачи, то возникает вопрос, где необходимо остановиться, т.к.дальнейшее улучшение невозможно, т.е. найти нижнюю границу сложности алгоритма. Определение нижней границы вызывает существенные затруднения и потому редко выполняются.
Определим сложность алгоритма сортировки методом пузырька массива А с nэлементами. Основу алгоритма составляет двойной цикл.
for (i=0; i<n-1, i++)
for(j=n-1; j<i; j - -)
if (A[j]<A[j-1])
<перестановка элементов A[j] и A[j-1]>
Перестановка элементов требует фиксированного времени. Для каждого i, устанавливаемого внешним циклом поi, внутренний цикл по j выполняется (n-i) раз, общее количество шагов определяется суммой
(n-i)=(n2-n)/2.
Следовательно, в худшем случае количество перестановок равно этому же значению. Таким образом, алгоритм метода пузырька имеет временную сложность О(n2), если даже в некоторых шагах (или даже всех) перестановок элементов не будет.
Если при некотором j не будет ни одной перестановки, то это означает, что массив уже отсортирован и процесс сортировки можно прекратить. Для этого достаточно ввести управляющий переключатель p, который устанавливается в нуль в начале внешнего цикла. Если во внутреннем цикле перестановок не было, его значение не изменяется, и по этому признаку осуществляется выход из внешнего цикла. Очевидно, что временная сложность будет равна О(1), если исходный массив был отсортирован, и О(n2) – в худшем случае, когда массив был отсортирован в обратном порядке.
При вычислениях времени выполнения программ с применением O-символики пользуются правилами сумм и произведений. Правило сумм заключается в следующем. Пусть время выполнения двух фрагментов программы Т1(n) и Т2 (n) имеет порядок 0(f1(n}) и T(f2(n)) соответственно. Время последовательного выполнения этих фрагментов вычисляется как Т1(n}+Т2(n) = O(f1(n)) + O(f2(n)) и имеет порядок O(max(f1(n),f2(n))). По правилу произведений: Т1(n) • Т2(n) = O(f1(n) • f2(n)). Тогда с применением этих правил можно определить время выполнения операторов базовых структур программ.
1. Время выполнения последовательности операторов определяется по правилу сумм, т.е. равно наибольшему времени выполнения одного оператора в данной последовательности.
2. Время выполнения оператора ветвления определяется наибольшим временем выполнения одной из ветвей оператора.
3. Время выполнения цикла определяется как произведение времени: выполнения тела цикла на количество повторений цикла.
При анализе сложности алгоритмов вводится понятие детерминированных и недетерминированных алгоритмов. В каждый момент времени выполнения алгоритм находится в некотором определенном состоянии. Состояние определяется совокупностью значений обрабатываемых данных и выполняемым действием. Свойство детерминированности означает, что для каждого имеющегося в данный момент состояния либо существует в точности одно следующее состояние, либо не существует никакого следующего — это заключительное состояние.
Пусть теперь для любого состояния алгоритма возможно иметь более одного следующего. Когда детерминированный алгоритм доходит до того места, где требуется выбрать одну из альтернатив, он исследует их последовательно друг за другом и выбирает один-единственный. Недетерминированный алгоритм просматривает все альтернативы не последовательно, а все их одновременно, копируя самого себя. Эти копии, в свою очередь, могут порождать новые копии и т.д. Все копии продолжают выполняться все вместе до тех пор, пока одна или несколько из них не найдут решения, после этого останавливаются все копии.
Все задачи, которые могут быть решены с помощью детерминированного алгоритма за полиномиальное время, образуют один класс, класс Р-задач (от англ. Polinomial), и считаются легко разрешимыми.
Задача, сложность которой не менее fn (где f — константа, или полином от n), считается экспоненциальной и относится к классу Е. При небольших значениях n экспоненциальный алгоритм, как отмечалось выше, может быть более «быстрым», чем полиномиальный. Но различие между этими алгоритмами при больших значениях n проявляется всегда.
Есть задачи, которые не попадают ни в класс Р, ни в класс Е. В настоящее время для этих задач не найдено полиномиального алгоритма. Доказано, что они являются взаимно эквивалентными. Это означает, что если бы для одной из них удалось найти полиномиальное решение, то все они также получили бы полиномиальное решение.
Задачи, которые могут быть решены с помощью недетерминированного алгоритма за полиномиальное время, относятся к классу NP-задач (NondeterministicallyPolinomial). Очевидно, что PNP,т.е. каждая задача, разрешимая по детерминированному алгоритму за полиномиальное время, также разрешима за полиномиальное время по недетерминированному алгоритму. Истинность обратного утверждения не доказана. Она является одним из не разрешенных вопросов современной математики и информатики.
Определение 1. Задача Q сводится к задаче R тогда и только тогда, когда для всякого решения s задачи R существует функция g(s), которую можно вычислить полиномиально и которая является решением задачи Q (QR). Это означает, что если мы умеем решать задачу R, то мы умеем решать и задачу Q.
Определение 2. Если одновременно QR и RQ, то говорят, что Q и R эквивалентны.
Определение 3. Задача называется NP-сложной (NP-трудной) тогда и только тогда, когда к ней сводима любая задача из класса NP Сама NP-сложная задача может принадлежать как к классу Р, так и к классу NP.
Определение 4. Задача называется NP-полной в том и только в том случае, если она является NP-сложной и попадает в класс NP.
Задача разрешимости логического выражения заключается в следующем. Пусть дано выражение Е, которое включает логические операторы ИЛИ, И, НЕ и логические переменные q1, q2, ...,qn.Нужно найти такие значения переменных qi, чтобы при этих значениях выражение E(q1, q2, ...,qn) былоистинным. Эта задача попадает в класс NP.
В 1971 г. Куком была доказана теорема: задача разрешимости логического выражения является NP-сложной.
Так как задача разрешимости принадлежит к классу NP, то она является NP-полной.
Данная теорема утверждает, что решение любой задачи из класса NP может быть получено из решения задачи разрешимости путем некоторой трансформации, которая тоже полиномиальна. Иначе, для решения произвольной задачи класса NP достаточно уметь решать задачу разрешимости. Трудность заключается в том, что мы не умеем как следует решать задачу разрешимости.
В настоящее время доказано, что большинство действительно сложных классических задач принадлежит к классу NP-сложных, и, более того, они являются NP-полными. В этом смысле все они эквивалентны. Поэтому если бы мы умели решать одну из них за полиномиальное время, то мы смогли бы решать все задачи этого класса также за полиномиальное время.