Я должен согласиться, что это довольно странно, когда вы впервые видите алгоритм O (log n) ... откуда вообще этот логарифм? Однако оказывается, что есть несколько разных способов получить логический термин, который будет отображаться в нотации с большим 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 1
п 2 я
log 2 n я
Другими словами, если мы выберем целое число i такое, что i log 2 n, то после деления n пополам i раз мы получим значение, не превышающее 1. Наименьшее i, для которого это гарантируется примерно log 2 n, поэтому, если у нас есть алгоритм, который делит на 2, пока число не станет достаточно маленьким, то мы можем сказать, что он завершается за O (log n) шагов.
Важная деталь заключается в том, что не имеет значения, на какую константу вы делите n (если она больше единицы); если вы разделите на константу k, потребуется log k n шагов, чтобы достичь 1. Таким образом, любой алгоритм, который многократно делит входной размер на некоторую долю, потребует O (log n) итераций для завершения. Эти итерации могут занять много времени, поэтому чистое время выполнения не обязательно должно быть O (log n), но количество шагов будет логарифмическим.
Итак, где это возникает? Классическим примером является двоичный поиск, быстрый алгоритм поиска в отсортированном массив для значения. Алгоритм работает так:
- Если массив пуст, верните, что элемента нет в массиве.
- Otherwise:
- Look at the middle element of the array.
- Если он равен искомому элементу, вернуть успех.
- If it's greater than the element we're looking for:
- Throw away the second half of the array.
- Повторение
- If it's less than the the element we're looking for:
- Throw away the first half of the array.
- Повторение
Например, чтобы найти 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 не может появиться в первой половине массива, поэтому мы можем выбросить первую половину массива, чтобы оставить
5
Снова посмотрим на середину этого массива:
5
^
Поскольку это именно то число, которое мы ищем, мы можем сообщить, что 5 действительно находится в массиве.
Так насколько это эффективно? Итак, на каждой итерации мы выбрасываем как минимум половину оставшихся элементов массива. Алгоритм останавливается, как только массив становится пустым или мы находим желаемое значение. В худшем случае элемента нет, поэтому мы продолжаем уменьшать размер массива вдвое, пока не закончатся элементы. Как долго это займет? Что ж, поскольку мы продолжаем разрезать массив пополам снова и снова, мы сделаем не более O (log n) итераций, так как мы не можем разрезать массив пополам больше, чем O (log n) раз, прежде чем мы запустим вне элементов массива.
Алгоритмы, следующие общей технике разделяй и властвуй (сокращение проблема на части, решая эти части, а затем снова собирая задачу), как правило, имеют в себе логарифмические члены по той же причине - вы не можете продолжать разрезать какой-либо объект пополам больше, чем O (log n) раз. Вы можете посмотреть на сортировку слиянием как на отличный пример этого .
Обработка значений по одной цифре за раз
Сколько цифр в десятичном числе n? Что ж, если в числе k цифр, то самая большая цифра кратна 10 k. Наибольшее k-значное число равно 999 ... 9 k раз, и это равно 10 k + 1 - 1. Следовательно, если мы знаем, что n содержит k цифр, то мы знайте, что значение n не превосходит 10 k + 1 - 1. Если мы хотим решить для k через n, мы получим
п 10 k + 1 - 1
п + 1 10 к + 1
журнал 10 (n + 1) k + 1
(log 10 (n + 1)) - 1 тыс.
Отсюда мы получаем, что k приблизительно равен десятичному логарифму числа n. Другими словами, количество цифр в 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
Наконец, мы добавляем предпоследнюю ("предпоследнюю" ... я люблю английский) цифру:
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) итераций. Многие сложные алгоритмы, такие как алгоритм кратчайших путей Габова или масштабируемая версия алгоритма максимального потока Форда-Фулкерсона, имеют логарифмический термин в их сложности потому что они работают по одной цифре за раз.
Что касается вашего второго вопроса о том, как вы решаете эту проблему, вы можете посмотреть этот связанный вопрос, который исследует более сложное приложение. Учитывая общую структуру проблем, описанных здесь, теперь вы можете лучше понимать, как думать о проблемах, когда знаете, что в результате есть логический термин, поэтому я бы посоветовал не смотреть на ответ, пока вы его не дадите. некоторые мысли.
Надеюсь это поможет!
person
templatetypedef
schedule
05.02.2012
O(log n)
можно рассматривать как: Если вы удвоите размер проблемыn
, вашему алгоритму потребуется лишь на постоянное количество шагов больше. - person phimuemue   schedule 06.02.2012