Алгоритм объединения двух списков без сравнения между ними

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

дано:

  • Списки 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), вот разумное решение:

  1. определить элементы, общие для обоих списков. Это O (n2) для наивного алгоритма можно улучшить его.

  2. (необязательно) убедитесь, что общие записи находятся в одном порядке в обоих списках.

  3. построить список результатов: все элементы 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)

(Это просто реализация алгоритма Анона. Обратите внимание, что вы можете проверить несогласованные входные списки без ущерба для производительности и что случайный доступ в списки не надо.)