Я должен согласиться, что это довольно странно, когда вы впервые видите алгоритм O (log n) ... откуда вообще этот логарифм? Тем не менее, оказывается, что есть несколько разных способов, которыми вы можете получить лог-термин для отображения в нотации Big-O. Вот некоторые из них:
Многократное деление на постоянную
Возьмите любое число n; скажем, 16. Сколько раз вы можете разделить n на два, прежде чем получите число, меньшее или равное единице? Для 16 у нас есть
16 / 2 = 8
8 / 2 = 4
4 / 2 = 2
2 / 2 = 1
Обратите внимание, что для этого нужно выполнить четыре шага. Интересно, что у нас также есть этот лог 2 16 = 4. Хммм ... как насчет 128?
128 / 2 = 64
64 / 2 = 32
32 / 2 = 16
16 / 2 = 8
8 / 2 = 4
4 / 2 = 2
2 / 2 = 1
Это заняло семь шагов, и log 2 128 = 7. Это совпадение? Нету! Для этого есть веская причина. Предположим, что мы разделим число n на 2 i раза. Тогда мы получим число n / 2 i . Если мы хотим найти значение i, где это значение не больше 1, мы получим
n / 2 i & le; 1
n & le; 2 я
log 2 n & le; я
Другими словами, если мы выберем целое число i такое, что i & ge; log 2 n, тогда после деления n пополам i мы получим значение, самое большее 1. Наименьшее i, для которого это гарантировано, примерно равно log 2 n, так что если у нас есть алгоритм, который делит на 2, пока число не станет достаточно маленьким, то мы можем сказать, что оно заканчивается O (log n) шагов.
Важной деталью является то, что не имеет значения, на какую константу вы делите n (если она больше единицы); если вы поделите на константу k, для достижения 1 потребуется log k n шагов. Таким образом, любой алгоритм, который многократно делит входной размер на некоторую дробь, потребует O (log n) итераций для завершения. Эти итерации могут занимать много времени, поэтому время выполнения сети не обязательно должно быть равно O (log n), но число шагов будет логарифмическим.
Так, где это происходит? Классическим примером является бинарный поиск , быстрый алгоритм поиска в отсортированном массиве по значению. Алгоритм работает так:
- Если массив пуст, вернуть, что элемент не присутствует в массиве.
- В противном случае:
- Посмотрите на средний элемент массива.
- Если он равен элементу, который мы ищем, верните успех.
- Если он больше, чем искомый элемент:
- Выбросьте вторую половину массива.
- Повторите
- Если он меньше, чем искомый элемент:
- Выбросить первую половину массива.
- Повторите
Например, для поиска 5 в массиве
1 3 5 7 9 11 13
Сначала мы посмотрим на средний элемент:
1 3 5 7 9 11 13
^
Поскольку 7> 5, а массив отсортирован, мы точно знаем, что число 5 не может быть в задней половине массива, поэтому мы можем просто отбросить его. Это оставляет
1 3 5
Итак, теперь мы посмотрим на средний элемент здесь:
1 3 5
^
Поскольку 3 <5, мы знаем, что 5 не может появиться в первой половине массива, поэтому мы можем выбросить первую половину массива, чтобы оставить </p>
5
Снова посмотрим на середину этого массива:
5
^
Поскольку это именно то число, которое мы ищем, мы можем сообщить, что 5 действительно находится в массиве.
Так насколько это эффективно? Ну, на каждой итерации мы отбрасываем как минимум половину оставшихся элементов массива. Алгоритм останавливается, как только массив становится пустым или мы находим требуемое значение. В худшем случае, элемент отсутствует, поэтому мы продолжаем уменьшать размер массива пополам до тех пор, пока не закончится количество элементов. Как долго это займет? Что ж, поскольку мы продолжаем разрезать массив пополам снова и снова, мы будем делать не более O (log n) итераций, поскольку мы не можем разрезать массив вдвое больше, чем O (log n), прежде чем запустить вне элементов массива.
Алгоритмы, следующие общей методике разделяй и властвуй (разбивая задачу на части, решая эти части, затем собирая проблему обратно вместе), как правило, имеют логарифмические выражения в их по той же причине - вы не можете продолжать резать некоторый объект вдвое больше, чем O (log n) раз. Возможно, вы захотите посмотреть сортировка слиянием как отличный пример этого.
Обработка значений по одной цифре за раз
Сколько цифр в числе оснований-10 n? Ну, если в числе есть k цифр, то у нас будет самая большая цифра, кратная 10 k . Наибольшее число k-цифр составляет 999 ... 9, k раз, и это равно 10 k + 1 - 1. Следовательно, если мы знаем, что n имеет k цифр, то мы знаем что значение n не более 10 k + 1 - 1. Если мы хотим решить для k в терминах n, мы получим
n & le; 10 k + 1 - 1
n + 1 & le; 10 к + 1
log 10 (n + 1) & le; к + 1
(log 10 (n + 1)) - 1 & le; к
Из чего мы получаем, что k - это приблизительно логарифм от n по основанию-10. Другими словами, количество цифр в n равно O (log n).
Например, давайте подумаем о сложности добавления двух больших чисел, которые слишком велики, чтобы вписаться в машинное слово. Предположим, что у нас есть эти числа, представленные в базе 10, и мы будем называть числа m и n. Одним из способов их добавления является метод начальной школы - выведите числа по одной цифре за раз, затем работайте справа налево. Например, чтобы добавить 1337 и 2065, мы начнем с записи чисел как
1 3 3 7
+ 2 0 6 5
==============
Мы добавляем последнюю цифру и переносим 1:
1
1 3 3 7
+ 2 0 6 5
==============
2
Затем мы добавляем вторую (последнюю) цифру и переносим 1:
1 1
1 3 3 7
+ 2 0 6 5
==============
0 2
Затем мы добавляем третью к последней («антепоследней») цифру:
1 1
1 3 3 7
+ 2 0 6 5
==============
4 0 2
Наконец, мы добавляем четвертую к последней ("preantepenultimate" ... я люблю английский) цифру:
1 1
1 3 3 7
+ 2 0 6 5
==============
3 4 0 2
Теперь, сколько работы мы проделали? Мы выполняем всего O (1) работы на цифру (то есть постоянный объем работы), и есть O (max {log n, log m}) общее количество цифр, которые необходимо обработать. Это дает сложность O (max {log n, log m}), потому что нам нужно посетить каждую цифру в двух числах.
Многие алгоритмы получают термин O (log n), работая по одной цифре за раз в некоторой базе. Классическим примером является сортировка по основанию , которая сортирует целые числа по одной цифре за раз. Существует много разновидностей сортировки по основанию, но они обычно выполняются за время O (n log U), где U - максимально возможное целое число, которое сортируется. Причина этого заключается в том, что каждый проход сортировки занимает O (n) времени, и для обработки каждой из цифр O (log U) наибольшего отсортированного числа требуется всего O (log U) итераций. Многие продвинутые алгоритмы, такие как алгоритм кратчайших путей Габова или масштабируемая версия алгоритма максимального потока Форда-Фулкерсона , имеют логарифмический термин в своей сложности, поскольку работают с одной цифрой в время.
Что касается вашего второго вопроса о том, как вы решаете эту проблему, вы можете посмотреть этот связанный вопрос , в котором рассматривается более сложное приложение. Учитывая общую структуру проблем, которые описаны здесь, теперь вы можете лучше понять, как думать о проблемах, когда вы знаете, что в результате есть логарифмический термин, поэтому я не советую смотреть на ответ, пока вы его не дадите. некоторые мысли.
Надеюсь, это поможет!