У меня есть (неупорядоченный) набор объектов. У меня также есть оракул, который принимает упорядоченный список объектов и возвращает true, если в этом списке был хотя бы один упорядоченный конфликт, в противном случае - false. Упорядоченный конфликт представляет собой упорядоченную пару объектов (A, B), так что оракул возвращает true для любого входного списка [..., A,..., B,...]. (A, B), являющийся упорядоченным конфликтом, не обязательно означает, что (B, A) является упорядоченным конфликтом.
Я хочу идентифицировать все неупорядоченные конфликты в наборе: то есть найти все пары {x, y} такие, что либо (x, y), либо (y, x) является упорядоченным конфликтом, как определено выше. Оракул медленный (от десятков до сотен миллисекунд за вызов), поэтому важно минимизировать количество вызовов оракула; очевидный наивный алгоритм (подавать каждую возможную упорядоченную пару элементов набора в вызовы oracle; O (n²)) неприемлем. Есть сотни элементов набора, и ожидается, что их будет менее десяти конфликтов.
Это до тех пор, пока я получил: если oracle возвращает true для двухэлементного списка, то, очевидно, элементы в списке представляют собой конфликт. Если oracle возвращает false для любого списка, то в этом списке нет упорядоченных конфликтов; если oracle возвращает false для обоих списков L и для разворота списка L, то в L. нет неупорядоченных конфликтов. Таким образом, алгоритм разделения и покоя, который не совсем отличается от приведенного ниже, должен работать:
Put all the set elements in a list L (choose any convenient order).
Invoke the oracle on L.
If the oracle returns false, invoke the oracle on rev(L).
If the oracle again returns false, there are no unordered conflicts within L.
# At this point the oracle has returned true for either L or rev(L).
If L is a two-element list, the elements of L constitute an unordered conflict.
Otherwise, somehow divide the set in half and recurse on each
Я застрял на части "разделяй набор пополам и рекурсив". Есть два осложнения. Во-первых, недостаточно взять верхнюю половину, а затем нижнюю половину упорядоченного списка, потому что конфликт может быть устранен разделом (рассмотрим [... A1, A2,... An... ] [... B1, B2,... Bn...]). Перечисление всех подмножеств размера n/2 должно работать, но я не знаю, как это сделать эффективно. Во-вторых, наивная рекурсия может повторить большую работу из-за неявного состояния в стеке вызовов - предположим, что мы определили, что A конфликтует с B, тогда любое обращение оракула со списком, содержащим как A, так и B, теряется, но мы все еще необходимо исключить другие конфликты {A, x} и {B, x}. Я могу сохранить memo-матрицу таким образом, что M [a] [b] истинна тогда и только тогда, когда (A, B) уже проверена, но я не знаю, как сделать эту игру приятной с рекурсией.
Дополнительные осложнения из-за контекста: если какой-либо объект появляется более одного раза в списке, второй и последующие экземпляры игнорируются. Кроме того, некоторые объекты имеют зависимости: if (P, Q) - зависимость, то любой вход oracle, в котором Q появляется перед первым появлением P (если есть), будет ложно сообщать о конфликте. Все зависимости уже определены до запуска этого алгоритма. Если P конфликтует с A, невозможно узнать, будет ли Q также противоречить A, но это приемлемое ограничение.
(Контекст: это для идентификации пар системных заголовков C, которые не могут быть включены в один и тот же исходный файл. "Оракул" - это компилятор.)