2016-01-06 19 views
5

Załóżmy mam jedną listę i kolejny krotki oboje są już klasyfikowane:Lepszy sposób na łączenie dwóch posortowaną listę liczb całkowitych

A = [10, 20, 30, 40] 
B = (20, 60, 81, 90) 

Co będzie mi potrzebne, aby dodać wszystkie elementy z B do A w taki sposób, że A pozostaje posortowany.

Rozwiązanie mogę wyposażone były:

for item in B: 
    for i in range(0, len(A)): 
     if item > A[i]: 
      i += 1 
     else: 
      A.insert(i, item) 

przy założeniu formatu m i B o rozmiarze n; to rozwiązanie w najgorszym przypadku zająłby O (m x n), jak mogę go poprawić?

+5

Zobacz algorytm scalania outplace używany w mergesort. Zajmuje czas 'O (m + n)'. – Haris

+1

Proponuję porównać do naiwnej (ale czytelnej) wersji 'posortowanej (A + B)'. Możesz również wyszukać istniejące biblioteki, które prawdopodobnie będą szybsze niż ręczna implementacja: http://www.grantjenks.com/docs/sortedcontainers/ – DainDwarf

+1

Wskazówka: nie modyfikuj żadnej z tych list; stwórz nowy. Kod stanie się czysty i szybki. – 9000

Odpowiedz

11

Prostym sposobem byłoby heapq.merge:

A = [10, 20, 30, 40] 

B = (20, 60, 81, 90) 

from heapq import merge 

for ele in merge(A,B): 
    print(ele) 

wyjściowa:

10 
20 
20 
30 
40 
60 
81 
90 

Niektóre czasy wykorzystujące inne rozwiązanie: O(n)

In [53]: A = list(range(10000)) 

In [54]: B = list(range(1,20000,10)) 

In [55]: timeit list(merge(A,B)) 
100 loops, best of 3: 2.52 ms per loop 

In [56]: %%timeit 
C = [] 
i = j = 0 
while i < len(A) and j < len(B): 
    if A[i] < B[j]: 
     C.append(A[i]) 
     i += 1 
    else: 
     C.append(B[j]) 
     j += 1 
C += A[i:] + B[j:] 
    ....: 
100 loops, best of 3: 4.29 ms per loop 
In [58]: m =list(merge(A,B)) 
In [59]: m == C 
Out[59]: True 

Jeśli chciał toczyć własną rękę jest to nieco szybciej niż Merge:

def merger_try(a, b): 
    if not a or not b: 
     yield chain(a, b) 
    iter_a, iter_b = iter(a), iter(b) 
    prev_a, prev_b = next(iter_a), next(iter_b) 
    while True: 
     if prev_a >= prev_b: 
      yield prev_b 
      try: 
       prev_b = next(iter_b) 
      except StopIteration: 
       yield prev_a 
       break 
     else: 
      yield prev_a 
      try: 
       prev_a = next(iter_a) 
      except StopIteration: 
       yield prev_b 
       break 
    for ele in chain(iter_b, iter_a): 
     yield ele 

Niektóre taktowania:

In [128]: timeit list(merge(A,B)) 
1 loops, best of 3: 771 ms per loop 

In [129]: timeit list(merger_try(A,B)) 
1 loops, best of 3: 581 ms per loop 

In [130]: list(merger_try(A,B)) == list(merge(A,B)) 
Out[130]: True 

In [131]: %%timeit         
C = [] 
i = j = 0 
while i < len(A) and j < len(B): 
    if A[i] < B[j]: 
     C.append(A[i]) 
     i += 1 
    else: 
     C.append(B[j]) 
     j += 1 
C += A[i:] + B[j:] 
    .....: 
1 loops, best of 3: 919 ms per loop 
+3

Co to jest kolejność tego algorytmu? – Arman

+1

@Arman Dokumentacja nie mówi, ale rozsądnie byłoby założyć, że operacja scalania będzie O (n), gdzie n jest łącznym rozmiarem wejść. I aby uzyskać wynik z powrotem do 'A', użyj' A = list (merge (A, B)). –

4

bisect moduł „zapewnia wsparcie dla prowadzenia listy posortowanych bez konieczności posortować po każdym włożeniu”:

import bisect 
for b in B: 
    bisect.insort(A, b) 

To rozwiązanie nie tworzy nową listę.


Uwaga bisect.insort(A, b) jest równoważna

