功率编程:O(1 ^ N),O(N ^ 1),O(2 ^ N),O(N ^ 2)合而为一


65

根据程序的运行方式,编写出表现出四种常见的O大 时间复杂度的程序(或函数)。无论采用哪种形式,都需要一个正整数N,您可以假设它小于2 31

  1. 当程序以其原始形式运行时,它应该具有恒定的复杂性。也就是说,复杂度应为Θ(1)或等效地为Θ(1 ^ N)

  2. 当程序反向运行时,它应该具有线性复杂度。也就是说,复杂度应为Θ(N)或等效地为Θ(N ^ 1)
    (这是有道理的,因为N^11^N逆转。)

  3. 当程序被加倍,即,级联到本身,并运行它应具有指数复杂性,特别是2 Ñ。也就是说,复杂度应为Θ(2 ^ N)
    (这是有道理的,因为22^N是双11^N)。

  4. 当程序被加倍执行反转并运行时,它应具有多项式复杂度,尤其是N 2。也就是说,复杂度应为Θ(N ^ 2)
    (这是有道理的,因为N^22^N逆转。)

这四种情况是您唯一需要处理的情况。

请注意,为精确起见,我使用大theta(Θ)表示法而不是大O,因为您的程序的运行时必须在上下两方面受所需复杂度的限制。否则,仅在O(1)中编写一个函数就可以满足所有四个点。了解这里的细微差别不是太重要。主要是,如果您的程序针对某个常数k执行k * f(N)运算,则很有可能为Θ(f(N))。

如果原始程序是

ABCDE

那么运行它应该花费恒定的时间。也就是说,无论输入N是1还是2147483647(2 31 -1)或两者之间的任何值,它都应在大致相同的时间内终止。

程序的反向版本

EDCBA

应该以N为单位花费线性时间。也就是说,终止所花费的时间应与N大致成比例。因此N = 1花费的时间最少,N = 2147483647花费的时间最多。

该程序的两倍版本

ABCDEABCDE

以N表示,应该花费2到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兼容性。)

大提醒

从最快到最慢(上面的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))对于常数k。所以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本身(BTW不是通常的输入大小概念:将输入N视为大小为log N)。因此,每个N 仅存在一种情况,同时是最佳,最差和平均情况。
ShreevatsaR '17

5
看来您偏离了通常的复杂性定义。我们总是将算法的时间复杂度定义为其输入大小的函数。在输入为数字的情况下,输入的大小为该数字的以2为底的对数。因此,该程序n = input(); for i in xrange(n): pass具有指数复杂性,因为它需要执行2 ** k步骤,其中k = log_2(n)输入大小在哪里。您应该弄清楚是否是这种情况,因为它会极大地改变需求。
wchargin '17

Answers:


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次)
  • 打印它

翻倍:

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次)
  • 打印它
  • 尝试评估输入
  • 失败
  • 创建数组[0] *(input ^ 2)(0重复输入平方的次数)
  • 打印它

当有多个呼叫时,为什么它不挂起等待用户交互input()
乔纳森·艾伦

1
在传输结束时传输“传输结束”是一个漏洞吗?
Leaky Nun

1
你能解释一下吗?
Brain Guider'May

1
回复:“文件结尾”参数,您正在向后看。当您使用终端时,因为有连接的东西,所以输入挂起的请求;可以发送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/#//;

实际花费指数时间的位是第二个eval:它评估命令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)


大!用主流语言做到这一点将是艰难的!这是我的投票!
阿俊(Arjun)

做得好,这很好。您可能是指$x.=q;##say...而不是$x.=q;#say...(用2 #而不是1)。(这可以解释为什么您计算的是76个字节而不是75个字节)。另外,应该-n在字节数中计数标志,因为没有它,程序将不会做很多事情。
Dada's

@Dada我不小心调换了evals///命令。如果您先做eval一个,就只需要一个#。接得好!
克里斯(Chris)

@Chris对,确实可以。您也许可以省略las​​t #$x=~s/#//;reverse产生;//#/s~=x$,在您的上下文中,它不执行任何操作,就像使用lead那样#。(虽然我还没有测试)。无论如何,+ 1!
Dada's

@达达尼斯再次抓住!
克里斯(Chris)

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)复杂度的程序,而它的double具有O(2 n)复杂度。
  • def是一个具有O(n 1)复杂度的程序,而它的double具有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

@Arjun谢谢您的赞赏。
Leaky Nun

这非常好,并且通过不使用IMO注释就真正成为挑战。太棒了!
ShreevatsaR '17

1
好吧,这实际上有注释...字符串未评估并且是NOP ...
Leaky Nun

4

果冻,20字节

部分受到Leaky Nun的Actually解决方案的启发。

前导和尾随的换行符很重要。

正常:


⁵Ŀ⁵
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

说明

此处的键位于中Ŀ,表示“将索引n处的链接称为monad”。链接从1开始从上到下索引,不包括主链接(最底部的一个)。Ŀ也是模块化的,因此,如果您尝试从5个链接中调用第7个链接,则实际上会调用第2个链接。

无论程序是什么版本,在该程序中被调用的链接始终是索引10()处的链接。但是,哪个链接位于索引10取决于版本。

每个之后出现Ŀ有如此逆转时不会折断。如果之前没有数字,则程序将在解析时出错Ŀ。有一个after是不适当的nilad,直接进入输出。

原始版本称为link ,该链接计算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

使用;s 跳过第4行的其他位

在将(2 ^ N)推入堆栈后,我们向下移动一行到上述打印循环中。由于有第一个循环,所以时间复杂度为Θ(N + 2 ^ N),但降至Θ(2 ^ N)。

在线尝试!

翻倍和反转

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

相关命令:

;&^

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

 @>:*[

  :

此功能几乎与反向程序相同,但N^2不会从堆栈中弹出,因为该程序第二个副本的第一行被追加到第一个副本的最后一行,这意味着drop命令($)永远不会执行,因此我们进入N^2堆栈顶部的打印循环。

在线尝试!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.