Программирование питания: O (1 ^ N), O (N ^ 1), O (2 ^ N), O (N ^ 2) все в одном


65

Напишите программу (или функцию), которая демонстрирует четыре общих больших сложности времени, в зависимости от того, как она выполняется. В любой форме оно принимает положительное целое число N, которое, как вы можете предположить, меньше 2 31 .

  1. Когда программа запускается в своем первоначальном виде, она должна иметь постоянную сложность. То есть сложность должна быть Θ (1) или, что то же самое, Θ (1 ^ N) .

  2. Когда программа перевернута и запущена, она должна иметь линейную сложность. То есть сложность должна быть Θ (N) или, что то же самое, Θ (N ^ 1) .
    (Это имеет смысл, так N^1как 1^Nнаоборот.)

  3. Когда программа в два раза , т.е. сцеплены к себе, и работать она должна иметь экспоненциальную сложность, а именно 2 N . То есть сложность должна быть Θ (2 ^ N) .
    (Это имеет смысл, поскольку 2в 2^Nдва раза больше, чем 1в 1^N.)

  4. Когда программа удвоилась и обратная и запустить его должен иметь полиномиальный сложности, в частности N 2 . То есть сложность должна быть Θ (N ^ 2) .
    (Это имеет смысл, так N^2как 2^Nнаоборот.)

Эти четыре случая - единственные, с которыми вам нужно разобраться.

Обратите внимание, что для точности я использую нотацию большого тета (no) вместо большого O, потому что время выполнения ваших программ должно быть ограничено как сверху, так и снизу требуемыми сложностями. В противном случае простое написание функции в O (1) удовлетворяет всем четырем точкам. Здесь не так уж важно понимать нюанс. Главным образом, если ваша программа выполняет k * f (N) операций для некоторой константы k, то это, вероятно, в Θ (f (N)).

пример

Если бы оригинальная программа была

ABCDE

затем запуск должен занять постоянное время. То есть, независимо от того, является ли вход N 1 или 2147483647 (2 31 -1), или любое другое значение между ними, оно должно завершиться примерно за то же время.

Обращенная версия программы

EDCBA

должно занимать линейное время в терминах N. То есть время, необходимое для завершения, должно быть примерно пропорционально N. Поэтому N = 1 занимает меньше всего времени, а N = 2147483647 - больше всего.

Удвоенная версия программы

ABCDEABCDE

должны принимать два-к-N раз , с точки зрения N. То есть, время, необходимое для завершения должна быть примерно пропорциональна 2 N . Таким образом, если N = 1 заканчивается примерно через секунду, N = 60 займет больше времени, чем возраст вселенной для завершения. (Нет, вам не нужно проверять это.)

Удвоенная и перевернутая версия программы

EDCBAEDCBA

должно занять время в квадрате в терминах N. То есть время, необходимое для завершения, должно быть примерно пропорционально N * N. Таким образом, если N = 1 заканчивается примерно через секунду, N = 60 потребовалось бы около часа для завершения.

подробности

  • Вам нужно показать или утверждать, что ваши программы работают в сложностях, о которых вы говорите. Предоставление некоторых временных данных является хорошей идеей, но также попытайтесь объяснить, почему теоретически сложность является правильной.

  • Хорошо, если на практике время, затрачиваемое вашими программами, не совсем отражает их сложность (или даже детерминированность). Например, вход N + 1 может иногда работать быстрее, чем N.

  • Среда, в которой вы запускаете свои программы, имеет значение. Вы можете сделать основные предположения о том, что популярные языки никогда не намеренно тратят время на алгоритмы, но, например, если вы знаете, что ваша конкретная версия Java реализует пузырьковую сортировку вместо более быстрого алгоритма сортировки , то вам следует принять это во внимание, если вы выполняете какую-либо сортировку ,

  • Для всех сложностей здесь предположим, что мы говорим о сценариях наихудшего случая , а не наилучшем или среднем случае.

  • Пространственная сложность программ не имеет значения, только временная сложность.

  • Программы могут выводить что угодно. Имеет значение только то, что они принимают положительное целое число N и имеют правильные временные сложности.

  • Комментарии и многострочные программы разрешены. (Вы можете предположить, что \r\nобратное - \r\nдля совместимости с Windows.)

