Programowanie mocy: O (1 ^ N), O (N ^ 1), O (2 ^ N), O (N ^ 2) wszystko w jednym


65

Napisz program (lub funkcję), który wykazuje cztery typowe złożone złożoności czasu O w zależności od tego, jak jest uruchamiany. W dowolnej formie przyjmuje dodatnią liczbę całkowitą N, którą możesz założyć, że jest mniejsza niż 2 31 .

  1. Gdy program jest uruchamiany w oryginalnej formie, powinien mieć stałą złożoność. Oznacza to, że złożoność powinna wynosić Θ (1) lub równoważnie Θ (1 ^ N) .

  2. Gdy program zostanie odwrócony i uruchomiony, powinien mieć złożoność liniową . Oznacza to, że złożoność powinna wynosić Θ (N) lub równoważnie Θ (N ^ 1) .
    (Ma to sens, ponieważ N^1jest 1^Nodwrócone).

  3. Gdy program zostanie podwojona , czyli łączone do siebie i uruchomić powinien mieć wykładniczą złożoność, a konkretnie 2 N . Oznacza to, że złożoność powinna wynosić Θ (2 ^ N) .
    (Ma to sens, ponieważ 2in 2^Njest podwójnie 1in 1^N.)

  4. Gdy program zostanie podwojona i odwrócone i uruchomić powinien mieć wielomian złożoności, a konkretnie N 2 . Oznacza to, że złożoność powinna wynosić Θ (N ^ 2) .
    (Ma to sens, ponieważ N^2jest 2^Nodwrócone).

Te cztery przypadki są jedynymi, z którymi musisz sobie poradzić.

Zauważ, że dla dokładności używam dużej theta (Θ) zamiast dużej O, ponieważ środowiska wykonawcze programów muszą być ograniczone zarówno powyżej, jak i poniżej wymaganymi złożonościami. W przeciwnym razie samo napisanie funkcji w O (1) spełni wszystkie cztery punkty. Nie jest zbyt ważne, aby zrozumieć niuans tutaj. Głównie, jeśli twój program wykonuje operacje k * f (N) dla pewnej stałej k, to prawdopodobnie jest to Θ (f (N)).

Przykład

Jeśli oryginalny program był

ABCDE

następnie uruchomienie powinno zająć cały czas. To znaczy, czy wejście N wynosi 1, czy 2147483647 (2 31 -1), czy jakakolwiek wartość pomiędzy nimi, powinno zakończyć się mniej więcej w tym samym czasie.

Odwrócona wersja programu

EDCBA

powinien zająć czas liniowy w odniesieniu do N. To znaczy, czas potrzebny do zakończenia powinien być w przybliżeniu proporcjonalny do N. Tak więc N = 1 zajmuje najmniej czasu, a N = 2147483647 zajmuje najwięcej.

Podwójna wersja programu

ABCDEABCDE

Należy wziąć dwa-do-N godziny w kategoriach N. Oznacza to, że czas potrzebny do rozwiązania powinny być w przybliżeniu proporcjonalna do 2 N . Więc jeśli N = 1 kończy się w ciągu około sekundy, N = 60 zajęłoby więcej niż wiek wszechświata, aby zakończyć. (Nie, nie musisz tego testować.)

Podwojona i odwrócona wersja programu

EDCBAEDCBA

powinien zająć do kwadratu czas w kategoriach N. Oznacza to, że czas potrzebny do zakończenia powinien być w przybliżeniu proporcjonalny do N * N. Więc jeśli N = 1 zakończy się za około sekundę, N = 60 zajmie około godziny.

