2012-04-14 15 views
13

Czytałem "Algorithms, 4th Ed" przez Sedgewick & Wayne, a po drodze wdrażam algorytmy omówione w JavaScript.Mergesort - czy dno jest szybsze niż odgórne?

Niedawno skorzystałem z przykładów mergesortu zawartych w książce, aby porównać podejścia zstępujące i oddolne ... ale stwierdzam, że oddolne działa szybciej (myślę). Zobacz moją analizę na moim blogu. - http://www.akawebdesign.com/2012/04/13/javascript-mergesort-top-down-vs-bottom-up/

Nie byłem w stanie znaleźć żadnej dyskusji, która mówi, że jedna metoda mergesort powinna być szybsza od drugiej. Czy moja implementacja (lub analiza) jest wadliwa?

Uwaga: moja analiza mierzy iteracyjne pętle algorytmu, a nie tylko porównuje/przenosi tablicę Być może jest to błędne lub nieistotne?

EDYCJA: Moja analiza nie przyspieszyła czasu, więc moje stwierdzenie o tym, że działa "szybciej", jest nieco mylące. Śledzę "iteracje" za pomocą metody rekursywnej (odgórnie) i pętli for (bottom-up) - a od dołu do góry wydaje się używać mniej iteracji.

+0

Porównania i zamiany są kluczowymi pozycjami kosztowymi w analizie sortowania, jestem prawie pewien. – Pointy

+1

@Pointy tak, zwykle byłyby to pozycje do analizy przy porównywaniu różnych algorytmów sortowania. Ale w tym przypadku powinny być takie same ... to ten sam algorytm, więc nie o to mi chodzi. Moja implementacja odzwierciedla to, co jest w książce ... czy to możliwe, że od dołu do góry używa mniejszej liczby pętli ponad/przez tablicę, ale ma taką samą liczbę porównań/ruchów? – arthurakay

+0

@NiklasB. Rozumiem twój punkt widzenia ... ale te nie przyczyniają się do rozbieżności w mojej liczbie iteracji. Jeśli spojrzysz na mój kod, śledzę tylko iteracje wewnątrz pętli rekurencyjnych/iteracyjnych. Math.floor() nie ma z tym nic wspólnego - nie używam analizy opartej na czasie – arthurakay

Odpowiedz

4

Jeśli przez szybszy oznacza mniej "iteracji", to tak. Jeśli zastanawiasz się nad czasem wykonania?

Powód jest taki, że 211313 iteracji robi więcej niż 22.527 iteracji.

Patrząc na źródło wydaje się, że niektóre z węzłów liści na diagramie są sortowane nie pojedynczo, co powoduje mniej scaleń i sortów, ale trwa dłużej.

+0

Dobre wyjaśnienie, dziękuję! Prawdopodobnie muszę trochę przetrawić implementację, ale przynajmniej wiem, że nie jestem całkowicie szalona. Ja po prostu nie spodziewałem się zmienić, biorąc pod uwagę zarówno moich algorytmów użyć tego samego scalania() kod. – arthurakay

13

Nie byłem w stanie znaleźć żadnej dyskusji, która mówi, że jedna metoda mergesort powinna być szybsza od drugiej.

Sortowniki typu bottom-up i top-down, a także inne warianty zostały dobrze przebadane w latach 90. Krótko mówiąc, jeśli mierzysz koszt jako liczbę porównań poszczególnych kluczy, najlepsze koszty są takie same (~ (n lg n)/2), najgorszy koszt odgórnego jest niższy lub równy najgorszemu przypadek oddolnego (ale oba ~ n lg n) i średni koszt odgórny jest mniejszy lub równy średniemu przypadkowi oddolnemu (ale obydwu ng n), gdzie "lg n" jest logarytm binarny. Różnice wynikają z terminów liniowych. Oczywiście, jeśli n = 2^p, te dwa warianty są w rzeczywistości dokładnie takie same. Oznacza to, że w ujęciu porównawczym odgórne jest zawsze lepsze niż oddolne. Ponadto udowodniono, że strategia podziału na połówki "połowy na pół" top-down merge sort jest optymalna. Prace badawcze pochodzą z Flajolet, Golin, Panny, Prodinger, Chen, Hwang i Sedgewick.

Oto co wymyśliłem w mojej książce projektowania i analizy programów czysto funkcjonalne (College Publications, Wielka Brytania), w Erlang:

tms([X|T=[_|U]]) -> cutr([X],T,U); 
tms(T)   -> T. 

