这在技术上是“Hello World”的O(1)算法吗?

时间:2015-12-02 17:06:55

标签: c# .net algorithm big-o

这会被归类为“Hello,World!”的O(1)算法吗? ??

public class Hello1
{
   public static void Main()
   {
      DateTime TwentyYearsLater = new DateTime(2035,01,01);
      while ( DateTime.Now < TwentyYearsLater )
      { 
          System.Console.WriteLine("It's still not time to print the hello ...");
      }
      System.Console.WriteLine("Hello, World!");
   }
}

我正在考虑使用

DateTime TwentyYearsLater = new DateTime(2035,01,01);
while ( DateTime.Now < TwentyYearsLater )
{ 
   // ... 
}

代码片段作为一个繁忙的循环,当有人要求某种复杂性的算法时,将其作为一个笑话。这是正确的吗?

15 个答案:

答案 0 :(得分:401)

此上下文中的Big O表示法用于描述函数输入大小与计算该输入结果所需执行的操作数之间的关系。

您的操作没有输出可以与输出相关,因此使用Big O表示法是没有意义的。操作所花费的时间是独立操作的输入(无...)。由于 输入与执行的操作数量之间没有关系,因此您无法使用Big O来描述不存在的关系

答案 1 :(得分:88)

Big-O表示法大致意味着对一定量的工作N进行操作,算法需要多少与N成比例的计算时间?&#39;。例如,对大小为N的数组进行排序可以采用N ^ 2,Nlog(N)等

这没有任何输入数据可供使用。所以它不是O(anything)

更糟糕;这在技术上不是一种算法。算法是计算数学函数值的方法 - 数学函数是从一个输入到输出的映射。由于这不需要任何输入并且不返回任何内容,因此在数学意义上它不是函数。来自维基百科:

  

算法是一种可以在a中表达的有效方法   有限的空间和时间,以及明确定义的正式语言   用于计算函数。从初始状态和初始状态开始   输入(可能是空的),指令描述的计算,   执行时,通过有限数量的明确定义   连续的状态,最终产生&#34;输出&#34;并终止于   最终结束状态。

从技术上讲,这是一个控制系统。来自维基百科;

  

控制系统是管理的设备或设备集,   命令,指导或调节其他设备的行为或   系统

对于想要对数学函数和算法之间的差异进行更深入的回答的人,以及计算机执行诸如控制台输出,显示图形或控制机器人等副作用的更强大功能,请{{3 }}

<强>抽象

  

计算位置计算的经典视图是输入(有理数或有限字符串)到输出的闭箱变换。根据计算的交互式视图,计算是一个持续的交互过程,而不是输入到输出的基于功能的转换。具体而言,与外部世界的通信在计算期间发生,而不是在计算之前或之后发生。这种方法从根本上改变了我们对什么是计算以及如何建模的理解。

     作为新范式的交互的接受受到强大教会 - 图灵论文(SCT)的阻碍,人们普遍认为图灵机器(TM)捕获所有计算,因此计算模型比TM更具表现力是不可能的。在本文中,我们展示了SCT以图灵从未想过的方式重新解释了原始的教会 - 图灵论文(CTT);它通常假定与原始的等价是一个神话。我们确定并分析了SCT普遍信仰的历史原因。只有接受它是假的,我们才能开始采用互动作为另类的计算范式

答案 2 :(得分:41)

不,您的代码的时间复杂度为O(2^|<DeltaTime>|)

正确编码当前时间 请允许我先为我的英语道歉。

Big O在CS中的作用和方式

Big O表示法不用于将程序输入与其运行时间联系起来
Big O表示法是一种表达两个数量的渐近比率的方法。