A.insert(bisect.bisect_right(A, b), b) 

Choć wyszukiwania jest szybki (O (log n)), wkładanie jest powolny (O(n)).

+0

To jest O (nlgm). Można to zrobić w O (n + m). –

+0

To wciąż jest rozwiązanie O (n^2) dla czegoś, co można zrobić w O (n). Nawet najbardziej czytelnym rozwiązaniem "posortowane (A + B)" jest O (n * logn), a zatem szybciej dla dużych wejść. 'bisect.insort' jest przydatny, gdy wstawiasz elementy jeden po drugim i potrzebujesz, aby wynik pozostał posortowany po każdym kroku - co nie ma miejsca w tym przypadku. – interjay

+0

Wkładka tak naprawdę nie jest O (n), prawda? Ekspansja list nie jest wykonywana 1 na raz. Python "over-allocates". –

1

edytowany

l1 = [10,20,30,40] 
l2 = (10,20,30,40) 
l2 = list(l2) 
l1 = sorted(l1+l2) 
+0

Wyszukiwanie indeksu można wykonać w O (logn) za każdym razem, ale wstawienie zajmie wtedy O (n), więc suma wynosi O (n^2). – interjay

+0

@interjay zobacz moją zmienioną odpowiedź – Arman

+0

@Arman to wciąż O (n^2). – anand

1

Trzeba przeprowadzić scalenie. Ale "tradycyjne" scalenie generuje nową listę; więc potrzebujesz trochę modyfikacji, aby rozwinąć jedną listę.

ia = ib = 0 
while ib < len(B): 
    if ia < len(A) and A[ia] < B[ib]: 
     if ia < len(A): 
      ia += 1 
    else: 
     A.insert(ia + 1, B[ib]) 
     ib += 1 
3

Oto rozwiązanie w O(n):

A = [10, 20, 30, 40] 
B = [20, 60, 81, 90] 
C = [] 
i = j = 0 
while i < len(A) and j < len(B): 
    if A[i] < B[j]: 
     C.append(A[i]) 
     i += 1 
    else: 
     C.append(B[j]) 
     j += 1 
C += A[i:] + B[j:] 
4

Wiele dobrych dyskusji w tym poście ! Twierdzenie o taktowaniu jest trudne, dlatego napisałem skrypt czasowy. To dość elementarne, ale myślę, że na razie to zrobi. Dołączyłem też wyniki.

import timeit 
import math 
import matplotlib.pyplot as plt 
from collections import defaultdict 


setup = """ 
import bisect 
import heapq 
from random import randint 


A = sorted((randint(1, 10000) for _ in range({}))) 
B = sorted((randint(1, 10000) for _ in range({}))) 


def bisect_sol(A, B): 
    for b in B: 
     bisect.insort(A, b) 


def merge_sol(A, B): 
    ia = ib = 0 
    while ib < len(B): 
     if ia < len(A) and A[ia] < B[ib]: 
      if ia < len(A): 
       ia += 1 
     else: 
      A.insert(ia + 1, B[ib]) 
      ib += 1 


def heap_sol(A, B): 
    return heapq.merge(A, B) 


def sorted_sol(A, B): 
    return sorted(A + B) 
""" 


sols = ["bisect", "merge", "heap", "sorted"] 
times = defaultdict(list) 
iters = [100, 1000, 2000, 5000, 10000, 20000, 50000, 100000] 

for n in iters: 
    for sol in sols: 
     t = min(timeit.repeat(stmt="{}_sol(A, B)".format(sol), setup=setup.format(n, n), number=1, repeat=5)) 
     print("({}, {}) done".format(n, sol)) 
     times[sol].append(math.log(t)) 

for sol in sols: 
    plt.plot(iters, times[sol]) 
plt.xlabel("iterations") 
plt.ylabel("log time") 
plt.legend(sols) 
plt.show() 

Jest to wynikiem:

Iterations vs. Time

To jasne, że modyfikowanie listy jest głównym wąskim gardłem, więc tworzenie nowa lista jest droga.

+1

możesz również dodać moją odpowiedź, chcę zobaczyć jej kolejność, bo do not dokumentu mówi – Arman

+2

@Arman: Dodawanie; Tymczasem proszę spojrzeć na kod. Nie chcę żadnych błędów w kodzie testowym! –

+0

Przetestowałem to teraz, jego poprawne – Arman

Powiązane problemy