cutr(S,[Y|T],[_,_|U]) -> cutr([Y|S],T,U); 
cutr(S, T,  U) -> mrg(tms(S),tms(T)). 

mrg( [], T)   -> T; 
mrg(  S, [])   -> S; 
mrg(S=[X|_],[Y|T]) when X > Y -> [Y|mrg(S,T)]; 
mrg( [X|S], T)   -> [X|mrg(S,T)]. 

pamiętać, że jest nie stabilny porządek. Ponadto, w Erlang (i OCaml), musisz użyć aliasów (ALIAS = ...) we wzorach, jeśli chcesz zaoszczędzić pamięć. Sztuczka polega na znalezieniu środka listy bez znajomości jej długości. Odbywa się to za pomocą cutr/3, który obsługuje dwa wskaźniki do listy wejściowej: jeden jest zwiększany o jeden, a drugi o dwa, więc gdy drugi osiągnie koniec, pierwszy znajduje się pośrodku. (Nauczyłem się tego z pracy Oliviera Danvy'ego.) W ten sposób nie musisz śledzić długości i nie duplikujesz komórek drugiej połówki listy, więc potrzebujesz tylko (1/2) n lg n dodatkowej przestrzeni, w przeciwieństwie do n lg n . Nie jest to dobrze znane.

Często twierdzi się, że wariant "od dołu do góry" jest preferowany w przypadku języków funkcjonalnych lub listy połączonej (Knuth, Panny, Prodinger), ale nie sądzę, aby było to prawdą.

byłem zaskoczony jak ty brakiem dyskusji na temat rodzaju łączenia, więc zrobiłem moje własne badania i napisał dużą rozdział o nim. Obecnie przygotowuję nowe wydanie z większą ilością materiałów na temat sortowania seryjnego.

Nawiasem mówiąc, istnieją inne warianty: kolejka seryjnej sortowania i scalania na linii sortowania (I omówić ostatnie w mojej książce).

[EDIT: Ponieważ miarą kosztu jest liczba porównań, nie ma różnicy między wyborze tablicę porównaniu połączonej listy. Oczywiście, jeśli zaimplementujesz wariant od góry do dołu z połączonymi listami, musisz być sprytny, ponieważ niekoniecznie znasz liczbę kluczy, ale musisz przejść przez co najmniej połowę kluczy za każdym razem i realokacja, w sumie (1/2) n lg n komórek (jeśli jesteś sprytny). Sortowanie typu "od dołu do góry" z połączonymi listami wymaga w rzeczywistości więcej dodatkowej pamięci, n lg n + n komórek. Tak więc, nawet z połączonymi listami, wariant zstępujący jest najlepszym wyborem. Jeśli chodzi o długość programu, przebieg może się różnić, ale w języku funkcjonalnym sortowanie z góry do dołu może być krótsze niż z dołu do góry, jeśli stabilność nie jest wymagana. Istnieją pewne dokumenty, które omawiają zagadnienia wdrożeń scalania rodzaju, jak na miejscu (za które trzeba tablice) lub stabilności itd Na przykład, skrupulatny Analiza mergesort Programów przez Katajainen i Larsson traff (1997)].

+0

piszesz „a średni koszt top-down jest niższa lub równa najgorszym przypadku dołu do góry (ale oba ~ n lg n)” jest tak, lub nie masz na myśli „przeciętny przypadek oddolnym” ? Czy analiza została przeprowadzona dla tablic, czy też jest ważna również dla list połączonych? –

+0

Masz rację. Poprawiłem swój tekst i dodałem informacje. – Christian

+0

dzięki; Byłbym bardzo zainteresowany widzeniem twoich optymalnych połączonych list połączonych funkcjonalnie mergesort, aby porównać to z: ['mgsort xs = scalenie scalenia [] [[x] | x <-xs]'] (http: // /en.wikipedia.org/wiki/Fold_(higher-order_function)#Tree-like_folds). –

7

miałem to samo pytanie na forum klasy Coursera za 2012 sierpniowym numerze this course. Profesor Kevin Wayne (z Princeton) odpowiedział, że w wielu przypadkach rekurencja jest szybsza niż iteracja z powodu buforowania lepszych osiągów.

Więc krótka odpowiedź, że mam w tym czasie było to, że z góry na dół merge sort będzie szybsze niż oddolnym seryjnej sortowania ze względów buforowania.

Należy pamiętać, że zajęcia odbywały się w języku programowania Java (nie w języku Javascript).

Powiązane problemy