Почему время выполнения алгоритма выбора O (n)?

Согласно Википедии, алгоритм выбора имеет время выполнения O(n), но я не уверен в этом. Может ли кто-нибудь объяснить, почему это O(n)?

В обычном быстром сортировке время выполнения - O(n log n). Каждый раз, когда мы разделяем ветвь на две ветки (больше, чем ось вращения и меньше оси), нам нужно продолжить процесс по обеим сторонам ветвей, тогда как алгоритму выбора нужно только обрабатывать одну сторону ветки. Я полностью понимаю эти моменты. Но если вы думаете о бинарном алгоритме поиска, то после того, как мы выбрали средний элемент, мы также продолжаем искать одну сторону ветки. Так делает алгоритм O(1)? Нет, конечно, бинарный алгоритм поиска по-прежнему равен O(log N) вместо O(1). Это то же самое, что и элемент поиска в двоичном дереве поиска. Мы ищем только одну сторону, но мы по-прежнему считаем O(log n) вместо O(1).

Может кто-нибудь объяснить, почему в алгоритме выбора, если мы продолжим поиск одной стороны, можно считать O(1) вместо O(log n)? Для меня я считаю, что алгоритм должен быть O(n log n), O(N) для partitoning и O(log n) для количества раз, чтобы продолжить поиск.

Ответ 1

Существует несколько различных алгоритмов выбора, начиная с гораздо более простого quickselect (ожидаемого O (n), наихудшего варианта O (n 2)) с более сложным алгоритмом медианы медианов (Θ (n)). Оба этих алгоритма работают с использованием этапа быстрого сортировки (время O (n)), чтобы переставить элементы и поместить один элемент в правильное положение. Если этот элемент находится в указанном индексе, мы закончили и можем просто вернуть этот элемент. В противном случае мы определяем, с какой стороны следует переписывать и рекурсировать там.

Давайте теперь сделаем очень сильное предположение - предположим, что мы используем quickselect (произвольно выбираем шарнир), и на каждой итерации нам удается угадать точную середину массива. В этом случае наш алгоритм будет работать следующим образом: мы делаем шаг раздела, отбрасываем половину массива, а затем рекурсивно обрабатываем половину массива. Это означает, что при каждом рекурсивном вызове мы заканчиваем работу, пропорциональную длине массива на этом уровне, но эта длина продолжает уменьшаться в два раза на каждой итерации. Если мы выработаем математику (игнорируя постоянные факторы и т.д.), Мы получим следующее время:

  • Работа на первом уровне: n
  • Работа после одного рекурсивного вызова: n/2
  • Работа после двух рекурсивных вызовов: n/4
  • Работа после трех рекурсивных вызовов: n/8
  • ...

Это означает, что общая работа выполнена

n + n/2 + n/4 + n/8 + n/16 +... = n (1 + 1/2 + 1/4 + 1/8 +...)

Обратите внимание, что этот последний член равен n раз сумме 1, 1/2, 1/4, 1/8 и т.д. Если вы выработаете эту бесконечную сумму, несмотря на то, что существует бесконечно много членов, общая сумма равна 2. Это означает, что общая работа

n + n/2 + n/4 + n/8 + n/16 +... = n (1 + 1/2 + 1/4 + 1/8 +...) = 2n

Это может показаться странным, но идея состоит в том, что если мы выполняем линейную работу на каждом уровне, но продолжаем сокращать массив пополам, мы заканчиваем работу примерно в 2 раза.

Важная деталь здесь состоит в том, что здесь действительно существуют O (log n) разные итерации, но не все из них выполняют равный объем работы. Действительно, каждая итерация выполняет половину работы по сравнению с предыдущей итерацией. Если мы проигнорируем тот факт, что работа уменьшается, вы можете заключить, что работа O (n log n), которая является правильной, но не плотной. Этот более точный анализ, который использует тот факт, что выполненная работа продолжает уменьшаться на каждой итерации, дает время выполнения O (n).

