Алгоритм объединения двух списков без сравнения между ними
я ищу алгоритм слияния двух отсортированных списков, но им не хватает оператора сравнения между элементами одного списка и элементами другого. Результирующий объединенный список может быть не уникальным, но любой результат, удовлетворяющий относительному порядку сортировки каждого списка, подойдет. Точнее:
дано:
- Списки A =
{a_1, ..., a_m}
и B ={b_1, ..., b_n}
. (Они также могут считаться наборами). - оператор предшествования
<
определяется среди элементов каждого списка таким образом, чтоa_i < a_{i+1}
иb_j < b_{j+1}
на1 <= i <= m
и1 <= j <= n
. - оператор приоритета не определен между элементами A и B:
a_i < b_j
не определено для любого действительногоi
иj
. - оператор равенства
=
определено среди всех элементов A или B (он определяется между элементом из A и элементом из B). - нет двух элементов из списка A равны, и то же самое относится к списку Б.
производим:
Список C = {c_1, ..., c_r}
такое, что:
-
C = union(A, B)
; элементы C-это объединение элементов из A и B. - если
c_p = a_i
,c_q = a_j
иa_i < a_j
, потомc_p < c_q
. (Порядок элементов из подсписков с соответствующих множеств A и B должны быть сохранены. - нет
i
иj
такое, чтоc_i = c_j
. (все дублированные элементы между A и B удаленный.)
я надеюсь, что этот вопрос имеет смысл и что я не задаю что-то ужасно очевидное, или что-то, для чего нет решения.
контекст:
конструктивное число может быть представлено точно в Конечно многих квадратичных расширениях поля рациональных чисел (используя двоичное дерево высоты, равное числу расширений поля). Поэтому представление конструктивного числа должно " знать" поле, в котором оно представлено. Списки A и B представляют собой последовательные квадратичные расширения рациональных чисел. Элементы A и B сами по себе являются конструктивными числами, которые определяются в контексте предыдущих меньших полей (следовательно, оператор приоритета). При сложении / умножении конструктивных чисел, сначала необходимо объединить квадратично расширенные поля, чтобы двоичная арифметика операции могут быть выполнены; результирующий список C-это квадратично расширенное поле, которое мочь представлять числа, представляемые обоими полями A и B. (Если у кого-то есть лучшее представление о том, как программно работать с конструктивными числами, дайте мне знать. Вопрос о конструктивных числах имеет возникла перед, а также Вот некоторые интересные ответы об их представлении.)
прежде чем кто-нибудь спросит, нет, этот вопрос не относится к mathoverflow; они ненавидят алгоритм (и, как правило, математику не выпускника) вопросы.
на практике списки A и B являются связанными списками (фактически хранятся в обратном порядке). Мне также нужно будет отслеживать, какие элементы C соответствовали которым в A и B, но это незначительная деталь. Алгоритм, который я ищу, не является операцией слияния в mergesort, поскольку оператор приоритета не определен между элементами двух объединяемых списков. Все в конечном итоге будет реализовано на C++ (я просто хочу перегрузку оператора). Это не домашнее задание, а в конечном итоге будет открытым исходным кодом, FWIW.
5 ответов
Я не думаю вы можете сделать это лучше, чем O(N*M), хотя я был бы рад ошибиться.
в таком случае, я бы сделал так:
- Возьмите первый (оставшийся) элемент A.
- ищите его в (что осталось от) B.
- если вы не найдете его в B, переместите его на выход
- если вы найдете его в B, переместите все из B до и включая матч, и удалите копию из А.
- повторите выше, пока A не опустеет
- переместить все, что осталось в B на выход
Если вы хотите обнаружить несовместимые упорядочения A и B, удалите "(то, что осталось)" из шага 2. Найдите весь B и поднимите ошибку, если найдете ее "слишком рано".
проблема в том, что при заданном общем элементе A нет возможности искать его в B лучше, чем линейное время (в размере B), потому что все, что у нас есть, это тест на равенство. Но, очевидно, нам нужно как-то найти совпадения и (здесь я немного машу руками, я не могу это сразу доказать), поэтому мы должны проверить каждый элемент a для сдерживания в B. Мы можем избежать кучу сравнений, потому что порядки двух наборов последовательны (по крайней мере, я предполагаю, что они есть, и если нет решения).
Итак, в худшем случае пересечение списков пусто, и никакие элементы A не сопоставимы с любой это требует N * M тестов на равенство для установления, следовательно, наихудшей границы.
для вашего примера задача A = (1, 2, c, 4, 5, f), B = (a, b, c, d, e, f), это дает результат (1,2,a,b,c,4,5,d,e,f), который мне кажется хорошим. Он выполняет 24 теста равенства в процессе (если я не могу считать): 6 + 6 + 3 + 3 + 3 + 3. Слияние с A и B наоборот даст (a,b,1,2,c,d,e,4,5,f), в этом случае с тем же количеством сравнений, так как соответствующие элементы просто так получилось, что в двух списках индексы равны.
Как видно из примера, операция не может быть повторена. слияние(A,B) приводит к списку с порядком,несовместимым с порядком слияния (B, A). Следовательно, merge ((merge (A,B),merge (B,A)) не определено. В общем случае результат слияния произволен, и если вы будете использовать произвольные заказы в качестве основы для новых полных заказов, вы создадите взаимоисключающие заказы.
похоже, что он будет использовать вырожденную форму топологической сортировки.
EDIT 2:
теперь с комбинированный режим:
import itertools
list1 = [1, 2, 'c', 4, 5, 'f', 7]
list2 = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
ibase = 0
result = []
for n1, i1 in enumerate(list1):
for n2, i2 in enumerate(itertools.islice(list2, ibase, None, 1)):
if i1 == i2:
result.extend(itertools.islice(list2, ibase, ibase + n2))
result.append(i2)
ibase = n2 + ibase + 1
break
else:
result.append(i1)
result.extend(itertools.islice(list2, ibase, None, 1))
print result
было бы достаточно объединить два списка? Он сохраняет относительную сортированность элементов из a и элементов из b.
тогда это просто вопрос удаления дубликатов.
EDIT: хорошо, после обсуждения комментариев (и учитывая дополнительное условие, что a_i=b_i & a_j=b_j & a_i<a_j => b_i<b-J
), вот разумное решение:
определить элементы, общие для обоих списков. Это O (n2) для наивного алгоритма можно улучшить его.
(необязательно) убедитесь, что общие записи находятся в одном порядке в обоих списках.
построить список результатов: все элементы a, которые находятся перед первым общим элементом, за которым следуют все элементы b перед первым общим элементом, за которым следует первый общий элемент и так далее.
учитывая проблему, как вы ее выразили, у меня такое чувство, что проблема может не иметь решения. Предположим, что у вас есть две пары элементов {a_1, b_1}
и {a_2, b_2}
здесь a_1 < a_2
в порядке а, и b_1 > b_2
в порядке B. теперь предположим, что a_1 = b_1
и a_2 = b_2
согласно оператору равенства для A и B. В этом сценарии я не думаю, что вы можете создать объединенный список, который удовлетворяет требованию заказа подсписка.
в любом случае, есть алгоритм это должно сработать. (Закодировано на Java-ish ...)
List<A> alist = ...
List<B> blist = ...
List<Object> mergedList = new SomeList<Object>(alist);
int mergePos = 0;
for (B b : blist) {
boolean found = false;
for (int i = mergePos; i < mergedList.size(); i++) {
if (equals(mergedList.get(i), b)) {
found = true; break;
}
}
if (!found) {
mergedList.insertBefore(b, mergePos);
mergePos++;
}
}
этот алгоритм O(N**2)
в худшем случае, и O(N)
в лучшем случае. (Я просматриваю некоторые детали реализации Java ... например, объединение итерации списка и вставки без серьезного штрафа за сложность ... но я думаю, что в данном случае это возможно.)
алгоритм игнорирует патологию, упомянутую в первом абзаце, и другие патологии; например, что элемент B может быть " равен" несколько элементов A или наоборот. Чтобы справиться с этим, алгоритм должен проверить каждого b
против всех элементов mergedList, которые не являются экземплярами B. Что делает алгоритм O(N**2)
в лучшем случае.
Если элементы хэшируются, это можно сделать за O (N) время, где N-общее количество элементов в A и B.
def merge(A, B):
# Walk A and build a hash table mapping its values to indices.
amap = {}
for i, a in enumerate(A):
amap[a] = i
# Now walk B building C.
C = []
ai = 0
bi = 0
for i, b in enumerate(B):
if b in amap:
# b is in both lists.
new_ai = amap[b]
assert new_ai >= ai # check for consistent input
C += A[ai:new_ai] # add non-shared elements from A
C += B[bi:i] # add non-shared elements from B
C.append(b) # add the shared element b
ai = new_ai + 1
bi = i + 1
C += A[ai:] # add remaining non-shared elements from A
C += B[bi:] # from B
return C
A = [1, 2, 'c', 4, 5, 'f', 7]
B = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
print merge(A, B)
(Это просто реализация алгоритма Анона. Обратите внимание, что вы можете проверить несогласованные входные списки без ущерба для производительности и что случайный доступ в списки не надо.)