在算法分析的情况下,这两个量输入(首先必须具有&#34;测量&#34;功能)和运行时间。
它们是问题实例 1 的编码长度和感兴趣的度量。

常用指标是

  1. 在给定的计算模型中完成算法所需的步骤数。
  2. 如果存在任何此类概念,则需要的空间由计算模型。
  3. 隐含地假设TM为模型,因此第一个点转换为过渡 2 函数的应用数量,即&#34;步骤&#34 ;和第二个转换至少写入一次的不同磁带单元的数量。

    它是否经常隐含地假设我们可以使用多项式相关编码而不是原始编码,例如,从头到尾搜索数组的函数具有O(n)复杂度,尽管编码为此类数组的实例应具有n*b+(n-1)的长度,其中b是每个元素的(常量)符号数。这是因为b被认为是计算模型的常量,因此上面的表达式和n渐近相同。

    这也解释了为什么类似Trial Division的算法是指数算法,尽管基本上是for(i=2; i<=sqr(N); i++)类算法 3

    请参阅this

    这也意味着大O表示法可能会使用尽可能多的参数来描述问题,对某些算法来说, k 参数是不常见的。

    因此关于&#34;输入&#34;或那个&#34;没有输入&#34;。

    现在研究案例

    Big O表示法不会对您的算法提出质疑,它只是假设您知道自己在做什么。它本质上是一个适用于所有地方的工具,甚至可能是故意棘手的算法(如你的)。

    为了解决您的问题,您使用了当前日期和未来日期,因此他们必须以某种方式成为问题的一部分;简单地说:它们是问题实例的一部分。

    特别是实例是:

    <DeltaTime>

    <>表示任何非病态的选择编码。

    请参阅下面的非常重要的说明。

    因此,您的大O复杂时间仅为O(2^|<DeltaTime>|),因为您进行了一系列迭代,这取决于当前时间的值。 将其他数字常量放在渐近符号是有用的,因为它消除了常量(因此例如使用O(10^|<DeltaTime>|*any_time_unit)是没有意义的)是没有意义的。

    棘手的部分是

    我们在上面做了一个重要的假设:计算模型证明了 5 时间,而且我指的是(实际?)物理时间。 在标准计算模型中没有这样的概念,TM不知道时间,我们将时间与步数联系起来,因为这就是我们的现实工作 4

    在你的模型中,时间是计算的一部分,你可以使用功能人的术语,说Main不纯,但概念是相同的。

    要理解这一点,应该注意到没有什么能阻止框架使用比物理时间快两倍,五倍,十倍的假时间。这样你的代码将运行在&#34; half&#34;,&#34;五分之一&#34;,&#34;十分之一&#34; &#34;时间&#34;。

    这种反射对于选择<DeltaTime>的编码很重要,这实际上是一种简洁的写作方式&lt;(CurrentTime,TimeInFuture)&gt;。 由于修道院不存在时间,因此CurrentTime的编码很可能是前一天的 Now (或任何其他选择),可编码为昨天,其中打破了随着物理时间的推移(以及DeltaTime减少之一)编码长度增加的假设

    我们必须在计算模型中正确建模时间,以便做一些有用的事情。

    我们唯一可以做的安全选择是对时间戳进行编码,增加长度(但仍然不使用一元)作为物理时间前进。这是我们需要的唯一真正的属性,也是编码需要捕获的属性。 只有这种类型的编码才能使您的算法具有时间复杂性。

    您的混淆(如果有的话)源自短语“时间”这个词 time 复杂性?&#39 ; &#39;需要多少时间?&#39;意味着非常不同的事情

    唉,术语使用相同的单词,但你可以尝试使用&#34;步骤复杂性&#34;在你脑海中重新问自己你的问题,我希望这会帮助你理解答案真的是^ _ ^

    1 这也解释了渐近方法的必要性,因为每个实例都有一个不同的,但不是任意的长度。
    2 我希望我在这里使用正确的英文术语 3 这也是我们经常在数学中找到log(log(n))项的原因 4 Id est,一个步骤必须占用一些有限的,但不是空的,也不是连接的,时间间隔。
    5 这意味着计算模式作为物理时间的知识,即可以用它的术语来表达。类比是泛型如何在.NET框架中工作。

答案 3 :(得分:29)

虽然这里有很多很棒的答案,但让我稍微改写一下。

存在描述函数的Big-O表示法。当应用于算法分析时,这要求我们首先根据函数定义该算法的某些特征。常见的选择是将步数视为输入大小的函数。正如其他答案所述,在你的案例中提出这样的功能似乎很奇怪,因为没有明确定义的&#34;输入&#34;。不过,我们仍然可以尝试这样做:

  • 我们可以将您的算法视为一个常量函数,它接受任何大小的任何输入,忽略它,等待一段固定的时间,然后完成。在这种情况下,它的运行时是 f(n)= const ,它是一个O(1)时间算法。这是你期望听到的,对吗? 是的,从技术上讲,它是一个O(1)算法
  • 我们可以将TwentyYearsLater视为&#34;输入大小&#34;类似感兴趣的参数。在这种情况下,运行时是 f(n)=(n-x)其中 x 是&#34;现在时间&#34;在调用的那一刻。当以这种方式看时,它是O(n)时间算法。每当你向其他人展示技术上的O(1)算法时,都会遇到这种反驳。
  • 哦,等等,如果 k = TwentyYearsLater是输入,那么它的 size n 实际上是表示它所需的位数,即 n = log(k)。因此,输入 n 的大小与运行时之间的依赖关系是 f(n)= 2 ^ n - x 。好像你的算法刚刚变得指数级慢! 呃。
  • 该程序的另一个输入实际上是OS给出的循环中DateTime.Now调用序列的答案流。我们实际上可以想象,在我们运行程序时,整个序列都是作为输入提供的。然后可以认为运行时取决于该序列的属性 - 即它的长度直到第一个TwentyYearsLater元素。在这种情况下,运行时再次是 f(n)= n ,算法是 O(n)

但话说回来,在你的问题中,你甚至没有说你对运行时感兴趣。如果你的意思是内存使用怎么办根据您对情况建模的方式,您可以说算法是O(1)-memory,或者也许是O(n)-memory(如果DateTime.Now的实现需要跟踪整个调用序列,为什么)

如果你的目标是想出一些荒谬的东西,那么为什么不全力以赴,并说你对屏幕上算法的代码大小如何依赖于选择的缩放级别。这可能类似于 f(缩放)= 1 /缩放,您可以自豪地将算法声明为 O(1 / n)像素大小!

答案 4 :(得分:21)

我不得不稍微不同意Servy。这个程序有一个输入,即使它不明显,这是系统的时间。这可能是您没有预期的技术性,但您的TwentyYearsFromNow变量距离系统现在的时间不到20年,它被静态分配到2035年1月1日。

因此,如果您使用此代码并在系统时间为1970年1月1日的计算机上执行它,则无论计算机的速度有多快,都需要65年才能完成(如果计算机有多快,可能会有一些变化时钟有故障)。如果您使用此代码并在系统时间为2035年1月2日的计算机上执行它,它几乎会立即完成。

我会说你的输入nJanuary 1st, 2035 - DateTime.Now,而且是O(n)。

然后还有操作次数的问题。有些人已经注意到更快的计算机将更快地打到循环,导致更多的操作,但这是无关紧要的。使用big-O表示法时,我们不考虑处理器的速度或操作的确切数量。如果您使用此算法并在计算机上运行它,然后再次运行它,但在同一台计算机上再运行10倍,您可能会希望操作数增加相同的10倍。

至于此:

  

我正在考虑使用[编辑代码]代码片段作为忙碌循环,当有人要求某种复杂性的算法时,将其作为一个笑话。这是正确的吗?

不,不是真的。其他答案已经涵盖了这一点,所以我只是想提一下。您通常不能将执行年数与任何大O表示法相关联。例如。没有办法说20年的执行= O(n ^ 87)或其他任何事情。即使在您给出的算法中,我也可以将TwentyYearsFromNow更改为20110年,75699436或123456789,而大O仍然是O(n)。

答案 5 :(得分:13)

Big-O分析处理所涉及的处理量,因为处理的数据量不受限制地增加。

在这里,您实际上只处理固定大小的单个对象。因此,应用大O分析在很大程度上(主要是?)取决于您如何定义术语。

例如,您可能意味着打印输出,并且等待很长时间,以便在相同的时间段内打印任何合理数量的数据。你还必须添加一些不同寻常的(如果不是完全错误的)定义,以获得更多 - 特别是,大O分析通常根据数量来定义执行特定任务所需的基本操作(但请注意,复杂性也可以考虑内存使用等方面,而不仅仅是CPU使用/操作)。

然而,基本操作的数量通常与所花费的时间相当接近,所以这并不是一个巨大的延伸,将两者视为同义词。然而不幸的是,我们仍然坚持使用其他部分:正在处理的数据量不受限制地增加。既然如此,没有固定的延迟就可以实现。要将O(1)与O(N)等同,您必须施加无限延迟,以便永久打印任何固定数量的数据,就像无限量的数据一样。

答案 6 :(得分:10)

big-O相对于什么?

你似乎直觉twentyYearsLater是一个“输入”。如果你确实把你的功能写成了

void helloWorld(int years) {
   // ...
}

这将是O(N),其中N =年(或者只是说O(years))。

我会说你的算法是O(N)相对于你在twentyYearsLater =开始的代码行中编写的任何数字。但人们通常不会将实际源代码中的数字视为输入。他们可能会将命令行输入视为输入,或将函数签名输入视为输入,但很可能不是源代码本身。这就是你和朋友争论的问题 - 这是“输入”吗?您设置代码的方式使其直观地看起来像一个输入,并且您可以肯定地询问它在程序的第6行上的数字N的大O运行时间,但是如果您使用这样的非默认选择作为输入,你真的需要明确它。

但是如果你把输入变成更常见的东西,比如命令行或函数的输入,根本就没有输出,函数是O(1)。这需要20年,但由于大O不会变为常数倍,O(1)= O(20年)。

类似的问题 - 什么是运行时:

void sortArrayOfSizeTenMillion(int[] array)

假设它完成它所说的并且输入有效,并且算法利用快速排序或冒泡排序或任何合理的,它是O(1)。

答案 7 :(得分:8)

这&#34;算法&#34;被正确描述为O(1)或恒定时间。有人认为这个程序没有输入,因此没有N可以用Big Oh来分析。我不同意没有输入。当将其编译为可执行文件并进行调用时,用户可以指定任意长度的任何输入。输入长度是N.

程序忽略输入(无论长度如何),因此无论输入的长度如何(给定的固定环境=开始时间+硬件),所用的时间(或执行的机器指令的数量)都是相同的,因此O(1)。

答案 8 :(得分:6)

我感到惊讶的一件事还没有被提及:big-O符号是一个上限!

每个人都注意到的问题是没有N描述算法的输入,所以没有什么可以进行大O分析。但是,这可以通过一些基本技巧轻松减轻,例如接受int n和打印&#34; Hello World&#34; n次。这样就可以解决这个问题,并回到真正的问题:DateTime怪物是如何运作的。

没有实际保证while循环将终止。我们认为它必须在某些时间,但请考虑DateTime.now返回系统日期和时间。实际上并不能保证这是单调增加的。有可能是一些经过病程训练的猴子不断改变系统日期和时间,直到2015年10月21日12:00:00 UTC,直到有人给猴子一些自动装鞋和一个悬浮板。这个循环实际上可以运行无限的时间!

当你真正深入研究big-O符号的数学定义时,它们是上界。他们展示了最糟糕的情况,无论多么不可能。最糟糕的情况*场景是一个无限的运行时,所以我们被迫声明没有大O符号来描述这个算法的运行时复杂性。它确实存在,只是因为1/0不存在。

*编辑:从我与KT的讨论中,假设我们用big-O表示法建模的场景是最坏的情况并不总是有效的。在大多数情况下,如果某个人未能指明我们使用哪种情况,他们打算探索最坏的情况。但是,您可以对最佳情况运行时进行大O复杂性分析。

答案 9 :(得分:5)

复杂性用于在时间/空间方面测量计算“马力”。 Big O表示法用于比较哪些问题是“可计算的”或“不可计算的”,并且还用于比较哪些解决方案 - 算法 - 比其他解决方案更好。因此,您可以将任何算法分为两类:可以在多项式时间内求解的算法和不能在多项式时间内求解的算法。

诸如Erathostene筛选的问题是O(n ^ exp),因此可以解决n的小值。它们是可计算的,只是不是在多项式时间(NP)中,因此当被问及给定数字是否为素数时,答案取决于这个数字的大小。此外,复杂性并不依赖于硬件,因此拥有更快的计算机不会改变任何东西......

Hello World不是一种算法,因此尝试确定其复杂性是没有意义的 - 这是没有的。一个简单的算法可以是这样的:给定一个随机数,确定它是偶数还是奇数。现在,给定的数字有500位数是否重要?不,因为您只需检查最后一位数字是偶数还是奇数。一个更复杂的算法是确定一个给定的数字是否均匀地除以3.虽然有些数字“容易”计算,但其他数字是“硬”的,这是因为它的大小:比较确定两者之间的重新排序所需的时间。一个数字,一个数字,另一个数字,500位数。

更复杂的情况是解码文本。您有一个明显的随机数组符号,您也知道这些符号正在为具有解密密钥的人传达一条消息。假设发件人使用了左边的密钥,你的Hello World会读到:Gwkki Qieks。 “大锤,无脑”解决方案将为这些字母生成所有组合:从Aaaa到Zzzz,然后搜索单词字典以识别哪些单词有效并共享密码中的两个常用字母(i,k)in同样的立场。这个转换函数就是Big O所测量的!

答案 10 :(得分:4)

每个人都正确地指出你没有定义 N ,但答案是最合理的解释。如果 N 是我们正在打印的字符串的长度,那么“hello,world!”就是一个例子,因为我们可以从对此的描述中推断出“for hello, world! ,“然后算法是O( N ),因为你可能有一个需要三十年,四十年或五十年才能打印的输出字符串,而你只需要一个恒定的时间。 O( kN + c )∈O( N )。

附录:

令我惊讶的是,有人在争论这个问题。回想一下大O和大Θ的定义。假设我们有一个等待恒定时间 c 的算法,然后在线性时间内输出长度 N 的消息。 (这是对原始代码示例的概括。)让我们随意说我们等待二十年才开始打印,打印万亿个字符需要另外二十年。例如,让 c = 20和 k = 10 12,但任何正实数都可以。这是每个字符的 d = c / k (在这种情况下为2×10 -11)年的比率,因此我们的执行时间 f N )渐近 dN + c 年。每当 N &gt; k dN = c / k N &gt; C 的。因此, dN &lt; dN + c = f N )&lt;所有 N 的2 dN &gt; k f N )∈Θ( N )。 Q.E.D。

