Wiem, że pytanie nie jest zbyt szczegółowe.
Chcę tylko, żeby ktoś mi powiedział, jak przekonwertować normalny sortowanie scalające na sortowanie scalające na miejscu (lub sortowanie scalające ze stałym dodatkowym obszarem narzutu).
Wszystko, co mogę znaleźć (w sieci) to strony z napisem „jest zbyt skomplikowane” lub „poza zakresem tego tekstu”.
Jedyne znane sposoby łączenia na miejscu (bez dodatkowej przestrzeni) są zbyt skomplikowane, aby można je było zredukować do praktycznego programu. (wzięty stąd )
Nawet jeśli jest zbyt skomplikowana, jaka jest podstawowa koncepcja tego, jak scalić sortowanie na miejscu?
Odpowiedzi:
Knuth zostawił to jako ćwiczenie (tom 3, 5.2.5). Istnieją lokalne rodzaje scalania. Muszą być wdrażane ostrożnie.
Po pierwsze, naiwne scalanie w miejscu, takie jak opisane tutaj, nie jest właściwym rozwiązaniem. Obniża wydajność do O (N 2 ) .
Chodzi o to, aby posortować część tablicy, a resztę wykorzystać jako obszar roboczy do scalenia.
Na przykład jak następująca funkcja scalania.
Potrzeba tablicę
xs
, obie tablice podrzędne sortowane są reprezentowane zakresie[i, m)
i[j, n)
odpowiednio. Obszar pracy zaczyna się odw
. Porównaj ze standardowym algorytmem scalania podanym w większości podręczników, ten wymienia zawartość między posortowaną pod-tablicą a obszarem roboczym. W rezultacie poprzedni obszar roboczy zawiera scalone posortowane elementy, podczas gdy poprzednie elementy przechowywane w obszarze roboczym są przenoszone do dwóch pod-macierzy.Istnieją jednak dwa ograniczenia, które należy spełnić:
Po zdefiniowaniu tego algorytmu scalania łatwo jest wyobrazić sobie rozwiązanie, które może posortować połowę tablicy; Kolejne pytanie brzmi: jak postępować z resztą nieposortowanej części przechowywanej w obszarze roboczym, jak pokazano poniżej:
Intuicyjnym pomysłem jest sortowanie rekurencyjne innej połowy obszaru roboczego, dlatego jest tylko 1/4 elementów, które nie zostały jeszcze posortowane.
Kluczowym punktem na tym etapie jest to, że musimy scalić posortowane 1/4 elementów B z posortowanymi 1/2 elementami A prędzej czy później.
Czy pozostało miejsce do pracy, które mieści tylko 1/4 elementów, wystarczająco duże, aby połączyć A i B? Niestety tak nie jest.
Jednak drugie ograniczenie wspomniane powyżej daje nam wskazówkę, że możemy go wykorzystać, ustawiając obszar roboczy tak, aby zachodził na którąkolwiek z pod-macierzy, jeśli możemy zapewnić łączącą się sekwencję, że nie scalone elementy nie zostaną nadpisane.
W rzeczywistości zamiast sortować drugą połowę obszaru roboczego, możemy posortować pierwszą połowę i umieścić obszar roboczy między dwoma sortowanymi tablicami w następujący sposób:
Ta konfiguracja skutecznie rozmieszcza obszar roboczy pokrywający się z podobszarem A. Pomysł ten zaproponowano w [Jyrki Katajainen, Tomi Pasanen, Jukka Teuhola. `` Praktyczne połączenie w miejscu ''. Nordic Journal of Computing, 1996].
Pozostaje więc tylko powtórzyć powyższy krok, co zmniejsza obszar roboczy z 1/2, 1/4, 1/8,… Gdy obszar roboczy staje się wystarczająco mały (na przykład pozostały tylko dwa elementy), możemy przełącz się na trywialny sposób wstawiania, aby zakończyć ten algorytm.
Oto implementacja w ANSI C na podstawie tego dokumentu.
Gdzie wcześniej zdefiniowano wmerge.
Pełny kod źródłowy można znaleźć tutaj, a szczegółowe wyjaśnienie można znaleźć tutaj
Nawiasem mówiąc, ta wersja nie jest najszybszym sortowaniem scalającym, ponieważ wymaga więcej operacji wymiany. Według mojego testu jest on szybszy niż standardowa wersja, która przydziela dodatkowe miejsca w każdej rekurencji. Jest jednak wolniejszy niż wersja zoptymalizowana, która z góry podwaja pierwotną tablicę i wykorzystuje ją do dalszego łączenia.
źródło
Knuth left this as an exercise (Vol 3, 5.2.5).
odnosi się do np. 13. [40] Zaimplementuj sugerowaną metodę sortowania wewnętrznego [na końcu tego rozdziału], która umożliwia sortowanie losowych danych w jednostkach czasu O (N) z tylko dodatkowymi lokalizacjami pamięci O (sqrt (N)) . ? ( 40 wskazuje na dość trudny lub długotrwały problem, który może być odpowiedni jako projekt terminowy w sytuacjach klasowych. )Uwzględniając jego „duży wynik”, niniejszy artykuł opisuje kilka wariantów sortowania scalonego w miejscu (PDF):
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.22.5514&rep=rep1&type=pdf
Sortowanie na miejscu z mniejszą liczbą ruchów
Jyrki Katajainen, Tomi A. Pasanen
Myślę, że to też jest istotne. Mam na sobie wydruk, który przekazał mi kolega, ale go nie przeczytałem. Wydaje się, że obejmuje podstawową teorię, ale nie jestem wystarczająco zaznajomiony z tym tematem, aby ocenić, jak kompleksowo:
http://comjnl.oxfordjournals.org/cgi/content/abstract/38/8/681
Optymalne stabilne łączenie
Antonios Symvonis
źródło
To naprawdę nie jest łatwe ani wydajne, i sugeruję, abyś tego nie robił, chyba że naprawdę musisz (i prawdopodobnie nie musisz, chyba że jest to praca domowa, ponieważ zastosowania łączenia w miejscu są głównie teoretyczne). Nie możesz zamiast tego użyć Quicksort? Quicksort i tak będzie szybszy z kilkoma prostszymi optymalizacjami, a jego dodatkowa pamięć to O (log N) .
W każdym razie, jeśli musisz to zrobić, musisz. Oto, co znalazłem: jeden i dwa . Nie jestem zaznajomiony z rodzajem scalania, ale wydaje się, że podstawową ideą jest użycie rotacji, aby ułatwić scalenie dwóch tablic bez użycia dodatkowej pamięci.
Zauważ, że jest to wolniejsze, nawet niż klasyczny sposób scalania, który nie jest na miejscu.
źródło
Krytycznym krokiem jest doprowadzenie do samego scalenia . Te źródła nie są tak trudne, jak to zauważają, ale tracisz coś, próbując.
Patrząc na jeden etap scalania:
Wiemy, że sortowane sekwencja jest mniejsza niż wszystko inne, że x jest mniejsza niż wszystko inne w A , a y jest mniejsza niż wszystko inne w B . W przypadku, gdy x jest mniejsze lub równe y , po prostu przesuń wskaźnik na początek litery A na jednym. W przypadku, gdy y jest mniejsze niż x , musisz przetasować y przez całe A, aby posortować . Ten ostatni krok sprawia, że jest to kosztowne (z wyjątkiem przypadków zdegenerowanych).
Generalnie jest to tańsze (szczególnie gdy tablice zawierają tylko pojedyncze słowa na element, np. Wskaźnik do łańcucha lub struktury), aby zamienić trochę miejsca na czas i mieć oddzielną tablicę tymczasową, między którą sortujesz.
źródło
Dla porównania, tutaj jest ładna implementacja stabilnego typu scalania w miejscu . Skomplikowane, ale nie tak źle.
Skończyło się na wdrożeniu zarówno stabilnego sortowania scalającego w miejscu, jak i stabilnego szybkiego sortowania w Javie. Uwaga: złożoność wynosi O (n (log n) ^ 2)
źródło
Przykład bezbuforowego scalania w C.
Przykład adaptacyjnego scalania (zoptymalizowanego).
Dodaje kod pomocniczy i modyfikacje, aby przyspieszyć scalanie, gdy dostępny jest bufor pomocniczy dowolnego rozmiaru (nadal działa bez dodatkowej pamięci). Wykorzystuje scalanie do przodu i do tyłu, obracanie pierścienia, scalanie i sortowanie w małej sekwencji oraz iteracyjny scalanie.
źródło
Ta odpowiedź zawiera przykład kodu , który implementuje algorytm opisany w artykule Practical In-Place Scalenie autorstwa Bing-Chao Huanga i Michaela A. Langstona. Muszę przyznać, że nie rozumiem szczegółów, ale podana złożoność kroku scalania to O (n).
Z praktycznego punktu widzenia istnieją dowody na to, że zwykłe wdrożenia na miejscu nie sprawdzają się lepiej w scenariuszach rzeczywistych. Na przykład standard C ++ definiuje std :: inplace_merge , co oznacza, że nazwa sugeruje operację scalania w miejscu.
Zakładając, że biblioteki C ++ są zazwyczaj bardzo dobrze zoptymalizowane, ciekawe jest, jak to zaimplementować:
1) libstdc ++ (część bazy kodu GCC): std :: inplace_merge
Implementacja deleguje do __inplace_merge , co pozwala uniknąć problemu, próbując przydzielić tymczasowy bufor:
W przeciwnym razie wraca do implementacji ( __merge_without_buffer ), która nie wymaga dodatkowej pamięci, ale nie działa już w czasie O (n).
2) libc ++ (część bazy kodu Clanga): std :: inplace_merge
Wygląda podobnie. Deleguje się do funkcji , która również próbuje przydzielić bufor . W zależności od tego, czy ma wystarczającą liczbę elementów, wybierze implementację. Funkcja zastępowania pamięci stałej nazywa się __buffered_inplace_merge .
Być może nawet powrót jest nadal czasem O (n), ale chodzi o to, że nie używają implementacji, jeśli dostępna jest pamięć tymczasowa.
Zauważ, że standard C ++ wyraźnie daje implementacjom swobodę wyboru tego podejścia poprzez obniżenie wymaganej złożoności z O (n) do O (N log N):
Oczywiście nie można tego uznać za dowód, że nigdy nie należy wykorzystywać stałej przestrzeni w miejscu w czasie O (n). Z drugiej strony, jeśli byłoby to szybsze, zoptymalizowane biblioteki C ++ prawdopodobnie przełączyłyby się na tego rodzaju implementację.
źródło
To jest moja wersja C:
źródło
Istnieje względnie prosta implementacja sortowania scalającego na miejscu przy użyciu oryginalnej techniki Kronrod, ale z prostszą implementacją. Obrazowy przykład ilustrujący tę technikę można znaleźć tutaj: http://www.logiccoder.com/TheSortProblem/BestMergeInfo.htm .
Istnieją również linki do bardziej szczegółowej analizy teoretycznej tego samego autora powiązanej z tym linkiem.
źródło
Właśnie próbowałem w miejscu algorytmu scalania sortowania scalającego w JAVA , używając algorytmu sortowania wstawiania, wykonując następujące kroki.
1) Dostępne są dwa uporządkowane tablice.
2) Porównaj pierwsze wartości każdej tablicy; i umieść najmniejszą wartość w pierwszej tablicy.
3) Umieść większą wartość w drugiej tablicy, używając sortowania wstawiania (przesuwaj od lewej do prawej).
4) Następnie ponownie porównaj drugą wartość pierwszej tablicy i pierwszą wartość drugiej tablicy i zrób to samo. Ale kiedy następuje zamiana, istnieje pewna wskazówka dotycząca pominięcia przy porównywaniu dalszych elementów, ale wymagana jest tylko zamiana.
Dokonałem tutaj optymalizacji; aby zachować mniejsze porównania w rodzaju wstawiania.
Jedyną wadą, jaką znalazłem w przypadku tych rozwiązań, jest to, że wymaga większej wymiany elementów tablicy w drugiej tablicy.
na przykład)
Pierwszy___ Tablica: 3, 7, 8, 9
Drugi zestaw: 1, 2, 4, 5
Następnie 7, 8, 9 sprawia, że druga tablica zamienia (przesuwa się w lewo o jeden) wszystkie swoje elementy za każdym razem, aby umieścić się w ostatniej.
Zatem tutaj założenie, że zamiana przedmiotów jest pomijalne w porównaniu z porównywaniem dwóch przedmiotów.
https://github.com/skanagavelu/algorithams/blob/master/src/sorting/MergeSort.java
źródło