Zasadniczo chcesz wiedzieć, czy istnieje algorytm sortowania, który nie obniżyłby się w porównaniu ze średnim przypadkiem, gdyby podano funkcję porównania podobną do:
int Compare(object a, object b) { return Random.Next(-1,1); }
... gdzie Random.Next () to metoda, która wygeneruje losowo wygenerowaną liczbę całkowitą między określoną dolną i górną granicą włącznie.
Odpowiedź jest taka, że większość podstawowych algorytmów sortowania będzie działać zgodnie z ich średnią wielkością, ponieważ spełniają co najmniej jeden z następujących dwóch warunków:
- Porównanie dwóch unikalnych elementów nigdy nie jest wykonywane dwukrotnie w tym samym sortowaniu i / lub
- W każdej iteracji tego rodzaju ustalana jest poprawna pozycja co najmniej jednego elementu, dzięki czemu element nigdy nie jest ponownie porównywany.
Na przykład SelectionSort dokonuje iteracji przez podlistę nieposortowanych elementów, znajduje element „najmniejszy” i / lub „największy” (przez porównanie każdego z największych jak dotąd), umieszcza go we właściwej pozycji i powtarza. W rezultacie, nawet przy niedeterministycznym komparatorze, pod koniec każdej iteracji algorytm znajdzie wartość, którą uważa za najmniejszą lub największą, zamienia ją na element w pozycji, którą próbuje określić i nigdy nie bierze pod uwagę ten element ponownie, dlatego spełnia warunek 2. Jednak A i B mogą być wielokrotnie porównywane podczas tego procesu (jako najbardziej ekstremalny przykład rozważ kilka przejść SelectionSort na tablicy posortowanej w odwrotnej kolejności), więc narusza warunek 1 .
MergeSort spełnia warunek 1, ale nie 2; w miarę łączenia się pod-macierzy elementy w tej samej pod-macierzy (po lewej lub po prawej stronie) nie są porównywane ze sobą, ponieważ już ustalono, że elementy po tej stronie tablicy są między sobą w porządku; algorytm porównuje tylko najmniej niezablokowany element każdej podtablicy z drugą, aby ustalić, która jest mniejsza i powinna przejść dalej na scalonej liście. Oznacza to, że dowolne dwa unikalne obiekty A i B zostaną porównane maksymalnie jeden raz, ale „ostateczny” indeks danego elementu w pełnej kolekcji nie jest znany, dopóki algorytm nie zostanie ukończony.
InsertionSort spełnia również tylko Warunek 1, mimo że jego ogólna strategia i złożoność bardziej przypomina SelectionSort. Każdy nieposortowany element jest porównywany z posortowanymi elementami, poczynając od największego, aż do znalezienia jednego mniejszego niż badany element. element jest wstawiany w tym punkcie, a następnie rozważany jest następny element. Rezultat jest taki, że względna kolejność dowolnego A i B jest określona przez jedno porównanie, a dalsze porównania między tym A i B nigdy nie są wykonywane, ale końcowa pozycja dowolnego elementu nie może być znana, dopóki wszystkie elementy nie zostaną uwzględnione.
QuickSort przestrzega obu tych zasadWarunki. Na każdym poziomie wybiera się i ustawia czop w taki sposób, że „lewa” strona zawiera elementy mniejsze niż czop, a „prawa” strona zawiera elementy większe niż czop. Wynikiem tego poziomu jest QuickSort (lewy) + pivot + QuickSort (prawy), co w zasadzie oznacza, że pozycja elementu przestawnego jest znana (jeden indeks większy niż długość lewej strony), oś nigdy nie jest porównywana z żadnym innym elementem po wybraniu go jako elementu przestawnego (można go porównać z poprzednimi elementami elementu przestawnego, ale elementy te są również znane i nie są zawarte w żadnych podgrupach) ORAZ wszelkie elementy A i B, które kończą się po przeciwnych stronach elementu przestawnego, nigdy nie są w porównaniu W większości implementacji czystego QuickSort, przypadek podstawowy jest jednym elementem, w którym to momencie jego bieżący indeks jest indeksem końcowym i nie dokonuje się dalszych porównań.
Jedyny rodzaj porównawczy, jaki mogę wymyślić, który nie spełniałby żadnego z tych warunków, to niezoptymalizowany BubbleSort. Jeśli sortowanie nie zaakceptuje, że X największych elementów jest na swoim właściwym miejscu po uruchomieniu pasaży X i / lub użyje przepustki „podwójnego sprawdzania”, aby sprawdzić, czy lista jest posortowana, sortowanie zostanie uznane za „wykonane”, gdy losowo komparator powrócił 1 lub 0 do każdych dwóch sąsiednich elementów listy podczas przechodzenia, a zatem nie swapy wykonywanych (zdarzenia, które, jeśli w pełni losowe, mogłoby nastąpić z prawdopodobieństwem , dla stosunkowo mała lista 25 elementów, to szansa na jeden w 2000 roku, podczas gdy dla 100 elementów prawdopodobieństwo wynosi 3,7 * 10 -18( 2 / 3 )N.- 1). W miarę wzrostu maksymalnej wartości bezwzględnej wyniku komparatora prawdopodobieństwo, że jedno porównanie zwróci wartość ujemną lub zero, zmniejsza się w kierunku .5, co sprawia, że prawdopodobieństwo zakończenia algorytmu jest znacznie mniej prawdopodobne (szansa 99 monet przewraca wszystkie głowice lądujące , co w zasadzie sprowadza się do tego, wynosi 1 na 1,2 * 10 30 )
EDYCJA DŁUGIEGO PÓŹNIEJ: Istnieje kilka „rodzajów” zaprojektowanych specjalnie jako przykłady tego, czego nie należy robić, i które zawierają losowy komparator; być może najbardziej znanym jest BogoSort. „Biorąc pod uwagę listę, jeśli lista nie jest w porządku, przetasuj listę i sprawdź ponownie”. Teoretycznie ostatecznie trafi na właściwą permutację wartości, podobnie jak powyżej „niezoptymalizowany BubbleSort”, ale przeciętny przypadek to czas czynnikowy (N! / 2), a także z powodu problemu urodzinowego (po wystarczającej liczbie losowych permutacji ty bardziej prawdopodobne jest, że napotkamy duplikaty permutacji niż unikatowe) istnieje niezerowa możliwość, że algorytm nigdy się nie zakończy, aby oficjalnie algorytm nie był związany z czasem.