Алгоритм объединения двух отсортированных массивов с минимальным количеством сравнений

24

Даны два отсортированных массива a , b типа T с размерами n и m . Я ищу алгоритм, который объединяет два массива в новый массив (максимальный размер n + m).

Если у вас дешевая операция сравнения, это довольно просто. Просто возьмите из массива с самым низким первым элементом, пока один или оба массива не пройдут полностью, затем добавьте остальные элементы. Примерно так /programming/5958169/how-to-merge-two-sorted-arrays-into-a-sorted-array

Однако ситуация меняется, когда сравнение двух элементов намного дороже, чем копирование элемента из исходного массива в целевой массив . Например, у вас может быть массив больших целых чисел произвольной точности или строки, где сравнение может быть довольно дорогим. Просто предположим, что создание массивов и копирование элементов бесплатное, и единственное, что стоит, - это сравнение элементов.

В этом случае вы хотите объединить два массива с минимальным количеством сравнений элементов . Вот несколько примеров, где вы должны быть в состоянии сделать намного лучше, чем простой алгоритм слияния:

a = [1,2,3,4, ... 1000]
b = [1001,1002,1003,1004, ... 2000]

Или

a = [1,2,3,4, ... 1000]
b = [0,100,200, ... 1000]

Есть несколько случаев, когда простой алгоритм слияния будет оптимальным, например,

a = [1,3,5,7,9,....,999]
b = [2,4,6,8,10,....,1000]

Таким образом, алгоритм должен в идеале грациозно ухудшаться и выполнять максимум n + m-1 сравнений в случае чередования массивов, или, по крайней мере, в худшем случае.

Одна вещь, которая должна хорошо работать для списков с большой разницей в размерах, - это использовать бинарный поиск для вставки элементов меньшего массива в больший массив. Но это не будет изящно ухудшаться, если оба списка имеют одинаковый размер и чередование.

Единственная вещь, доступная для элементов, - это (общая) функция упорядочения, поэтому любая схема, которая делает сравнение дешевле, невозможна.

Любые идеи?

Я придумал этот бит в Scala . Я считаю, что это оптимально с точки зрения количества сравнений, но я не могу это доказать. По крайней мере, это намного проще, чем то, что я нашел в литературе.

А с момента первоначальной публикации я написал в блоге сообщение о том, как это работает.