答案 11 :(得分:4)

大多数人似乎都缺少两件非常重要的事情。

  1. 程序 有输入。这是硬编码的日期/时间 与系统时间进行比较。输入在运行算法的人的控制下,而系统时间则不在。运行此程序的人唯一可以控制的是他们在比较中硬编码的日期/时间。

  2. 程序根据输入而不同,但不是输入 set 的大小,这是大O符号所关注的。< / p>

  3. 因此,它是不确定的,最好的&#39;大O&#39;该程序的表示法可能是O(null),或者可能是O(NaN)。

答案 12 :(得分:4)

我认为人们会被抛弃,因为代码不像传统算法一样看起来。这是代码的翻译,形式更加完善,但仍然符合OP的问题精神。

void TrolloWorld(long currentUnixTime, long loopsPerMs){
    long laterUnixTime = 2051222400000;  //unix time of 01/01/2035, 00:00:00
    long numLoops = (laterUnixTime-currentUnixTime)*loopsPerMs;

    for (long i=0; i<numLoops; i++){
        print ("It's still not time to print the hello …");
    }
    print("Hello, World!");
}

输入是显式的,而在它们被代码启动时以及运行代码的硬件的速度隐式给出之前。代码是确定性的,并且对于给定的输入具有明确定义的输出。