Detale

  • Musisz pokazać lub argumentować, że twoje programy działają w złożoności, o której mówisz, że są. Podanie pewnych danych dotyczących czasu jest dobrym pomysłem, ale spróbuj także wyjaśnić, dlaczego teoretycznie złożoność jest poprawna.

  • Jest w porządku, jeśli w praktyce czasy, w których twoje programy nie są w pełni reprezentatywne dla ich złożoności (a nawet determinizmu). np. wejście N + 1 może czasami działać szybciej niż N.

  • Środowisko, w którym uruchamiasz swoje programy, ma znaczenie. Możesz przyjąć podstawowe założenia dotyczące tego, że popularne języki nigdy celowo nie marnują czasu na algorytmy, ale na przykład, jeśli wiesz, że twoja konkretna wersja Java implementuje sortowanie bąbelkowe zamiast szybszego algorytmu sortowania , powinieneś wziąć to pod uwagę, jeśli wykonujesz jakiekolwiek sortowanie .

  • Dla wszystkich zawiłości tutaj załóżmy, że mówimy o scenariuszach najgorszych przypadków , a nie najlepszych lub średnich.

  • Złożoność przestrzenna programów nie ma znaczenia, tylko złożoność czasowa.

  • Programy mogą wysyłać cokolwiek. Liczy się tylko to, że przyjmują dodatnią liczbę całkowitą N i mają prawidłową złożoność czasową.

  • Komentarze i programy wieloliniowe są dozwolone. (Możesz założyć, że \r\nodwrócona jest \r\nzgodna z Windows).

Wielkie przypomnienia

Od najszybszego do najwolniejszego O(1), O(N), O(N^2), O(2^N)(kolejność 1, 2, 4, 3 powyżej).

Wolniejsze terminy zawsze dominują, np O(2^N + N^2 + N) = O(2^N).

O(k*f(N)) = O(f(N))dla stałej k. Więc O(2) = O(30) = O(1)i O(2*N) = O(0.1*N) = O(N).

Pamiętaj O(N^2) != O(N^3)i O(2^N) != O(3^N).

Zgrabny duży ściągawka O.

Punktacja

To normalny kod golfowy. Najkrótszy oryginalny program (czas stały) w bajtach wygrywa.


Doskonałe pytanie! Drobny punkt: nie musimy określać najgorszego / najlepszego przypadku / średniego przypadku, ponieważ jedynym wejściem, który liczy się jako rozmiar N, jest sama liczba N (która BTW nie jest zwykłym pojęciem wielkości wejściowej: traktuj wejście N jako logarytmu wielkości N). Zatem dla każdego N jest tylko jeden przypadek, który jest jednocześnie najlepszym, najgorszym i przeciętnym przypadkiem.
ShreevatsaR

5
Wygląda na to, że odstąpiłeś od zwykłych definicji złożoności. Zawsze definiujemy złożoność czasową algorytmu w zależności od wielkości jego danych wejściowych . W przypadku, gdy dane wejściowe są liczbą, wielkość danych wejściowych jest logarytmem podstawowym 2 tej liczby. Tak więc program n = input(); for i in xrange(n): passma wykładniczą złożoność, ponieważ wykonuje 2 ** kkroki, gdzie k = log_2(n)jest wielkość wejściowa. Należy wyjaśnić, czy tak jest, ponieważ radykalnie zmienia to wymagania.
wchargin

Odpowiedzi:


36

Python 3 , 102 bajty

try:l=eval(input());k=1#)]0[*k**l(tnirp
except:k=2#2=k:tpecxe
print(k**l*[0])#1=k;))(tupni(lave=l:yrt

Wypróbuj online!

Jest to O (1 ^ n), ponieważ to właśnie robi program:

  • ocenić dane wejściowe
  • utwórz tablicę [0]
  • Wydrukuj to

Wywrócony:


try:l=eval(input());k=1#)]0[*l**k(tnirp
except:k=2#2=k:tpecxe
print(l**k*[0])#1=k;))(tupni(lave=l:yrt

Wypróbuj online!

Jest to O (n ^ 1), ponieważ to właśnie robi program:

  • ocenić dane wejściowe
  • utwórz tablicę [0] * wejście (0 powtórzone tyle razy, ile dane wejściowe)
  • Wydrukuj to