Big O Напоминания

От самого быстрого до самого медленного O(1), O(N), O(N^2), O(2^N)(порядок 1, 2, 4, 3 выше).

Более медленные условия всегда доминируют, например O(2^N + N^2 + N) = O(2^N).

O(k*f(N)) = O(f(N))для постоянной к. Так O(2) = O(30) = O(1)и O(2*N) = O(0.1*N) = O(N).

Помните O(N^2) != O(N^3)и O(2^N) != O(3^N).

Аккуратный большой O шпаргалка.

счет

Это нормальный код гольфа. Самая короткая оригинальная программа (с постоянным временем) в байтах побеждает.


Отличный вопрос! Минорная точка: нам не нужно указывать наихудший случай / лучший случай / средний случай, потому что единственным входом, который считается размером N, является само число N (что, кстати, не является обычным понятием размера ввода: это будет обрабатывать вход N как размер журнала N). Таким образом, существует только один случай для каждого N, который является одновременно лучшим, худшим и средним случаем.
— ShreevatsaR

5
Похоже, что вы отклонились от обычных определений сложности. Мы всегда определяем временную сложность алгоритма как функцию размера его входных данных . В случае, когда входное значение является числом, размер входного значения представляет собой логарифм base-2 этого числа. Таким образом, программа n = input(); for i in xrange(n): passимеет экспоненциальную сложность, потому что она принимает 2 ** kшаги, где k = log_2(n)находится размер ввода. Вы должны уточнить, так ли это на самом деле, поскольку это резко меняет требования.
— wchargin

Ответы:


36

Python 3 , 102 байта

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

Попробуйте онлайн!

Это O (1 ^ n), так как это то, что делает программа:

  • оценить вход
  • создать массив [0]
  • распечатай это

Перевернутый:


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

Попробуйте онлайн!

Это O (n ^ 1), так как это то, что делает программа:

  • оценить вход
  • создать массив [0] * input (0 повторяется столько же раз, сколько и input)
  • распечатай это

Вдвое:

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

Попробуйте онлайн!

Это O (2 ^ n), так как это то, что делает программа:

  • оценить вход
  • создать массив [0]
  • распечатай это
  • попробуйте оценить вход
  • провал
  • создать массив [0] * (2 ^ input) (0 повторяется столько раз, сколько 2 ^ input)
  • распечатай это

Вдвое и наоборот:


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

Попробуйте онлайн!

Это O (n ^ 2), так как это то, что делает программа:

  • оценить вход
  • создать массив [0] * input (0 повторяется столько же раз, сколько и input)
  • распечатай это
  • попробуйте оценить вход
  • провал
  • создайте массив [0] * (input ^ 2) (0 повторяется столько раз, сколько введено в квадрате)
  • распечатай это

Почему он не зависает в ожидании взаимодействия с пользователем при нескольких вызовах input()?
— Джонатан Аллан

1
Это лазейка, что "конец передачи" передается в конце передачи?
— Утренняя монахиня

1
Вы можете это объяснить?
— Мозговой

1
Re: аргумент «конец файла», вы смотрите на вещи задом наперед. Когда вы используете терминал, запросы на ввод зависают, потому что с ним что-то связано; Ctrl-D может быть отправлен для явной отправки без ввода. Если вход подключен к пустому файлу (как это делает TIO, если вы оставляете поле ввода пустым), или если он подключен к файлу, в котором все входные данные были прочитаны, нет необходимости в запросе на ввод для выполнения каких-либо действий; он уже знает, что нет ввода. В UNIX / Linux «конец файла» и «нет доступного ввода» неразличимы для обычных файлов.

