Вы находитесь на странице: 1из 9

Государственное бюджетное образовательное учреждение

Московской области
«Красногорский колледж»
Практическое занятие №7. «Оценка сложности алгоритма»

Выполнил студент: Каргин М.П

Преподаватель: Кусов А.Ю


Цель: изучить принцип оценки сложности алгоритмов.

Задание: оценить сложность следующих алгоритмов:

1. Сортировка пузырьком (Bubble Sort)

2. Бинарный поиск (Binary Search)

3. Факторизация целых числе (Integer Factorization)


Оценка сложности алгоритмов, или
что такое О(log n)

Оценка сложности
Сложность алгоритмов обычно оценивают по времени
выполнения или по используемой памяти. В обоих случаях
сложность зависит от размеров входных данных: массив из
100 элементов будет обработан быстрее, чем аналогичный
из 1000. При этом точное время мало кого интересует: оно
зависит от процессора, типа данных, языка
программирования и множества других параметров. Важна
лишь асимптотическая сложность, т. е. сложность при
стремлении размера входных данных к бесконечности.
Допустим, некоторому алгоритму нужно выполнить 4n3 +
7n условных операций, чтобы обработать n элементов
входных данных. При увеличении n на итоговое время
работы будет значительно больше влиять возведение n в
куб, чем умножение его на 4 или же прибавление 7n. Тогда
говорят, что временная сложность этого алгоритма
равна О(n3), т. е. зависит от размера входных данных
кубически.
Использование заглавной буквы О (или так называемая О-
нотация) пришло из математики, где её применяют для
сравнения асимптотического поведения функций.
Формально O(f(n)) означает, что время работы алгоритма
(или объём занимаемой памяти) растёт в зависимости от
объёма входных данных не быстрее, чем некоторая
константа, умноженная на f(n).

Примеры

O(n) — линейная сложность


Такой сложностью обладает, например, алгоритм поиска
наибольшего элемента в не отсортированном массиве. Нам
придётся пройтись по всем n элементам массива, чтобы
понять, какой из них максимальный.

O(log n) — логарифмическая сложность


Простейший пример — бинарный поиск. Если массив
отсортирован, мы можем проверить, есть ли в нём какое-то
конкретное значение, методом деления пополам. Проверим
средний элемент, если он больше искомого, то отбросим
вторую половину массива — там его точно нет. Если же
меньше, то наоборот — отбросим начальную половину. И так
будем продолжать делить пополам, в итоге проверим log
n элементов.
O(n2) — квадратичная сложность
Такую сложность имеет, например, алгоритм сортировки
вставками. В канонической реализации он представляет из
себя два вложенных цикла: один, чтобы проходить по всему
массиву, а второй, чтобы находить место очередному
элементу в уже отсортированной части. Таким образом,
количество операций будет зависеть от размера массива
как n * n, т. е. n2.
Бывают и другие оценки по сложности, но все они основаны
на том же принципе.
Также случается, что время работы алгоритма вообще не
зависит от размера входных данных. Тогда сложность
обозначают как O(1). Например, для определения значения
третьего элемента массива не нужно ни запоминать
элементы, ни проходить по ним сколько-то раз. Всегда нужно
просто дождаться в потоке входных данных третий элемент и
это будет результатом, на вычисление которого для любого
количества данных нужно одно и то же время.
Аналогично проводят оценку и по памяти, когда это важно.
Однако алгоритмы могут использовать значительно больше
памяти при увеличении размера входных данных, чем другие,
но зато работать быстрее. И наоборот. Это помогает
выбирать оптимальные пути решения задач исходя из
текущих условий и требований.
Наглядно
Время выполнения алгоритма с определённой сложностью в
зависимости от размера входных данных при скорости 106 операций в
секунду:

Сортировка пузырьком (Bubble Sort)


Cортировка простыми обменами, сортировка пузырьком (англ. bubble sort) —
простой алгоритм сортировки. Для понимания и реализации этот алгоритм — простейший, но
эффективен он лишь для небольших массивов. Сложность алгоритма: .
Алгоритм считается учебным и практически не применяется вне учебной литературы, вместо
него на практике применяются более эффективные алгоритмы сортировки. В то же время
метод сортировки обменами лежит в основе некоторых более совершенных алгоритмов, таких
как шейкерная сортировка, пирамидальная сортировка и быстрая сортировка.
Алгоритм

Алгоритм состоит из повторяющихся проходов по сортируемому массиву. За каждый проход