Podwojony:

try:l=eval(input());k=1#)]0[*k**l(tnirp
except:k=2#2=k:tpecxe
print(k**l*[0])#1=k;))(tupni(lave=l:yrt
try:l=eval(input());k=1#)]0[*k**l(tnirp
except:k=2#2=k:tpecxe
print(k**l*[0])#1=k;))(tupni(lave=l:yrt

Wypróbuj online!

Jest to O (2 ^ n), ponieważ to właśnie robi program:

  • ocenić dane wejściowe
  • utwórz tablicę [0]
  • Wydrukuj to
  • spróbuj ocenić dane wejściowe
  • zawieść
  • utwórz tablicę [0] * (wejście 2 ^) (0 powtórzone tyle razy, ile wejście 2 ^)
  • Wydrukuj to

Podwojone i odwrócone:


try:l=eval(input());k=1#)]0[*l**k(tnirp
except:k=2#2=k:tpecxe
print(l**k*[0])#1=k;))(tupni(lave=l:yrt
try:l=eval(input());k=1#)]0[*l**k(tnirp
except:k=2#2=k:tpecxe
print(l**k*[0])#1=k;))(tupni(lave=l:yrt

Wypróbuj online!

Jest to O (n ^ 2), ponieważ to właśnie robi program:

  • ocenić dane wejściowe
  • utwórz tablicę [0] * wejście (0 powtórzone tyle razy, ile dane wejściowe)
  • Wydrukuj to
  • spróbuj ocenić dane wejściowe
  • zawieść
  • utwórz tablicę [0] * (wejście ^ 2) (0 powtórzone tyle razy, ile wejście do kwadratu)
  • Wydrukuj to

Dlaczego nie zawiesza się w oczekiwaniu na interakcję użytkownika, gdy jest wiele połączeń z input()?
Jonathan Allan

1
Czy istnieje luka, że ​​„koniec transmisji” jest przesyłany na końcu transmisji?
Leaky Nun

1
Możesz to wyjaśnić?
Brain Guider

1
Re: argument „koniec pliku”, patrzysz na rzeczy wstecz. Gdy używasz terminala, prośby o wejście zawieszają się, ponieważ coś jest z nim związane; ctrl-D można wysłać, aby jawnie nie wysyłać żadnych danych wejściowych. Jeśli dane wejściowe są podłączone do pustego pliku (tak jak robi to TIO, jeśli pole wprowadzania pozostawia się puste) lub jeśli jest podłączone do pliku, w którym wszystkie dane wejściowe zostały odczytane, prośba o dane wejściowe nie jest konieczna; już wie, że nie ma danych wejściowych. W systemach UNIX / Linux „koniec pliku” i „brak dostępnych danych wejściowych” są nierozróżnialne w przypadku zwykłych plików.

3
W pierwszym przypadku, kczy dane wejściowe lsą jedno, więc nadal pracujesz 1**k, prawda? Co powinno zająć trochę O(log(k))czasu, mimo że ty, ja i wszyscy wiemy z góry, że to jedno?
Richard Rast

18

Perl 5, 82 73 71 + 1 (dla flagi -n) = 72 bajty

Jestem pewien, że mogę grać w golfa (dużo) bardziej, ale przed snem spędziłem wystarczająco dużo czasu na debugowaniu i jestem dumny z tego, co do tej pory mam.

#x$=_$;
$x.=q;#say for(1..2**$_)#)_$..1(rof _$=+x$;;
eval $x;$x=~s/#//;

Sam program nie korzysta z danych wejściowych, po prostu ocenia ciąg rozpoczynający się od komentarza, a następnie dokonuje podstawienia pojedynczego ciągu, więc jest to wyraźnie w stałym czasie. Jest to w zasadzie odpowiednik:

$x="#";
eval $x;
$x=~s/#//;

Podwojony:

