(я предполагаю, что ваша цель - найти все пары в L, которые суммируют что-то в M)
Забудьте хеш-таблицы!
Сортировка обоих списков.
Затем выполните внешний цикл вашего алгоритма: пройдитесь по каждому элементу i в L, затем по каждому большему элементу j в L. По ходу, сформируйте сумму и проверьте, находится ли он в M.
Но не смотрите с помощью бинарного поиска: просто выполните линейное сканирование с последнего места, где вы смотрели. Допустим, вы работаете с некоторым значением i, и у вас есть некоторое значение j, за которым следует некоторое значение j '. При поиске (i + j) вы попали бы в точку M, где найдено это значение, или в первое по величине значение. Вы сейчас ищете (я + J '); поскольку j '> j, вы знаете, что (i + j')> (i + j), и поэтому в M не может быть более раннего, чем последнее место, которое вы получили. Если и L, и M равномерно распределены, есть отличный шанс того, что точка в M, где вы найдете (i + j '), находится всего в нескольких шагах.
Если массивы распределены неравномерно, то лучше, чем линейное сканирование, быть сканированием скачка - смотрите N элементов за раз, вдвое меньше N, если переход идет слишком далеко.
Я полагаю, что это алгоритм O (n ^ 2), который так же быстр, как любой предложенный алгоритм хеширования (который имеет примитивную операцию O (1), но все же должен выполнять O (n ** 2) из них). Это также означает, что вам не нужно беспокоиться об O (n log n) для сортировки. Он имеет гораздо лучшую локальность данных, чем хеш-алгоритмы - он в основном состоит из парных потоковых чтений по массивам, повторяемых n раз.
РЕДАКТИРОВАТЬ: Я написал реализации оригинального алгоритма Пола Бейкера, алгоритма хеширования Ника Ларсена и моего алгоритма, а также простую систему сравнения. Реализации просты (линейное зондирование в хеш-таблице, пропуски в моем линейном поиске отсутствуют), и мне пришлось угадывать различные параметры размеров. См. http://urchin.earth.li/~twic/Code/SumTest/ для кода. Я приветствую исправления или предложения, касающиеся любых реализаций, инфраструктуры и параметров.
Для L и M, содержащих 3438 элементов каждый, со значениями в диапазоне от 1 до 34380 и с хэш-таблицей Ларсена, имеющей коэффициент загрузки 0,75, медианные времена для запуска:
- Бейкер (бинарный поиск): 423 716 646 нс
- Larsen (hashtable): 733 479 121 нс
- Андерсон (линейный поиск): 62 077 597 нс
Разница намного больше, чем я ожидал (и, я признаю, не в том направлении, которое я ожидал). Я подозреваю, что я сделал одну или несколько серьезных ошибок в реализации. Если кто-нибудь заметит один, я действительно хотел бы услышать об этом!
Во-первых, я выделил хеш-таблицу Ларсена внутри метода timed. Таким образом, он оплачивает стоимость размещения и (некоторого) сбора мусора. Я думаю, что это справедливо, потому что это временная структура, которая нужна только алгоритму. Если вы думаете, что это то, что можно использовать повторно, было бы достаточно просто переместить его в поле экземпляра и выделить его только один раз (и Arrays. Заполнить его нулем внутри метода timed) и посмотреть, как это влияет на производительность. *