3
В первом случае kвход lявляется одним и тем же, так что вы все еще вычисляете 1**k, верно? Что должно занять O(log(k))время, несмотря на тот факт, что вы, я и все заранее знаем, что это одно?
— Ричард Раст

18

Perl 5, 82 73 71 + 1 (для флага -n) = 72 байта

Я уверен, что могу играть в это (намного) больше, но это время ложиться спать, я потратил достаточно времени на отладку, как есть, и в любом случае я горжусь тем, что у меня есть.

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

Сама программа не использует ввод, а просто оценивает строку, начинающуюся с комментария, а затем выполняет подстановку одной строки, так что это явно в постоянном времени. Это в основном эквивалентно:

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

Вдвое:

#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/#//;

Бит, который на самом деле занимает экспоненциальное время, является вторым значением: он оценивает команду say for(1..2**$_), в которой перечислены все числа от 1 до 2 ^ N, что явно занимает экспоненциальное время.

Перевернутый:

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

Это (наивно) вычисляет суммирование входных данных, которое явно занимает линейное время (так как каждое добавление происходит в постоянном времени). Код, который фактически запускается, эквивалентен:

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

Последняя строка просто так, что когда эти команды повторяются, это займет квадратичное время.

В обратном порядке и в два раза:

;//#/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#

Теперь это берет суммирование суммы ввода (и добавляет ее к суммированию ввода, но неважно). Так как это делает заказ N^2сложения, это занимает квадратичное время. Это в основном эквивалентно:

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

Вторая строка находит суммирование ввода, делая Nдополнения, в то время как четвертая делает summation(N)сложения, что есть O(N^2).


Большой! Делать это на обычном языке было бы сложно! Это мой upvote!
— Арджун

Отлично, это очень мило. Вы, вероятно, имели в виду $x.=q;##say...вместо $x.=q;#say...хотя (с двумя #вместо 1). (Это объясняет, почему вы насчитали 76 байтов вместо 75). Кроме того, вы должны посчитать -nфлаг в вашем byountount, так как ваша программа мало что делает без него.
— Дада

@Dada Я случайно переставил команды evalи s///. Если вы делаете evalпервое, вам нужен только один #. Хороший улов!
— Крис

@ Крис Правильно, это действительно работает. Возможно, вы сможете опустить последнее #: $x=~s/#//;перевернутые продукты ;//#/s~=x$, которые в вашем контексте ничего не делают, как это было бы с ведущими #. (Я не проверял это все же). Независимо от этого, есть +1!
— Дада

@Dada Хороший улов еще раз!
— Крис

17

На самом деле , 20 байтов

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

Попробуйте онлайн!

Входные данные: 5

Выход:

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

Перевернутый:

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

Попробуйте онлайн!

Выход:

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

Вдвое:

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

Попробуйте онлайн!

Выход:

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]

Вдвое и наоборот:

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

Попробуйте онлайн!

Выход:

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]

Смысл

На самом деле это основанный на стеке язык.

  • abcэто программа, которая имеет сложность O (1 n ), а ее двойник имеет сложность O (2 n ).
  • defэто программа, которая имеет сложность O (n 1 ), а ее двойник имеет сложность O (n 2 ).

Тогда мое представление "abc"ƒ"fed", где ƒэто оценить. Таким образом, "fed"не будет оцениваться.

Генерация индивидуальной программы