#x$=_$;
$x.=q;#say for(1..2**$_)#)_$..1(rof _$=+x$;;
eval $x;$x=~s/#//;
#x$=_$;
$x.=q;#say for(1..2**$_)#)_$..1(rof _$=+x$;;
eval $x;$x=~s/#//;

Bit, który faktycznie zajmuje czas wykładniczy, jest drugim eval: ocenia polecenie say for(1..2**$_), które wyświetla wszystkie liczby od 1 do 2 ^ N, co wyraźnie zajmuje czas wykładniczy.

Wywrócony:

;//#/s~=x$;x$ lave
;;$x+=$_ for(1..$_)#)_$**2..1(rof yas#;q=.x$
;$_=$x#

To (naiwnie) oblicza sumę danych wejściowych, co wyraźnie zajmuje czas liniowy (ponieważ każde dodawanie odbywa się w stałym czasie). Uruchomiony kod jest równoważny z:

$x+=$_ for(1..$_);
$_=$x;

Ostatni wiersz jest po prostu taki, że powtórzenie tych poleceń zajmie kwadratowy czas.

Odwrócony i podwojony:

;//#/s~=x$;x$ lave
;;$x+=$_ for(1..$_)#)_$**2..1(rof yas#;q=.x$
;$_=$x#
;//#/s~=x$;x$ lave
;;$x+=$_ for(1..$_)#)_$**2..1(rof yas#;q=.x$
;$_=$x#

To teraz bierze sumowanie sumy danych wejściowych (i dodaje je do sumy danych wejściowych, ale cokolwiek innego). Ponieważ porządkuje N^2dodatki, zajmuje to kwadratowy czas. Jest to w zasadzie odpowiednik:

$x=0;
$x+=$_ for(1..$_);
$_=$x;
$x+=$_ for(1..$_);
$_=$x;

Drugi wiersz zawiera podsumowanie danych wejściowych, Ndodając dodatki, podczas gdy czwarty summation(N)dodaje, czyli O(N^2).


Wspaniały! Robienie tego w głównym języku byłoby trudne! To ma moje poparcie!
Arjun

Dobra robota, to całkiem miłe. Prawdopodobnie miałeś na myśli $x.=q;##say...zamiast $x.=q;#say...(z dwoma #zamiast 1). (To by wyjaśniało, dlaczego policzyłeś 76 bajtów zamiast 75). Powinieneś także liczyć -nflagę na swoim bytecount, ponieważ twój program nie robi wiele bez niej.
Dada

@Dada przypadkowo transponowały evala s///polecenia. Jeśli zrobisz evalpierwszy, potrzebujesz tylko tego #. Dobry chwyt!
Chris

@Chris Racja, to rzeczywiście działa. Być może uda ci się pominąć ostatni #: $x=~s/#//;odwrotne produkcje ;//#/s~=x$, które w twoim kontekście nic nie robią, tak jak przy wiodącym #. (Nie przetestowałem tego jednak). Niezależnie od tego, masz +1!
Dada

@Dada Jeszcze raz niezły chwyt!
Chris

17

Właściwie 20 bajtów

";(1╖╜ⁿr"ƒ"rⁿ@╜╖1(;"

Wypróbuj online!

Wejście: 5

Wynik:

