Открыть Электронные книги
Категории
Открыть Аудиокниги
Категории
Открыть Журналы
Категории
Открыть Документы
Категории
Московской области
«Красногорский колледж»
Практическое занятие №7. «Оценка сложности алгоритма»
Оценка сложности
Сложность алгоритмов обычно оценивают по времени
выполнения или по используемой памяти. В обоих случаях
сложность зависит от размеров входных данных: массив из
100 элементов будет обработан быстрее, чем аналогичный
из 1000. При этом точное время мало кого интересует: оно
зависит от процессора, типа данных, языка
программирования и множества других параметров. Важна
лишь асимптотическая сложность, т. е. сложность при
стремлении размера входных данных к бесконечности.
Допустим, некоторому алгоритму нужно выполнить 4n3 +
7n условных операций, чтобы обработать n элементов
входных данных. При увеличении n на итоговое время
работы будет значительно больше влиять возведение n в
куб, чем умножение его на 4 или же прибавление 7n. Тогда
говорят, что временная сложность этого алгоритма
равна О(n3), т. е. зависит от размера входных данных
кубически.
Использование заглавной буквы О (или так называемая О-
нотация) пришло из математики, где её применяют для
сравнения асимптотического поведения функций.
Формально O(f(n)) означает, что время работы алгоритма
(или объём занимаемой памяти) растёт в зависимости от
объёма входных данных не быстрее, чем некоторая
константа, умноженная на f(n).
Примеры
Первый проход:
(5 1 4 2 8) (1 5 4 2 8), Здесь алгоритм сравнивает два первых элемента и меняет их
местами.
(1 5 4 2 8) (1 4 5 2 8), Меняет местами, так как
(1 4 5 2 8) (1 4 2 5 8), Меняет местами, так как
(1 4 2 5 8) (1 4 2 5 8), Теперь, ввиду того, что элементы стоят на своих местах (),
алгоритм не меняет их местами.
Второй проход:
(1 4 2 5 8) (1 4 2 5 8)
(1 4 2 5 8) (1 2 4 5 8), Меняет местами, так как
(1 2 4 5 8) (1 2 4 5 8)
Теперь массив полностью отсортирован, но алгоритму это
неизвестно. Поэтому ему необходимо сделать полный проход и
определить, что перестановок элементов не было.
Третий проход:
(1 2 4 5 8) (1 2 4 5 8)
(1 2 4 5 8) (1 2 4 5 8)
Теперь массив отсортирован и алгоритм может быть
завершён.
Несмотря на то, что код достаточно прост, в нём есть несколько ловушек.
Что будет, если first и last по отдельности умещаются в свой тип,
а first+last — нет?[1] Если теоретически возможны массивы столь большого
размера, приходится идти на ухищрения:
o Использовать код first + (last - first) / 2 , который точно не
приведёт к переполнениям (то и другое — неотрицательные целые
числа).
Если first и last — указатели или итераторы, такой код
единственно правильный. Преобразование в uintptr_t и
дальнейший расчёт в этом типе нарушает абстракцию, и нет
гарантии, что результат останется корректным указателем.
Разумеется, чтобы сохранялась сложность алгоритма,
нужны быстрые операции «итератор+число → итератор»,
«итератор−итератор → число».
o Если first и last — типы со знаком, провести расчёт в беззнаковом
типе: ((unsigned)first + (unsigned)last) / 2 .
В Java соответственно [уточнить]: (first + last) >>> 1 .
o Написать расчёт на ассемблере, с использованием флага переноса.
Что-то наподобие add eax, b; rcr eax, 1 . А вот длинные
типы использовать нецелесообразно, first + (last - first) /
2 быстрее.
В двоичном поиске часты ошибки на единицу. Поэтому важно протестировать
такие случаи: пустой массив ( n=0 ), ищем отсутствующее значение (слишком
большое, слишком маленькое и где-то в середине), ищем первый и последний
элемент. Не выходит ли алгоритм за границы массива? Не зацикливается ли?
Иногда требуется, чтобы, если x в цепочке существует в нескольких экземплярах,
находило не любой, а обязательно первый (как вариант: последний; либо вообще
не x , а следующий за ним элемент). Код на Си в такой ситуации находит первый
из равных, более простой код на Си++ — какой попало.
Алгоритмы факторизации
Как правило, на вход таких алгоритмов подаётся число , которое необходимо
факторизовать, состоящее из символов (если представлено в двоичном виде).
При этом алгоритм ищет первый простой делитель, после чего, при
необходимости, можно запустить алгоритм заново для дальнейшей
факторизации. Также, прежде чем начинать факторизацию большого числа,
следует убедиться в том, что оно не простое. Для этого достаточно пройти тест
числа на простоту. Эта задача детерминированно разрешима за полиномиальное
время.
В зависимости от сложности алгоритмы факторизации можно разбить на две
группы. Первая группа — экспоненциальные алгоритмы, сложность которых
экспоненциально зависит от длины входящих параметров (то есть от
длины самого числа в бинарном представлении). Вторая
группа — субэкспоненциальные алгоритмы.
Вопрос о существовании алгоритма факторизации с полиномиальной
сложностью на классическом компьютере является одной из важных открытых
проблем современной теории чисел. В то же время факторизация с
полиномиальной сложностью возможна на квантовом компьютере с
помощью алгоритма Шора (класс BQP).
Применение в криптографии