2015-11-21 8 views
9

Niedawno usłyszałem o Library sort i ponieważ muszę sprawić, by moi uczniowie pracowali nad Insertion sort (z którego biblioteka sortuje jest pochodną), postanowiłem zbudować dla nich ćwiczenie na ten nowy temat. Wspaniałą rzeczą jest to, że ten algorytm twierdzi, że ma złożoność O (n log n) (patrz tytuł Insertion Sort is O(n log n) lub tekst na stronie Wikipedia z powyższego linku).Empiryczna złożoność mojej implementacji "sortowania bibliotek" nie wydaje się pasować do niczego podobnego do O (n log n)

Jestem świadomy, że pomiary empiryczne nie zawsze są wiarygodne, ale starałem się zrobić co w mojej mocy i jestem trochę rozczarowany poniższą fabułą (niebieski to biblioteka, zielony to lokalny quicksort od Rosetta Code); osie pionowe to średni czas obliczony jako średnia wielu różnych prób; osie poziome są wielkością listy. Losowe listy o wielkości n mają całkowite elementy od 0 do 2n. Kształt krzywej nie wygląda tak jak coś związanego z O (n log n).

Plot 1

Oto mój kod (w tym części testowej); przegapiłem coś?

# -*- coding: utf8 -*- 

def library_sort(l): 
    # Initialization 
    d = len(l) 
    k = [None]*(d<<1) 
    m = d.bit_length() # floor(log2(n) + 1) 
    for i in range(d): k[2*i+1] = l[i] 

    # main loop 
    a,b = 1,2 
    for i in range(m): 
     # Because multiplication by 2 occurs at the beginning of the loop, 
     # the first element will not be sorted at first pass, wich is wanted 
     # (because a single element does not need to be sorted) 
     a <<= 1 
     b <<= 1 
     for j in range(a,min(b,d+1)): 
      p = 2*j-1 
      s = k[p] 
      # Binary search 
      x, y = 0, p 
      while y-x > 1: 
       c = (x+y)>>1 
       if k[c] != None: 
        if k[c] < s: x = c 
        else: y = c 
       else: 
        e,f = c-1,c+1 
        while k[e] == None: e -= 1 
        while k[f] == None: f += 1 
        if k[e] > s: y = e 
        elif k[f] < s: x = f 
        else: 
         x, y = e, f 
         break 
      # Insertion 
      if y-x > 1: k[ (x+y)>>1 ] = s 
      else: 
       if k[x] != None: 
        if k[x] > s: y = x # case may occur for [2,1,0] 
        while s != None: 
         k[y], s = s, k[y] 
         y += 1 
       else: k[x] = s 
      k[p] = None 
     # Rebalancing 
     if b > d: break 
     if i < m-1: 
      s = p 
      while s >= 0: 
       if k[s] != None: 
        # In the following line, the order is very important 
        # because s and p may be equal in which case we want 
        # k[s] (which is k[p]) to remain unchanged 
        k[s], k[p] = None, k[s] 
        p -= 2 
       s -= 1 
    return [ x for x in k if x != None ] 


def quicksort(l): 
    array = list(l) 
    _quicksort(array, 0, len(array) - 1) 
    return array 

def _quicksort(array, start, stop): 
    if stop - start > 0: 
     pivot, left, right = array[start], start, stop 
     while left <= right: 
      while array[left] < pivot: 
       left += 1 
      while array[right] > pivot: 
       right -= 1 
      if left <= right: 
       array[left], array[right] = array[right], array[left] 
       left += 1 
       right -= 1 
     _quicksort(array, start, right) 
     _quicksort(array, left, stop) 

import random, time 

def test(f): 
    print "Testing", f.__name__,"..." 
    random.seed(42) 
    x = [] 
    y = [] 
    for i in [ 25, 50, 100, 200, 300, 500, 1000, 5000, 
       10000, 15000, 25000, 40000, 50000, 100000 ]: 
     n = 100000//i + 1 
     m = 2*i 
     x.append(i) 
     t = time.time() 
     for j in range(n): 
      f([ random.randrange(0,m) for _ in range(i) ]) 
     y.append((time.time()-t)/n) 
    return x,y 

import matplotlib.pyplot as plt 
x1, y1 = test(library_sort) 
x2, y2 = test(quicksort) 
plt.plot(x1,y1) 
plt.plot(x2,y2) 
plt.show() 

Edit: I obliczane więcej wartości, włącza się do tłumacza pypy w celu obliczenia trochę więcej w tym samym czasie; tutaj jest inna fabuła; Dodałem również krzywe odniesienia. Trudno jest być tego pewna, ale wciąż wygląda trochę bardziej jak O (n²) niż O (n log n) ...

Plot 2

+1

Czas działania wynosi O (n log n) Średnio nie najgorszym przypadku. – chepner

+0

Natychmiastowe przemyślenia - (1) wydaje się zaskakująco płaska od 50 000 do 100 000 punktów danych ;-), może jeszcze kilka koniecznych przypadków testowych, (2) asymptotycznie O (n log n), oczekiwane, może nadal wyglądać bardzo różnie w małych przypadkach (to opisuje granicę, gdy rozmiar staje się bardzo duży), może 100 000 jest wciąż trochę za małe. – Steve314

+0