rⁿ@╜╖1(;
[0]
5

Wywrócony:

";(1╖╜@ⁿr"ƒ"rⁿ╜╖1(;"

Wypróbuj online!

Wynik:

rⁿ╜╖1(;
[0, 1, 2, 3, 4]
5

Podwojony:

";(1╖╜ⁿr"ƒ"rⁿ@╜╖1(;"";(1╖╜ⁿr"ƒ"rⁿ@╜╖1(;"

Wypróbuj online!

Wynik:

rⁿ@╜╖1(;
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
rⁿ@╜╖1(;
rⁿ@╜╖1(;
[0]

Podwojone i odwrócone:

";(1╖╜@ⁿr"ƒ"rⁿ╜╖1(;"";(1╖╜@ⁿr"ƒ"rⁿ╜╖1(;"

Wypróbuj online!

Wynik:

rⁿ╜╖1(;
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
rⁿ╜╖1(;
rⁿ╜╖1(;
[0, 1, 2, 3, 4]

Główny pomysł

W rzeczywistości jest językiem stosowym.

  • abcjest programem o złożoności O (1 n ), a jego podwójność ma złożoność O (2 n ).
  • defjest programem, który ma złożoność O (n 1 ), a jego program podwójny ma złożoność O (n 2 ).

Następnie mój wniosek brzmi "abc"ƒ"fed", gdzie ƒjest ocena. W ten sposób "fed"nie będą oceniane.

Generowanie indywidualnego programu

Pseudokod pierwszego komponentu ;(1╖╜ⁿr:

register += 1 # register is default 0
print(range(register**input))

Pseudokod drugiego komponentu ;(1╖╜ⁿ@r:

register += 1 # register is default 0
print(range(input**register))

Nigdy nie myślałem, że to będzie w ogóle możliwe! Świetna robota, proszę pana! +1
Arjun

@Arjun Dziękujemy za uznanie.
Leaky Nun

Jest to doskonałe i naprawdę podnosi wyzwanie, nie wykorzystując komentarzy IMO. Niesamowite!
ShreevatsaR

1
Cóż, tak naprawdę ma komentarze ... ciągi są nieocenione i są NOP ...
Leaky Nun

4

Galaretka , 20 bajtów

Częściowo zainspirowany faktycznym rozwiązaniem Leaky Nun .

Wiodące i końcowe znaki nowej linii są znaczące.

Normalna:


⁵Ŀ⁵
R
R²
2*R
‘
⁵Ŀ⁵

Wypróbuj online!

Wejście: 5

Wynik:

610

Wywrócony:


⁵Ŀ⁵
‘
R*2
²R
R
⁵Ŀ⁵

Wypróbuj online!

Wejście: 5

Wynik:

[1, 2, 3, 4, 5]10

Podwojony


⁵Ŀ⁵
R
R²
2*R
‘
⁵Ŀ⁵

⁵Ŀ⁵
R
R²
2*R
‘
⁵Ŀ⁵

Wypróbuj online!

Wejście: 5

Wynik:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32]10

Podwojone i cofnięte


⁵Ŀ⁵
‘
R*2
²R
R
⁵Ŀ⁵

⁵Ŀ⁵
‘
R*2
²R
R
⁵Ŀ⁵

Wypróbuj online!

Wejście: 5

Wynik:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]10

Wyjaśnienie

Klucz jest tutaj Ŀ, co oznacza „wywołuje łącze o indeksie n jako monadę”. Linki są indeksowane od góry do dołu, zaczynając od 1, z wyłączeniem linku głównego (najniższego). Ŀjest również modułowy, więc jeśli spróbujesz zadzwonić na numer 7 z 5 linków, faktycznie zadzwonisz na link 2.

Łącze wywoływane w tym programie jest zawsze linkiem o indeksie 10 ( ), niezależnie od wersji programu. Jednak, który link ma indeks 10, zależy od wersji.

To, co pojawia się po każdym, Ŀjest tam, więc nie pęka po odwróceniu. Program wyświetli błąd w czasie analizy, jeśli wcześniej nie było numeru Ŀ. Posiadanie po nim jest nie na miejscu nilad, który po prostu idzie prosto do wyjścia.

Oryginalna wersja wywołuje link , który oblicza n + 1.

Wersja odwrócona wywołuje łącze R, które generuje zakres 1 .. n.

Wersja podwójna wywołuje link 2*R, który oblicza 2 n i generuje zakres 1 .. 2 n .

Wersja podwójna i odwrócona wywołuje łącze ²R, które oblicza n 2 i generuje zakres 1 .. n 2 .


4

Befunge-98 , 50 bajtów

Normalna

\+]#:\-1vk  !:;#
@k!:-1 .: ;#]#$ <;
[*:>@ 
&$< ^&;

Jest to zdecydowanie najprostszy program z 4 - jedynymi wykonywanymi poleceniami są:

\+]
  !
  : @
&$< ^&;

Ten program wykonuje kilka nieistotnych czynności przed naciśnięciem polecenia „skręć w prawo” ( ]) i strzałki. Następnie trafia w 2 polecenia „weź dane”. Ponieważ na wejściu jest tylko 1 liczba i ze względu na to, jak TIO traktuje &s, program kończy się po 60 sekundach. Jeśli są 2 wejścia (i ponieważ mogę bez dodawania bajtów), IP przechodzi w górę do funkcji „program końcowy”.

Wypróbuj online!

Wywrócony

;&^ <$&
 @>:*[
;< $#]#; :. 1-:!k@
#;:!  kv1-\:#]+\

Ten jest trochę bardziej skomplikowany. odpowiednie polecenia są następujące:

;&^  $
  >:*[
;< $#]#; :. 1-:!k@
  :

co jest równoważne z

;&^                   Takes input and sends the IP up. the ; is a no-op
  :                   Duplicates the input.
  >:*[                Duplicates and multiplies, so that the stack is [N, N^2
     $                Drops the top of the stack, so that the top is N
     ]#;              Turns right, into the loop
         :.           Prints, because we have space and it's nice to do
            1-        Subtracts 1 from N
              :!k@    If (N=0), end the program. This is repeated until N=0
;< $#]#;              This bit is skipped on a loop because of the ;s, which comment out things

Ważna jest tutaj część :. 1-:!k@. Jest to przydatne, ponieważ dopóki pchamy prawidłową złożoność na stos przed wykonaniem złożoności w krótszym czasie, możemy uzyskać pożądaną. Zostanie to wykorzystane w pozostałych 2 programach w ten sposób.

Wypróbuj online!

Podwojony

\+]#:\-1vk  !:;#
@k!:-1 .: ;#]#$ <;
[*:>@ 
&$< ^&;\+]#:\-1vk  !:;#
@k!:-1 .: ;#]#$ <;
[*:>@ 
&$< ^&;

A odpowiednie polecenie to:

\+]
  !
  :
&$< ^&;\+]#:\-1vk  !:;#
@k!:-1 .: ;#]#$ <;

Ten program przechodzi w 2 pętle. Po pierwsze, podąża tą samą ścieżką, co normalny program, który wypycha 1 i N na stos, ale zamiast zawijać do drugiego &, IP przeskakuje komentarz w pętli, która popycha 2^N:

        vk!:    If N is 0, go to the next loop.
      -1        Subtract 1 from N
 +  :\          Pulls the 1 up to the top of the stack and doubles it
  ]#            A no-op
\               Pulls N-1 to the top again

Pozostałe bity w linii 4 są pomijane za pomocą ;s

Po wypchnięciu (2 ^ N) na stos, przechodzimy w dół linii do wyżej wspomnianej pętli drukującej. Z powodu pierwszej pętli złożoność czasu wynosi Θ (N + 2 ^ N), ale zmniejsza się do Θ (2 ^ N).

Wypróbuj online!

Podwojone i cofnięte

;&^ <$&
 @>:*[
;< $#]#; :. 1-:!k@
#;:!  kv1-\:#]+\;&^ <$&
 @>:*[
;< $#]#; :. 1-:!k@
#;:!  kv1-\:#]+\

Odpowiednie polecenia:

;&^

;< $#]#; :. 1-:!k@

 @>:*[

  :

Działa to prawie identycznie jak program odwrócony, ale N^2nie jest usuwany ze stosu, ponieważ pierwszy wiersz drugiej kopii programu jest dołączany do ostatniego wiersza pierwszego, co oznacza, że ​​polecenie drop ( $) nigdy nie zostanie wykonane , więc wchodzimy w pętlę drukowania z N^2na górze stosu.

Wypróbuj online!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.