Big O: górna granica
„Big O” ( ) jest zdecydowanie najczęstszym. Kiedy analizujesz złożoność algorytmu, przez większość czasu liczy się górna granica tego, jak szybko rośnie czas pracy¹, gdy rośnie wielkość danych wejściowych. Zasadniczo chcemy wiedzieć, że uruchomienie algorytmu nie zajmie „zbyt długo”. Nie możemy tego wyrazić w rzeczywistych jednostkach czasu (sekundach), ponieważ zależałoby to od dokładnej implementacji (sposobu pisania programu, jakości kompilatora, szybkości procesora komputera,…). Oceniamy więc, co nie zależy od takich szczegółów, czyli ile czasu zajmuje uruchomienie algorytmu, gdy dostarczymy mu większe dane wejściowe. I zależy nam przede wszystkim na tym, kiedy możemy być pewni, że program został ukończony, dlatego zwykle chcemy wiedzieć, że zajmie to tak długo taki lub mniej czas.O
Stwierdzenie, że algorytm ma czas działania dla rozmiaru wejściowego oznacza, że istnieje pewna stała tak że algorytm wykonuje się co najwyżej w krokach , tj. Czas działania algorytmu rośnie co najwyżej tak szybko, jak (do współczynnika skalowania). Odnotowanie czasu działania algorytmu dla wielkości wejściowej , nieformalnie oznacza, że do pewnego współczynnika skalowania.n K KO(f(n))nKf T ( n ) n O ( n ) T ( n ) ≤ f ( n )Kf(n)fT(n)nO(n)T(n)≤f(n)
Dolna granica
Czasami przydaje się mieć więcej informacji niż górna granica. jest odwrotnością : wyraża, że funkcja rośnie co najmniej tak szybko, jak inna. oznacza, że dla pewnego stałego , lub mówiąc nieformalnie, górę do pewnego współczynnika skalowania.ΩOT(n)=Ω(g(n))T(N)≥K′g(n)K′T(n)≥g(n)
Kiedy czas działania algorytmu można precyzyjnie określić, łączy i : wyraża to, że znane jest tempo wzrostu funkcji, aż do współczynnika skalowania. oznacza, że dla niektórych stałych i . Mówiąc nieformalnie, do pewnego współczynnika skalowania.ΘOΩT(n)=Θ(h(n))Kh(n)≥T(n)≥K′h(n)KK′T(n)≈h(n)
Dalsze uwagi
„Małe” i są używane znacznie rzadziej w analizie złożoności. Mała jest silniejsza niż duża ; gdzie oznacza wzrost, który nie jest szybszy, oznacza, że wzrost jest ściśle wolniejszy. I odwrotnie, wskazuje na zdecydowanie szybszy wzrost.oωoOOoω
Byłem nieco nieformalny w powyższej dyskusji. Wikipedia ma formalne definicje i bardziej matematyczne podejście.
Należy pamiętać, że użycie znaku równości w i tym podobne jest błędne. Ściśle mówiąc, jest zbiorem funkcji zmiennej i powinniśmy pisać .T(n)=O(f(n))O(f(n))nT∈O(f)
Przykład: niektóre algorytmy sortowania
Ponieważ jest to raczej suche, dam przykład. Większość algorytmów sortowania ma kwadratowy najgorszy czas działania, tj. Dla danych wejściowych o rozmiarze czas działania algorytmu wynosi . Na przykład sortowanie selekcji ma czas działania , ponieważ wybranie tego elementu wymaga porównań , co daje łącznie porównania . W rzeczywistości liczba porównań wynosi zawsze dokładnie , co rośnie jako . Możemy więc dokładniej określić złożoność czasową sortowania selekcji: jest to .nO(n2)O(n2)kn−kn(n−1)/2n(n−1)/2n2Θ(n2)
Teraz weź sortowanie korespondencji seryjnej . Sortowanie po scaleniu jest również kwadratowe ( ). To prawda, ale niezbyt precyzyjna. W rzeczywistości sortowanie korespondencji ma w najgorszym przypadku czas działania . Podobnie jak sortowanie selekcyjne, przepływ pracy sortowania scalającego jest zasadniczo niezależny od kształtu danych wejściowych, a jego czas działania wynosi zawsze do stałego mnożnika, tzn. Jest to .O(n2)O(nlg(n))nlg(n)Θ(nlg(n))
Następnie rozważ szybkie sortowanie . Quicksort jest bardziej złożony. Z pewnością jest to . Co więcej, najgorszy przypadek szybkiego sortowania jest kwadratowy: najgorszym przypadkiem jest . Jednak najlepszy przypadek szybkiego sortowania (gdy dane wejściowe są już posortowane) ma charakter liniowy: najlepsze, co możemy powiedzieć dla dolnej granicy szybkiego segmentu w ogóle, to . Nie powtórzę tutaj dowodu, ale średnia złożoność quicksort (średnia przejęta przez wszystkie możliwe kombinacje danych wejściowych) wynosi .O(n2)Θ(n2)Ω(n)Θ(nlg(n))
Istnieją ogólne wyniki dotyczące złożoności algorytmów sortowania w typowych ustawieniach. Załóżmy, że algorytm sortowania może porównywać tylko dwa elementy jednocześnie, z wynikiem tak lub nie ( lub ). Jest zatem oczywiste, że czas działania dowolnego algorytmu sortującego to zawsze (gdzie jest liczbą elementów do posortowania), ponieważ algorytm musi przynajmniej raz porównać każdy element, aby wiedzieć, gdzie będzie pasował. Ta dolna granica może być spełniona, na przykład, jeśli dane wejściowe są już posortowane, a algorytm po prostu porównuje każdy element z następnym i utrzymuje je w porządku (to porównania ). Mniej oczywiste jest to, że maksymalny czas działania jest koniecznyx≤yx>yΩ(n)nn−1Ω(nlg(n)) . Możliwe jest, że algorytm będzie czasem dokonywał mniej porównań, ale musi istnieć pewna stała tak że dla dowolnego rozmiaru wejściowego istnieje co najmniej jedno wejście, na którym algorytm daje więcej niż porównania. Ideą dowodu jest zbudowanie drzewa decyzyjnego algorytmu, tj. Śledzenie decyzji algorytmu na podstawie wyników każdego porównania. Ponieważ każde porównanie zwraca wynik „tak lub nie”, drzewo decyzyjne jest drzewem binarnym. Jestmożliwe permutacje danych wejściowych, a algorytm musi rozróżnić je wszystkie, więc wielkość drzewa decyzyjnego wynosiKnKnlg(n)n!n!. Ponieważ drzewo jest drzewem binarnym, do dopasowania wszystkich tych węzłów potrzeba . Głębokość to maksymalna liczba decyzji podejmowanych przez algorytm, więc uruchomienie algorytmu obejmuje co najmniej tyle porównań: maksymalny czas działania wynosi .Θ(lg(n!))=Θ(nlg(n))Ω(nlg(n))
¹ Lub inne zużycie zasobów, takie jak miejsce w pamięci. W tej odpowiedzi rozważam tylko czas działania.