Istnieje wiele pytań dotyczących sposobu analizowania czasu pracy algorytmów (patrz np runtime-analiza i algorytm-analysis ). Wiele z nich jest podobnych, na przykład ci, którzy proszą o analizę kosztów zagnieżdżonych pętli lub algorytmy dzielenia i zdobywania, ale większość odpowiedzi wydaje się być dostosowana do indywidualnych potrzeb.
Z drugiej strony odpowiedzi na inne ogólne pytanie wyjaśniają szerszy obraz (w szczególności w odniesieniu do analizy asymptotycznej) kilkoma przykładami, ale nie pokazują, jak zabrudzić sobie ręce.
Czy istnieje ustrukturyzowana, ogólna metoda analizy kosztów algorytmów? Kosztem może być czas działania (złożoność czasu) lub inna miara kosztów, taka jak liczba wykonanych porównań, złożoność przestrzeni lub coś innego.
To ma stać się pytaniem referencyjnym, które można wykorzystać do wskazania początkującym; stąd jego szerszy niż zwykle zakres. Prosimy o podanie ogólnych, dydaktycznie przedstawionych odpowiedzi, które są zilustrowane co najmniej jednym przykładem, ale mimo to obejmują wiele sytuacji. Dzięki!
Odpowiedzi:
Tłumaczenie kodu na matematykę
Biorąc pod uwagę (mniej lub bardziej) formalną semantykę operacyjną , możesz dosłownie przetłumaczyć (pseudo-) kod algorytmu na wyrażenie matematyczne, które daje wynik, pod warunkiem, że możesz manipulować tym wyrażeniem w użytecznej formie. Działa to dobrze w przypadku dodatkowych miar kosztów, takich jak liczba porównań, zamiany, zestawienia, dostęp do pamięci, cykle niektórych abstrakcyjnych potrzeb maszyny i tak dalej.
Przykład: Porównania w Bubblesort
Rozważ ten algorytm, który sortuje daną tablicę
A
:Powiedzmy, że chcemy przeprowadzić zwykłą analizę algorytmu sortowania, czyli policzyć liczbę porównań elementów (wiersz 5). Od razu zauważamy, że ta ilość nie zależy od zawartości tablicyn
A
, tylko od jej długości . Możemy więc przetłumaczyć (zagnieżdżone) pętle dosłownie na (zagnieżdżone) sumy; zmienna pętli staje się zmienną sumującą, a zakres jest przenoszony. Otrzymujemy:for
,docmp( n ) = ∑ja= 0n- 2∑jot= 0n -i - 21 = ⋯ = n ( n - 1 )2)= ( n2))
gdzie to koszt każdej realizacji linii 5 (którą liczymy).1
Przykład: zamiana w Bubblesort
Przez oznaczę podprogram składający się z wierszy do i przez C i , j koszty wykonania tego podprogramu (jeden raz).P.ja , j doja , j
i
j
Powiedzmy, że chcemy policzyć swapy , czyli jak często jest wykonywane. Jest to „blok podstawowy”, czyli podprogram, który jest zawsze wykonywany atomowo i ma pewien stały koszt (tutaj 1 ). Kontrakty na takie bloki to jedno z przydatnych uproszczeń, które często stosujemy bez zastanowienia się nad tym.P.6 , 8 1
Z podobnym tłumaczeniem jak powyżej dochodzimy do następującej formuły:
.dozamiana( A ) = ∑i = 0n - 2∑j = 0n - i - 2do5 , 9( A( i , j ))
oznacza stan tablicy przed ( i , j ) -tą iteracją P 5 , 9 .ZA( i , j ) ( i , j ) P.5 , 9
Zauważ, że używam zamiast n jako parametru; wkrótce zobaczymy dlaczego. Nie dodam i i j jako parametrów C 5 , 9, ponieważ koszty nie zależą od nich tutaj ( to znaczy w modelu kosztu jednolitego ); w ogóle mogą.ZA n ja jot do5 , 9
Oczywiście koszty zależą od zawartości A (wartości, a konkretnie), więc musimy to uwzględnić. Teraz stajemy przed wyzwaniem: w jaki sposób „rozpakowujemy” C 5 , 9 ? Cóż, możemy uczynić zależność od treści A wyraźnym:P.5 , 9 ZA do5 , 9 ZA
A[j]
A[j+1]
.do5 , 9( A( i , j )) = C5( A( i , j )) + { 10, A( i , j )[ j ] > A( i , j )[ j + 1 ], jeszcze
W przypadku dowolnej tablicy wejściowej koszty te są dobrze określone, ale chcemy bardziej ogólnego zestawienia; musimy przyjąć silniejsze założenia. Przeanalizujmy trzy typowe przypadki.
Najgorszy przypadek
Wystarczy spojrzeć na sumę i zauważyć, że , możemy znaleźć trywialną górną granicę kosztów:do5 , 9( A( i , j )) ∈ { 0 , 1 }
.dozamiana( A ) ≤ ∑i = 0n - 2∑j = 0n - i - 21 = n ( n - 1 )2)= ( n2))
Ale czy może się to zdarzyć , tj. Czy osiągnięto dla tej górnej granicy? Jak się okazuje, tak: jeśli wprowadzimy odwrotnie posortowaną tablicę odrębnych parami elementów, każda iteracja musi wykonać swap¹. Dlatego wyprowadziliśmy dokładną liczbę najgorszych przypadków zamiany Bubblesort.ZA
Najlepszy przypadek
I odwrotnie, istnieje trywialna dolna granica:
.dozamiana( A ) ≥ ∑i = 0n - 2∑j = 0n - i - 20 = 0
Może się to również zdarzyć: w tablicy, która jest już posortowana, Bubblesort nie wykonuje pojedynczej zamiany.
Przeciętny przypadek
Najgorszy i najlepszy przypadek otwiera spore luki. Ale jaka jest typowa liczba zamian? Aby odpowiedzieć na to pytanie, musimy zdefiniować, co oznacza „typowy”. Teoretycznie nie mamy powodu, aby preferować jeden wkład nad drugim, dlatego zwykle zakładamy jednolity rozkład wszystkich możliwych danych wejściowych, to znaczy każde wejście jest jednakowo prawdopodobne. Ograniczamy się do tablic z odrębnymi parami elementów, a zatem zakładamy model losowej permutacji .
Następnie możemy przepisać nasze koszty w ten sposób²:
Teraz musimy wyjść poza zwykłą manipulację sumami. Patrząc na algorytm, zauważamy, że każda zamiana usuwa dokładnie jedną inwersję w (wymieniamy tylko sąsiadów3). Oznacza to, że liczba wykonywanych na swapy A jest dokładnie liczba inwersji inw ( A ) z . W ten sposób możemy zastąpić dwie wewnętrzne sumy i uzyskaćZA ZA inv( A ) ZA
.E [ Czamiana] = 1n !∑ZAinv( A )
Na szczęście dla nas ustalono średnią liczbę inwersji
co jest naszym końcowym wynikiem. Należy pamiętać, że jest to dokładnie połowa najgorszych kosztów.
i = n-1
nie została wykonana „ostatnia” iteracja z zewnętrzną pętlą, która nigdy nic nie robi.Ogólna metoda
W przykładzie widzieliśmy, że musimy przełożyć strukturę kontroli na matematykę; Przedstawię typowy zestaw reguł tłumaczenia. Zauważyliśmy również, że koszt dowolnego podprogramu może zależeć od aktualnego stanu , czyli (z grubsza) bieżących wartości zmiennych. Ponieważ algorytm (zwykle) modyfikuje stan, ogólna metoda jest nieco trudna do odnotowania. Jeśli poczujesz się zdezorientowany, sugeruję, abyś wrócił do przykładu lub sam wymyślił.
Oznaczamy bieżącym stanem (wyobraź sobie, że jest to zestaw przypisań zmiennych). Kiedy wykonujemy program zaczynający się w stanie ψ , kończymy w stanie ψ / P (pod warunkiem, że zakończy się).ψ ψ ψ / P
P
P
Indywidualne oświadczenia
Biorąc pod uwagę tylko jedną instrukcjędoS.( ψ )
S;
, przypisujesz jej koszt . Zazwyczaj będzie to stała funkcja.Wyrażenia
Jeśli masz wyrażenie
E
formularzaE1 ∘ E2
(powiedzmy, wyrażenie arytmetyczne, gdzie∘
może być dodawanie lub mnożenie, sumujesz koszty rekurencyjnie:.domi( ψ ) = c∘+ C.mi1( ψ ) + Cmi2)( ψ )
Zauważ, że
więc być może będziesz musiał być elastyczny z tą regułą.
Sekwencja
Biorąc pod uwagę program
P
jako sekwencję programówQ;R
, dodajesz koszty do.doP.( ψ ) = CQ( ψ ) + CR( ψ / Q )
Warunkowe
Biorąc pod uwagę program
P
formularzaif A then Q else R end
, koszty zależą od stanu:Ogólnie rzecz biorąc, ocena
A
może bardzo dobrze zmienić stan, stąd aktualizacja kosztów poszczególnych oddziałów.For-Loops
Biorąc pod uwagę program
P
formularzafor x = [x1, ..., xk] do Q end
, przypisz kosztygdzie jest stanem przed przetwarzaniem wartości , tj. po iteracji z ustawieniem na ..., .ψja
Q
xi
x
x1
xi-1
Zwróć uwagę na dodatkowe stałe dla utrzymania pętli; należy utworzyć zmienną pętli ( ) i przypisać jej wartości ( c step_for ). Jest to istotne, ponieważdoinit_for dostep_for
xi
może być kosztowne ifor
-loop z pustym korpusie (np po uproszczeniu w zachodzącym z określonym kosztem w najlepszym przypadku) nie ma zerowe koszty, jeżeli spełnia ona iteracji.Podczas pętli
Biorąc pod uwagę program
P
formularzawhile A do Q end
, przypisz kosztyPo sprawdzeniu algorytmu ta powtarzalność często może być ładnie przedstawiona jako suma podobna do sumy dla pętli for.
Przykład: Rozważ ten krótki algorytm:
Stosując regułę, otrzymujemy
i
x
i
To rozwiązuje z podstawowych środków do
Wywołania procedur
Biorąc pod uwagę program
P
formularzaM(x)
dla niektórych parametrów,x
gdzieM
jest procedura z (nazwanym) parametremp
, przypisz kosztyZastanawiam się nad niektórymi problemami semantycznymi, które możesz mieć z tym państwem tutaj. Będziesz chciał rozróżnić stan globalny i takie lokalne wywołania procedur. Załóżmy, że przekazujemy tutaj tylko stan globalny i
M
otrzymujemy nowy stan lokalny, inicjowany przez ustawienie wartościp
nax
. Ponadtox
może być wyrażeniem, które (zwykle) zakładamy, że zostanie ocenione przed przekazaniem.Przykład: Rozważ procedurę
Zgodnie z regułami otrzymujemy:
Pamiętaj, że pomijamy stan globalny, ponieważ
fac
wyraźnie nie ma dostępu do żadnego. Ten konkretny nawrót jest łatwy do rozwiązaniaOmówiliśmy funkcje językowe, które napotkasz w typowym pseudo-kodzie. Uważaj na ukryte koszty podczas analizy pseudo kodu wysokiego poziomu; w razie wątpliwości rozwiń się. Notacja może wydawać się nieporęczna iz pewnością jest kwestią gustu; wymienionych pojęć nie można jednak zignorować. Jednak z pewnym doświadczeniem będziesz mógł od razu zobaczyć, które części stanu są istotne dla której miary kosztów, na przykład „rozmiar problemu” lub „liczba wierzchołków”. Resztę można upuścić - to znacznie upraszcza sprawę!
Jeśli uważasz, że teraz jest to zbyt skomplikowane, informujemy: to jest ! Wyprowadzenie dokładnych kosztów algorytmów w dowolnym modelu, który jest tak blisko rzeczywistych maszyn, że umożliwia przewidywanie czasu wykonywania (nawet względnego), jest trudnym przedsięwzięciem. I to nawet nie bierze pod uwagę buforowania i innych nieprzyjemnych efektów na prawdziwych maszynach.
Dlatego analiza algorytmów jest często upraszczana do tego stopnia, że jest matematycznie wykonalna. Na przykład, jeśli nie potrzebujesz dokładnych kosztów, możesz przeszacować lub niedocenić w dowolnym momencie (dla górnej lub dolnej granicy): zmniejszyć zestaw stałych, pozbyć się warunków warunkowych, uprościć sumy i tak dalej.
Uwaga na temat kosztów asymptotycznych
Jest to (często) uczciwe, ponieważ abstrakcyjne stwierdzenia mają pewne (generalnie nieznane) koszty w rzeczywistości, w zależności od maszyny, systemu operacyjnego i innych czynników, a krótkie czasy działania mogą być zdominowane przez system operacyjny ustawiający proces przede wszystkim. W każdym razie masz jakieś zaburzenia.
Oto jak analiza asymptotyczna odnosi się do tego podejścia.
Zidentyfikuj dominujące operacje (które powodują koszty), to znaczy operacje, które występują najczęściej (aż do stałych czynników). W przykładzie Bubblesort możliwym wyborem jest porównanie w linii 5.
Alternatywnie, powiąż wszystkie stałe operacji elementarnych ich maksymalną (z góry) wzgl. ich minimum (od dołu) i wykonaj zwykłą analizę.
Dalsza lektura
Istnieje wiele innych wyzwań i sztuczek w analizie algorytmów. Oto kilka zalecanych lektur.
Jak opisywać algorytmy, je udowadniać i analizować?
Jak możemy założyć, że podstawowe operacje na liczbach wymagają stałego czasu?
Co stanowi jedną jednostkę czasu w analizie środowiska wykonawczego?
Istnieje wiele pytań oznaczonych analizą algorytmów, które wykorzystują techniki podobne do tego.
źródło
Liczba wykonanych wyciągów
Istnieje inna metoda, której mistrz Donald E. Knuth w swojej serii The Art of Computer Programming . W przeciwieństwie do tłumaczenia całego algorytmu na jedną formułę , działa on niezależnie od semantyki kodu po stronie „zestawiania rzeczy” i pozwala zejść na niższy poziom tylko wtedy, gdy jest to konieczne, zaczynając od widoku „orła oka”. Każde stwierdzenie może być analizowane niezależnie od reszty, co prowadzi do bardziej przejrzystych obliczeń. Jednak technika ta dobrze nadaje się do dość szczegółowego kodu, nie tyle pseudo kodu wyższego poziomu.
Metoda
Zasadniczo jest to dość proste:
Oblicz całkowite koszty
Możesz wstawić oszacowania i / lub wielkości symboliczne w dowolnym punkcie, osłabiając odpowiednio. odpowiednio uogólniając wynik.
Przykład: wyszukiwanie w pierwszej kolejności
Rozważ następujący algorytm przechodzenia przez wykres:
foo
dfs
while
foo
To prowadzi nas do całkowitych kosztów dokładnie
i dostać
Dalsza lektura
Zobacz na dole mojej drugiej odpowiedzi .
źródło
Analiza algorytmów, podobnie jak dowodzenie twierdzeń, jest w dużej mierze sztuką (np. Istnieją proste programy (takie jak problem Collatza) ), których nie umiemy analizować). Możemy przekonwertować problem złożoności algorytmu na matematyczny, as kompleksowo odpowiedział Raphael , ale następnie, aby wyrazić zależność od kosztu algorytmu w zakresie znanych funkcji, musimy:
źródło