Конечно, это очень оптимистичное предположение - мы почти никогда не получаем раскол 50/50! - но используя более мощную версию этого анализа, вы можете сказать, что если вы можете гарантировать любой сплит с постоянным коэффициентом, общая работа будет только некоторой постоянной кратной n. Если мы выберем абсолютно случайный элемент на каждой итерации (как мы это делаем в quickselect), то при ожидании нам нужно выбрать только два элемента, прежде чем мы получим некоторый элемент поворота в середине 50% массива, что означает, что на ожидание, только два раунда выбирая точку опоры необходимо, прежде чем мы в конечном итоге собирание то, что дает 25/75 раскола. Здесь ожидается ожидаемое время выполнения O (n) для quickselect.

Формальный анализ алгоритма медианы медианов намного сложнее, потому что повторение затруднено и нелегко анализировать. Интуитивно, алгоритм работает, выполняя небольшую работу, чтобы гарантировать хорошую ось. Однако, поскольку есть два разных рекурсивных вызова, анализ, подобный приведенному выше, не будет работать корректно. Вы можете использовать расширенный результат, называемый теоремой Akra-Bazzi, или использовать формальное определение big-O, чтобы явно доказать, что время выполнения O (n). Для более детального анализа ознакомьтесь с "Введение в алгоритмы, третье издание" Корменом, Лейсссоном, Ривестом и Штайн.

Надеюсь это поможет!

Ответ 2

Позвольте мне попытаться объяснить разницу между выбором и бинарным поиском.

Алгоритм двоичного поиска на каждом шаге выполняет операции O (1). Всего есть log (N) шагов, и это делает его O (log (N))

Алгоритм выбора на каждом шаге выполняет операции O (n). Но это "n" продолжает сокращаться вдвое. Есть полностью log (N) шагов. Это делает его N + N/2 + N/4 +... + 1 (log (N) раз) = 2N = O (N)

Для двоичного поиска это 1 + 1 +... (log (N) раз) = O (logN)

Ответ 3

В Quicksort дерево рекурсии - это уровни lg (N), и для каждого из этих уровней требуется O (N) объем работы. Таким образом, общее время работы - O (NlgN).

В Quickselect дерево повторения - это уровни lg (N), и каждый уровень требует только половину работы над уровнем выше. Это дает следующее:

N * (1/1 + 1/2 + 1/4 + 1/8 + ...)

или

N * Summation(1/i^2)
    1 < i <= lgN

Важно отметить, что я перехожу от 1 к lgN, но не от 1 до N, а также не от 1 до бесконечности.

Суммирование оценивается до 2. Следовательно, Quickselect = O (2N).

Ответ 4

Quicksort не имеет большого значения O nlogn - это наихудшее время выполнения n ^ 2.

Я предполагаю, что вы спрашиваете об алгоритме выбора хора (или быстро выбираете), а не алгоритме наивного выбора, который является O (kn). Как и quicksort, quickselect имеет наихудшее время выполнения O (n ^ 2) (если выбраны плохие повороты), а не O (n). Он может работать во время ожидания n, потому что он только сортирует одну сторону, как вы указываете.

Ответ 5

Сортировка = переупорядочивающие элементы - O (n log n), но выбор - это что-то вроде принятия i-го элемента = индексация. И для этого как в связанном списке, так и в двоичном дереве это O (n).

Ответ 6

Потому что для выбора, вы не сортируете, обязательно. Вы можете просто подсчитать количество элементов, которые имеют какое-либо заданное значение. Таким образом, медиана O (n) может быть выполнена путем подсчета количества раз каждого значения и выбора значения, которое содержит 50% элементов выше и ниже. Он проходит через массив, просто увеличивая счетчик для каждого элемента массива, поэтому он O (n).

Например, если у вас есть массив "a" из 8-разрядных номеров, вы можете сделать следующее:

int histogram [ 256 ];
for (i = 0; i < 256; i++)
{
    histogram [ i ] = 0;
}
for (i = 0; i < numItems; i++)
{
    histogram [ a [ i ] ]++;
}
i = 0;
sum = 0;
while (sum < (numItems / 2))
{
    sum += histogram [ i ];
    i++;
}

В конце, переменная "i" будет содержать 8-битное значение медианы. Это было около 1,5 проходов через массив "a". Один раз через весь массив, чтобы подсчитать значения, и половину через него, чтобы получить окончательное значение.