Рюдигер Клаен
источник
2
Нет способа сделать меньше сравнений, чем в «простом алгоритме слияния». Вы можете попытаться обработать крайние случаи, как первое, которое вы упомянули, но это ухудшит средний случай.
Мефи
5
@Mephy: просветите нас и дайте нам формальное доказательство, пожалуйста. Или, если вы не можете, рассмотрите возможность удаления (или, по крайней мере, уточнения) вашего комментария.
Док Браун
4
@DocBrown, если бы у меня было формальное доказательство, я бы дал ответ, а не комментарий. В любом случае, это довольно очевидная линейная проблема, потому что попытка найти лучшее, чем линейное решение потребует как минимум линейного времени.
Мефи
4
@Mephy: Я предлагаю вам уделить время, чтобы прочитать ответ ниже, и дважды подумать о том, что вы написали.
Док Браун
4
@Mephy Большинство вещей, которые очевидны («вы не можете сделать умножение менее чем за O (n ^ 2)», «если я изменю выбранную дверь, я не улучшу свои шансы выиграть цену» , «вы можете сортировать меньше, чем O (n log n) ", ..) неправильно. Например, использование подхода бинарного поиска в более коротком списке должно улучшить средний случай.
Во

Ответы:

31

Обычный алгоритм сортировки слиянием - шаг слияния с обычно применяемыми сравнениями n + m -1, где один список имеет размер n, а другой список имеет размер m. Использование этого алгоритма является наиболее простым способом объединения двух отсортированных списков.

Если сравнения слишком дороги, вы можете сделать две вещи: либо вы минимизируете количество сравнений, либо минимизируете стоимость сравнений.

Давайте сосредоточимся на минимизации стоимости сравнения. Вы и только вы можете решить, могут ли данные, которые вы сравниваете, быть квантованы или нет. Если вы можете их квантовать, это является формой реализации метода хеширования, который сохраняет порядок. Например, если ваши данные сравниваются по имени, затем по первому имени, ... вы можете взять первый символ в символах «Klaehn, Ruediger» и уменьшить / квантовать ваш элемент данных до «Kl.Ru», если вы сравните его Для «Packer, The» вы сохраняете порядок «Pa.Th» - теперь вы можете применить более дешевый алгоритм сравнения, сравнивая приведенные значения. Но если вы найдете другой «Kl.Ru», у вас теперь есть близкое значение, и вы могли бы теперь перейти к более дорогому подходу, сравнивая эти элементы.

Если вы можете извлечь это квантованное значение из ваших данных, быстрее чем сравнивать его, это первое, что вы делаете, вы сначала сравниваете квантованное или хешированное значение. Помните, что это значение необходимо вычислять только один раз, поэтому вы можете вычислить его при создании элемента данных.

Я также упомянул другой способ, чтобы минимизировать ваши сравнения.

Я заглянул в классическую книгу TAOCP - Том 3 - Сортировка и поиск (стр. 197-207, раздел 5.3.2), в которой полно 10 страниц по этой теме. Я нашел две ссылки на алгоритмы, которые быстрее чем n + m-1 сравнений.

Во-первых, это алгоритм слияния Хван-Линя, а во-вторых, улучшение Гленна К. Манахера - оба приводятся в TAOCP, а также алгоритм Кристена, который приближается к нижней границе необходимых сравнений при особых условиях длины n и m. из списков.

Алгоритм Manacher был представлен в журнале ACM Vol. 26 Номер 3 на страницах 434-440: «Значительные улучшения алгоритма слияния« Хван-Лин ». список с m элементами и список с n элементами могут иметь различную длину, но они также должны быть упорядочены по количеству элементов, которые они содержат m <= n

Алгоритм Хван-Лин разбивает списки для слияния, разделяя их на более мелкие списки и сортируя списки, сравнивая первый элемент каждого подсписка и решая, нужно ли сравнивать некоторые элементы в подсписке или нет. Если первый список меньше второго, то велика вероятность, что последовательные элементы более длинного списка могут быть перенесены в результирующий список без сравнения. Если первый элемент маленького ist больше, чем первый элемент разделенного большего списка, все элементы перед подсписком могут быть скопированы без сравнения.

Усредненный анализ случая слияния аорифмов Хвана и Линя (Vega, Frieze, Santha) в разделе 2 позволяет найти псевдокод алгоритма HL. Что намного лучше, чем мое описание. И вы можете видеть, почему сравнений меньше - алгоритм использует двоичный поиск, чтобы найти индекс, куда вставить элемент из более короткого списка.

Если списки не чередуются, как в вашем последнем примере, у вас должен быть оставшийся меньший и оставшийся больший список в большинстве случаев. Это когда HL-алгоритм начинает работать лучше.

thepacker
источник
Спасибо за ваш комментарий по этому поводу. Я проверил свой ответ и обнаружил, что Кнут потратил целых 10 страниц на эту тему. А потом я взял JACM с полки и посмотрел туда больше. Я улучшу свой ответ. - Нет необходимости в понижении. Алгоритм хеширования (квантования) - это простая идея, которая может быть применена ко многим наборам данных, но только тот парень, который спросил, является единственным, кто решает, применимо ли это к его данным или нет.
упаковщик
4
После того, как вы улучшите свой ответ, все, кто проголосовал против вас, получат шанс снова проголосовать за вас ;-)
Док Браун
+1 за то, что если размеры сильно отличаются, то стандартное слияние не является оптимальным.
Флориан F
1

Предположим, что два массива имеют N и M элементов, N ≥ M, и все элементы различны.

Если отсортированный массив содержит элемент x из N, за которым следует элемент y из M или наоборот, тогда x и y должны были бы сравниваться, иначе мы бы не знали, в каком порядке они принадлежат. (Не может быть цепочки других элементов, скажем, a, b, c, где мы знаем, что x <a <b <c <y, например, потому что между x и y нет элементов. Таким образом, x и y должны были сравниваться непосредственно.

Если N> M, то возможно иметь массив, в котором каждому элементу M предшествует и следует элемент N, что означает, что необходимо по крайней мере 2M сравнений - даже если вы используете недетерминированный алгоритм сортировки, который может сделать идеальное предположение, какие цифры сравнивать. (Что это значит: предположим, что у вас N большое, M = 1. Двоичный поиск выполняет O (log2 N) шагов; недетерминированный алгоритм будет угадывать, между какими двумя элементами принадлежит один элемент второго массива, и проведет два сравнения с подтвердите предположение).

gnasher729
источник