@chepner - dlaczego miałbyś oczekiwać losowego testu, aby pokazać najgorsze zachowanie? Gdy jeden z testów to 100 000 pozycji, wydaje się to mało prawdopodobne. – Steve314

Odpowiedz

1

link do pliku PDF, który używa innego algorytmu za to, co opisuje również jako biblioteka rodzaju na stronie 7:

http://classes.engr.oregonstate.edu/eecs/winter2009/cs261/Textbook/Chapter14.pdf

plik pdf opisuje liczenia hybrydowy i sortowanie przez wstawianie, co wydaje się znacznie różnią się od artykułu wiki, mimo że plik pdf wspomina wiki artykuł. Ze względu na fazę liczenia, tak zwane luki mają dokładnie taką wielkość, że docelowa tablica ma taki sam rozmiar jak oryginalna tablica, a nie musi być większa przez jakiś stały czynnik, jak wspomniano w artykule wiki.

Na przykład, jedna metoda wykonywania tego, co plik pdf wywołuje sortowanie biblioteki w tablicy liczb całkowitych, przy użyciu najbardziej znaczącego bajtu każdej liczby całkowitej, tworzona jest tablica zliczeń == tablica rozmiarów kubełków. Są one przekształcane na tablicę indeksów początkowych dla każdego zasobnika poprzez sumowanie sumujące. Następnie tablica indeksów startowych jest kopiowana do tablicy indeksów końcowych dla każdego segmentu (co oznaczałoby, że początkowo puste są wszystkie segmenty). Następnie sortowanie rozpoczyna się, dla każdej liczby całkowitej z macierzy źródłowej, wybierz zasobnik za pomocą najbardziej znaczącego bajtu, a następnie wykonaj sortowanie wstawiania w oparciu o początkowe i końcowe indeksy dla tego zasobnika, a następnie zwiększ indeks końcowy.

Plik pdf wymienia za pomocą pewnego rodzaju skrótu, aby wybrać segmenty dla obiektów ogólnych. Hash musiałby zachować kolejność sortowania.

Domyślam się, że złożoność czasu byłaby czasem wykonania sortowania wstawiania, czyli O (wielkość_wękanej^2) dla każdego zasobnika, ponieważ przepustka do tworzenia indeksów liczników/kubełków jest liniowa.

Dla liczb całkowitych, ponieważ większość logiki dla sortowania radix-zliczaniem już istnieje, może równie dobrze wykonać wieloprzebiegowy, najmniej znaczący dla większości znaczących bajtów zliczanie-podstawa-radix i zapomnieć o wykonaniu sortowania wstawiania.

Wracając do artykułu wiki, nie ma żadnego wyjaśnienia, jak wykryć pustą lukę. Zakładając, że żadna wartość wartownika do reprezentowania pustej przerwy nie jest dostępna, trzecia tablica może być użyta do wskazania, czy lokalizacja w drugiej macierzy zawierała dane lub była pusta.

1

Są 2 problemy z kodem test(), a nie realizacji library_sort():

  • zatrzymywania czasu obejmuje generowanie wartości (inline), patrz działka poniżej
  • produkcji wielu same wartości ("Vals podwojone "w kodzie poniżej) dla ograniczonego zakresu przy użyciu losowego: prawdopodobnie nie zamierzałeś mieć> 20% powtarzanych wartości z m = 2*i.
 
def rand_n(n, short_rnd): 
    m = (2 * n) if short_rnd else (2**30) # >20 % repeated if m just 2*n 
    return [random.randrange(0, m) for _ in range(n)] 

def get_time_over_n(f, vals_inline, short_rnd): 
    exp = 4 if f == sorted else 3 
    a = 2 * 10**exp 
    l_n = [a*1, a*2, a*5, a*10, a*20, a*50] 
    ll_vals = [] 
    for n in l_n: # values for non-inline 
     ll_vals.append(rand_n(n, short_rnd)) 
    n_tests = len(ll_vals) 
    x = [] 
    y = [] 
    for i, l_vals in enumerate(ll_vals): 
     n = len(l_vals) 
     print('%10i (still %2i)' % (n, n_tests - i), end=': ') 
     x.append(n/1000) 
     t = time.time() 
     if vals_inline: 
      f(rand_n(n, short_rnd)) 
     else: 
      f(l_vals) 
     dt = time.time() - t 
     print("%8.3f s" % dt) 
     y.append(1000 * dt/n) 
    return x, y 

COLS = ['green', 'blue', 'red', 'black'] 
plt.rc('axes', prop_cycle=(plt.cycler('color', COLS))) 
WHAT = ['ok', 'outside, but repeated vals', 'many vals, but inline', 'inline & repeated vals'] 
params = [(0, 0), (0, 1), (1, 0), (1, 1)] 

f = sorted # built-in 
#f = library_sort 
for i_plot in range(4): 
    print("%s(), %s (%s)..." % (f.__name__, WHAT[i_plot], COLS[i_plot])) 
    x, y = get_time_over_n(f, *params[i_plot]) 
    plt.plot(x, y, label=WHAT[i_plot]) 
plt.xlabel('n values/1000') 
plt.ylabel('time * 1000/n [s]') 
plt.legend(bbox_to_anchor=(1, 1.025), ncol=2, bbox_transform=plt.gcf().transFigure) 
plt.show() 

enter image description here

Powiązane problemy