由于我们可以提供的输入受到限制,因此将执行的操作数量存在上限,因此该算法实际上是O(1)。

答案 13 :(得分:2)

此时,是

此算法具有隐式输入,即程序启动的时间。执行时间将线性变化 1 ,具体取决于启动时间。在2035年及之后的年份,while循环立即退出,程序在常数操作 2 之后终止。所以可以说运行时是O(max(2035 - start year, 1)) 3 。但由于我们的起始年份具有最小值,因此该算法的执行时间永远不会超过20年(即恒定值)。

您可以通过定义DateTime TwentyYearsLater = DateTime.Now + new TimeSpan(365*20,0,0,0); 4

来使您的算法更符合您的意图

1 由于每个时间单位的操作次数最多,因此操作次数测量的执行时间更为技术性。 2 假设提取DateTime.Now是一个恒定的操作,这是合理的 3 我在这里有点滥用大O符号,因为这是start year的递减函数,但我们可以通过用years prior to 2035来表达它来轻松纠正这个问题。
4 然后算法不再依赖于开始时间的隐式输入,但这并不重要。

答案 14 :(得分:1)

我认为这是O(n)。 使用http://www.cforcoding.com/2009/07/plain-english-explanation-of-big-o.html作为参考。

  

什么是大O?

     

Big O符号旨在描述一个相对复杂的   算法通过降低增长率到关键因素的关键   因素倾向于无穷大。

  

我能想到的Big-O最好的例子就是做算术。我们学到的基本算术运算   在学校里:

     

加成;减法;乘法;和分裂。每个都是   操作或问题。解决这些问题的方法称为   算法

对于您的示例,

给出n = 20的输入(单位年数)。

算法是数学函数f()。其中f()恰好等待n年,其中&#39; debug&#39;中间的字符串。比例因子为1.可以通过改变该比例因子来减少/增加f()。

对于这种情况,输出也是20(改变输入线性地改变输出)。

基本上功能是

f(n) = n*1 = n
    if  n = 20, then 
f(20) = 20