Как найти несколько ki наименьших элементов в массиве?

Я борюсь с домашним заданием, и мне нужно немного подтолкнуть - вопрос в том, чтобы разработать алгоритм, который за время O (nlogm) найдет несколько наименьших элементов 1<k1<k2<...<kn, и у вас есть m * k. Я знаю, что простой алгоритм выбора требует o(n) времени, чтобы найти k-й элемент, но как уменьшить m в вашем повторении? Я хотел бы делать и k1, и kn в каждом прогоне, но это займет только 2, а не m/2.

Был бы признателен за некоторые направления. Спасибо


person user2067051    schedule 09.10.2013    source источник
comment
comment
Я нахожу вашу формулировку проблемы действительно запутанной. Что такое m? Почему вы используете little-oh в o(n)?   -  person NPE    schedule 09.10.2013
comment
Спасибо, эта задача со 100 наибольшими числами выглядит очень похоже. Но я не понимаю, как они придумывают nlogm tim. @NPE M - это в основном количество ki, которое нам нужно, если не сказать, что нам нужны 1-е, 2-е и 3-е наименьшие числа, m = 3.   -  person user2067051    schedule 09.10.2013
comment
Принятый ответ на этот вопрос — O(n log m).   -  person Bernhard Barker    schedule 09.10.2013


Ответы (1)


Если я правильно понимаю вопрос, у вас есть вектор K, содержащий m индексов, и вы хотите найти k-й ранжированный элемент A для каждого k в K. Если K содержит наименьшие m индексов (т.е. k = 1,2, ...,m), то это можно легко сделать за линейное время T=O(n) с помощью быстрого выбора, чтобы найти элемент k_m (поскольку все меньшие элементы будут слева в конце быстрого выбора). Итак, я предполагаю, что K может содержать любой набор m индексов.

Один из способов добиться этого — запустить quickselect для всех K одновременно. Вот алгоритм

QuickselectMulti(A,K)
If K is empty, then return an empty result set
Pick a pivot p from A at random
Partition A into sets A0<p and A1>p.
i = A0.size + 1
if K contains i, then remove i from K and add (i=>p) to the result set.
Partition K into sets K0<i and K1>i
add QuickselectMulti(A0,K0) to the result set
subtract i from each k in K1  
call QuickselectMulti(A1,K1), add i to each index of the output, and add this to the result set
return the result set

Если K содержит только один элемент, это то же самое, что и рандомизированный быстрый выбор. Чтобы понять, почему время работы в среднем равно O(n log m), сначала рассмотрим, что происходит, когда каждый поворот точно делит A и K пополам. В этом случае вы получаете два рекурсивных вызова, поэтому у вас есть

T = n + 2T(n/2,m/2)
  = n + n + 4T(n/4,m/4)
  = n + n + n + 8T(n/8,m/8)

Так как m уменьшается вдвое каждый раз, то n будет встречаться в этом суммировании log m раз. Чтобы фактически получить ожидаемое время выполнения, требуется немного больше работы, потому что вы не можете предположить, что сводная точка разделит оба массива пополам, но если вы выполните вычисления, вы увидите, что время выполнения на самом деле составляет O (n log м) в среднем.

При редактировании: анализ этого алгоритма может упростить это, выбрав опорную точку, запустив p=Quickselect(A,k_i), где k_i — средний элемент K, а не выбирая p случайным образом. Это гарантирует, что K каждый раз делится пополам, и поэтому количество рекурсивных вызовов будет точно log m, а поскольку quickselect выполняется за линейное время, результат все равно будет O(n log m).

person mrip    schedule 09.10.2013
comment
Спасибо, чувак, именно то, что мне было нужно! извините за плохое объяснение. Только один вопрос: почему m каждый раз сокращается вдвое? - person user2067051; 09.10.2013
comment
Каждый раз m разбивается на две части m1 и m2 так, что m1+m2=m (или m1+m2=m-1, если индекс i принадлежит K). Они соответствуют двум подмножествам K1 и K2 множества K, разделенным индексом i. Как я уже сказал, вы не можете предполагать, что K будет каждый раз точно сокращаться вдвое, так что у вас есть еще кое-какая работа, но если вы проведете вычисления, вы обнаружите, что ожидаемое время выполнения все равно будет уменьшаться. быть O (n log m). - person mrip; 09.10.2013
comment
Если подумать, вы можете сделать это проще, выбрав p, запустив p=Quickselect(A,k_i), где k_i — средний элемент K. Это гарантирует, что K каждый раз делится пополам, и поэтому количество рекурсивные вызовы будут ровно log m. - person mrip; 09.10.2013
comment
Спасибо. Еще один вопрос - add (i=›p) означает добавить a[i] к результату, верно? - person user2067051; 09.10.2013
comment
В основном, да. Я представляю результат как карту от индексов к значениям, так что это означает добавить пару i maps в p к карте результата. Обратите внимание, что здесь a[i]=p. Итак, да, вы могли бы так же легко забыть об индексах и просто вернуть набор значений, и в этом случае вы могли бы просто добавить p к набору результатов. - person mrip; 09.10.2013