В решении ghoseb's есть небольшой недостаток, что делает его O (n ** 2), а не O (n).
Проблема в том, что это выполняет:
item = l1.pop(0)
Со связанными списками или запросами это будет операция O (1), поэтому не будет влиять на сложность, но так как списки python реализованы как векторы, это копирует остальные элементы l1 на один пробел слева, O ( н) операция. Поскольку это делается при каждом прохождении списка, алгоритм O (n) превращается в алгоритм O (n ** 2). Это можно исправить с помощью метода, который не изменяет исходные списки, а просто отслеживает текущую позицию.
Я попробовал сравнить исправленный алгоритм с простой сортировкой (l1 + l2), как предложено dbr
def merge(l1,l2):
if not l1: return list(l2)
if not l2: return list(l1)
# l2 will contain last element.
if l1[-1] > l2[-1]:
l1,l2 = l2,l1
it = iter(l2)
y = it.next()
result = []
for x in l1:
while y < x:
result.append(y)
y = it.next()
result.append(x)
result.append(y)
result.extend(it)
return result
Я протестировал их со списками, созданными с помощью
l1 = sorted([random.random() for i in range(NITEMS)])
l2 = sorted([random.random() for i in range(NITEMS)])
Для списков разных размеров я получаю следующие значения времени (повторяя 100 раз):
# items: 1000 10000 100000 1000000
merge : 0.079 0.798 9.763 109.044
sort : 0.020 0.217 5.948 106.882
Так что, на самом деле, похоже, что dbr - это правильно, просто использование sorted () предпочтительнее, если только вы не ожидаете очень больших списков, хотя у него действительно хуже алгоритмическая сложность. Точка безубыточности составляет около миллиона элементов в каждом списке источников (всего 2 миллиона).
Одно из преимуществ подхода слияния состоит в том, что переписать его как генератор, который будет использовать существенно меньше памяти (нет необходимости в промежуточном списке), тривиально.
[Изменить]
Я повторил это с ситуацией, близкой к вопросу - используя список объектов, содержащий поле "date
", которое является объектом datetime.
Приведенный выше алгоритм был изменен для сравнения с .date
, а метод сортировки был изменен на:
return sorted(l1 + l2, key=operator.attrgetter('date'))
Это немного меняет дело. Сравнение более дорогое означает, что число, которое мы выполняем, становится более важным по сравнению с постоянной скоростью реализации. Это означает, что слияние компенсирует потерянные позиции, превосходя метод sort () на 100 000 элементов. Сравнение, основанное на еще более сложном объекте (например, больших строках или списках), вероятно, еще больше сместит этот баланс.
# items: 1000 10000 100000 1000000[1]
merge : 0.161 2.034 23.370 253.68
sort : 0.111 1.523 25.223 313.20
[1]: Примечание: на самом деле я сделал только 10 повторов для 1 000 000 элементов и соответственно увеличил его, поскольку это было довольно медленно.