Псевдокод первого компонента ;(1╖╜ⁿr:

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

Псевдокод второго компонента ;(1╖╜ⁿ@r:

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

Я никогда не думал, что это будет возможно! Отличная работа, сэр! +1
— Арджун,

@Arjun Спасибо за вашу благодарность.
— Утренняя монахиня

Это отлично и действительно принимает вызов, не используя комментарии IMO. Потрясающие!
— ShreevatsaR

1
Ну, это на самом деле есть комментарии ... строки не оценены и являются NOPs ...
— Leaky Nun

4

Желе , 20 байт

Отчасти вдохновлено решением Actual Leaky Nun .

Ведущие и конечные новые строки имеют большое значение.

Обычный:


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

Попробуйте онлайн!

Входные данные: 5

Выход:

610

Перевернутый:


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

Попробуйте онлайн!

Входные данные: 5

Выход:

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

двойной


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

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

Попробуйте онлайн!

Входные данные: 5

Выход:

[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

Вдвое и наоборот


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

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

Попробуйте онлайн!

Входные данные: 5

Выход:

[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

объяснение

Ключ здесь in Ŀ, что означает «вызывает ссылку в индексе n как монаду». Ссылки индексируются сверху вниз, начиная с 1, исключая основную ссылку (самую нижнюю). Ŀтакже является модульным, поэтому, если вы попытаетесь позвонить по ссылке номер 7 из 5 ссылок, вы на самом деле позвоните по ссылке 2.

Ссылка, вызываемая в этой программе, всегда указывается в индексе 10 ( ⁵) независимо от того, какая это версия программы. Однако какая ссылка находится в индексе 10, зависит от версии.

То, ⁵что появляется после каждого Ŀ, есть, поэтому оно не ломается при обращении. Программа выдаст ошибку во время разбора, если номер не был ранее Ŀ. Наличие ⁵после него - это неуместный нилад, который просто идет прямо к выводу.

Оригинальная версия вызывает ссылку ‘, которая вычисляет n + 1.

Обращенная версия вызывает ссылку R, которая генерирует диапазон 1 .. n.

Удвоенная версия вызывает ссылку 2*R, которая вычисляет 2 n и генерирует диапазон 1 .. 2 n .

Удвоенная и обращенная версия вызывает ссылку ²R, которая вычисляет n 2 и генерирует диапазон 1 .. n 2 .


4

Befunge-98 , 50 байтов

Обычный

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

Это, безусловно, самая простая программа из 4 - выполняются только следующие команды:

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

Эта программа делает некоторые ненужные вещи перед тем, как нажать «поворот направо» ( ]) и стрелку. Затем он нажимает 2 команды «принять ввод». Поскольку на входе присутствует только 1 число и из-за того, как TIO обрабатывает &s, программа завершается через 60 секунд. Если есть 2 входа (и потому что я могу без добавления байтов), IP перейдет в функцию «завершения программы».

Попробуйте онлайн!

Перевернутый

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

Этот немного сложнее. соответствующие команды следующие:

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

что эквивалентно

;&^                   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

Важной частью здесь является :. 1-:!k@немного. Это полезно, потому что, пока мы помещаем правильную сложность в стек перед тем, как выполнить ее в более короткие сроки, мы можем получить желаемую. Это будет использоваться в оставшихся 2 программах таким образом.

Попробуйте онлайн!

двойной

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

И соответствующие команды:

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

Эта программа идет в 2 цикла. Во-первых, он следует тем же путем, что и обычная программа, которая помещает 1 и N в стек, но вместо перехода ко второму &, IP перепрыгивает комментарий в цикл, который выдвигает 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

Другие биты в строке 4 пропускаются с использованием ;s

После того, как (2 ^ N) помещено в стек, мы перемещаемся вниз по линии в вышеупомянутый цикл печати. Из-за первого цикла сложность времени равна Θ (N + 2 ^ N), но это уменьшает до Θ (2 ^ N).

Попробуйте онлайн!

Вдвое и наоборот

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

Соответствующие команды:

;&^

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

 @>:*[

  :

Это работает почти идентично обращенной программе, но N^2не извлекается из стека, потому что первая строка второй копии программы добавляется к последней строке первой, что означает, что команда drop ( $) никогда не выполняется Итак, мы идем в цикл печати с N^2верхней части стека.

Попробуйте онлайн!

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.