элементы последовательно сравниваются попарно и, если порядок в паре неверный,
выполняется перестановка элементов. Проходы по массиву повторяются  раз или до тех пор,
пока на очередном проходе не окажется, что обмены больше не нужны, что означает —
массив отсортирован. При каждом проходе алгоритма по внутреннему циклу, очередной
наибольший элемент массива ставится на своё место в конце массива рядом с предыдущим
«наибольшим элементом», а наименьший элемент перемещается на одну позицию к началу
массива («всплывает» до нужной позиции, как пузырёк в воде — отсюда и название
алгоритма).

Пример работы алгоритма


Возьмём массив с числами «5 1 4 2 8» и отсортируем значения по возрастанию, используя
сортировку пузырьком. Выделены те элементы, которые сравниваются на данном этапе.

Наглядная демонстрация алгоритма.

Первый проход:
(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)
Теперь массив отсортирован и алгоритм может быть
завершён.

Бинарный поиск (Binary Search)


Двоичный (бинарный) поиск (также известен как метод деления
пополам или дихотомия) — классический алгоритм поиска элемента в отсортированном
массиве (векторе), использующий дробление массива на половины. Используется
в информатике, вычислительной математике и математическом программировании.
Частным случаем двоичного поиска является метод бисекции, который применяется для
поиска корней заданной непрерывной функции на заданном отрезке.

Поиск элемента в отсортированном массиве

1. Определение значения элемента в середине структуры данных. Полученное


значение сравнивается с ключом.
2. Если ключ меньше значения середины, то поиск осуществляется в первой
половине элементов, иначе — во второй.
3. Поиск сводится к тому, что вновь определяется значение серединного
элемента в выбранной половине и сравнивается с ключом.
4. Процесс продолжается до тех пор, пока не будет найден элемент со значением
ключа или не станет пустым интервал для поиска.

Визуализация бинарного поиска по массиву. Искомое число —


7.

Несмотря на то, что код достаточно прост, в нём есть несколько ловушек.

 Что будет, если  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 , а следующий за ним элемент). Код на Си в такой ситуации находит первый
из равных, более простой код на Си++ — какой попало.

Факторизация целых чисел (Integer Factorization)


Факторизацией натурального числа называется его разложение в произведение простых
множителей. Существование и единственность (с точностью до порядка следования
множителей) такого разложения следует из основной теоремы арифметики.
В отличие от задачи распознавания простоты числа, факторизация предположительно
является вычислительно сложной задачей. В настоящее время неизвестно, существует
ли эффективный не квантовый алгоритм факторизации целых чисел. Однако доказательства
того, что не существует решения этой задачи за полиномиальное время, также нет.
Предположение о том, что для больших чисел задача факторизации является вычислительно
сложной, лежит в основе широко используемых алгоритмов (например, RSA). Многие области
математики и информатики находят применение в решении этой задачи. Среди
них: эллиптические кривые, алгебраическая теория чисел и квантовые вычисления.

Схематическая иллюстрация факторизации числа 525.

Алгоритмы факторизации
Как правило, на вход таких алгоритмов подаётся число , которое необходимо
факторизовать, состоящее из  символов (если  представлено в двоичном виде).
При этом алгоритм ищет первый простой делитель, после чего, при
необходимости, можно запустить алгоритм заново для дальнейшей
факторизации. Также, прежде чем начинать факторизацию большого числа,
следует убедиться в том, что оно не простое. Для этого достаточно пройти тест
числа на простоту. Эта задача детерминированно разрешима за полиномиальное
время.
В зависимости от сложности алгоритмы факторизации можно разбить на две
группы. Первая группа — экспоненциальные алгоритмы, сложность которых
экспоненциально зависит от длины входящих параметров (то есть от
длины  самого числа в бинарном представлении). Вторая
группа — субэкспоненциальные алгоритмы.
Вопрос о существовании алгоритма факторизации с полиномиальной
сложностью на классическом компьютере является одной из важных открытых
проблем современной теории чисел. В то же время факторизация с
полиномиальной сложностью возможна на квантовом компьютере с
помощью алгоритма Шора (класс BQP).

Применение в криптографии

Предполагаемая большая вычислительная сложность задачи факторизации


лежит в основе криптостойкости некоторых алгоритмов шифрования с открытым
ключом, таких как RSA. Более того, если известен хотя бы один из параметров
ключей RSA, то система взламывается однозначно, кроме того, существует
множество алгоритмов восстановления всех ключей в системе, обладая какими-то
данными.

Вывод : мы изучили принцип оценки сложности алгоритмов.

Вам также может понравиться