我正在学习大O符号的运行时间和摊销时间。我理解O(n)线性时间的概念,这意味着输入的大小成比例地影响算法的增长。。。例如,二次时间O(n2)等也是如此。甚至是通过阶乘增长的算法,如置换生成器,其O(n!)次。

例如,以下函数为O(n),因为算法与其输入n成比例增长:

f(int n) {
  int i;
  for (i = 0; i < n; ++i)
    printf("%d", i);
}

类似地,如果存在嵌套循环,时间将为O(n2)。

但O(log n)到底是什么?例如,说一个完整的二叉树的高度是O(log n)是什么意思?

我确实知道(也许不是很详细)对数是什么,从这个意义上说:log10 100=2,但我不知道如何识别具有对数时间的函数。


当前回答

对数

好的,让我们试着完全理解对数到底是什么。

想象一下,我们有一根绳子,把它拴在一匹马身上。如果绳子直接系在马身上,那么马拉离(例如,从人身上)所需的力直接为1。

现在想象绳子绕在一根杆子上。要想脱身的马现在必须用力拉很多倍。次数取决于绳索的粗糙度和杆的大小,但我们假设它会将一个人的力量乘以10(当绳索完全转弯时)。

现在,如果绳子绕一圈,马需要用力拉10倍。如果人类决定让马变得很困难,他可以再次将绳子绕在一根杆子上,使它的力量增加10倍。第三个循环将再次将强度增加10倍。

我们可以看到,对于每个循环,值增加10。获得任何数字所需的圈数称为数字的对数,即我们需要3个柱将你的力量乘以1000倍,需要6个柱将力量乘以1000000。

3是1000的对数,6是1000000的对数(以10为底)。

那么O(log n)实际上是什么意思?

在上面的例子中,我们的“增长率”是O(logn)。每增加一圈,我们的绳子所能承受的力就会增加10倍:

Turns | Max Force
  0   |   1
  1   |   10
  2   |   100
  3   |   1000
  4   |   10000
  n   |   10^n

现在上面的例子确实使用了基数10,但幸运的是,当我们讨论大o符号时,对数的基数是微不足道的。

现在,让我们假设您正在尝试猜测1-100之间的数字。

Your Friend: Guess my number between 1-100! 
Your Guess: 50
Your Friend: Lower!
Your Guess: 25
Your Friend: Lower!
Your Guess: 13
Your Friend: Higher!
Your Guess: 19
Your Friend: Higher!
Your Friend: 22
Your Guess: Lower!
Your Guess: 20
Your Friend: Higher!
Your Guess: 21
Your Friend: YOU GOT IT!  

现在你猜了7次才猜对。但这里的关系是什么?你可以从每一个额外的猜测中猜出最多的项目是什么?

Guesses | Items
  1     |   2
  2     |   4
  3     |   8
  4     |   16
  5     |   32
  6     |   64
  7     |   128
  10    |   1024

使用该图,我们可以看到,如果我们使用二进制搜索来猜测1-100之间的数字,最多需要7次尝试。如果我们有128个数字,我们也可以在7次尝试中猜出数字,但129个数字最多需要8次尝试(与对数相关,这里我们需要7次猜测128个值范围,10次猜测1024个值范围。7是128的对数,10是1024的对数(以2为底))。

注意,我用粗体字“最多”。大O符号总是指更坏的情况。如果你运气好,你可以一次猜出数字,所以最好的情况是O(1),但那是另一回事。

我们可以看到,我们的数据集正在缩小。识别算法是否具有对数时间的一个很好的经验法则是查看数据集在每次迭代后是否按一定顺序收缩

O(n log n)呢?

你最终会遇到一个线性时间O(n log(n))算法。上述经验法则再次适用,但这一次对数函数必须运行n次,例如,将列表的大小减少n次,这在合并排序等算法中发生。

您可以很容易地确定算法时间是否为n log n。寻找一个在列表(O(n))中迭代的外部循环。然后查看是否存在内部循环。如果内部循环在每次迭代时都在切割/减少数据集,则该循环为(O(logn)),因此整个算法为=O(n logn)。

免责声明:绳对数示例摘自W.Sawyer的《数学家的喜悦》一书。

其他回答

这仅仅意味着该任务所需的时间随着log(n)的增加而增加(例如:n=10时为2s,n=100时为4s,…)。请阅读维基百科关于二进制搜索算法和大O符号的文章以了解更多的精度。

每次编写算法或代码时,我们都会尝试分析其渐近复杂性。它不同于它的时间复杂性。

渐近复杂度是算法执行时间的行为,而时间复杂度是实际执行时间。但有些人可以互换使用这些术语。

因为时间复杂度取决于各种参数。1.物理系统2.编程语言3.编码样式4.还有更多。。。。。。

实际执行时间不是一个很好的分析指标。

相反,我们将输入大小作为参数,因为无论代码是什么,输入都是相同的。因此,执行时间是输入大小的函数。

以下是线性时间算法的示例

线性搜索给定n个输入元素,要搜索数组中的元素,最多需要“n”个比较。换句话说,无论你使用什么编程语言,你喜欢什么编码风格,在什么系统上执行它。在最坏的情况下,它只需要n次比较。执行时间与输入大小成线性比例。

它不仅仅是搜索,无论是什么工作(增量、比较或任何操作),它都是输入大小的函数。

所以当你说任何算法都是O(logn)这意味着执行时间是输入大小n的log倍。

随着输入大小的增加,完成的工作(这里是执行时间)增加。(因此,比例)

      n      Work
      2     1 units of work
      4     2 units of work
      8     3 units of work

随着输入大小的增加,所做的工作也会增加,并且与任何机器无关。如果你试图找出工作单位的价值它实际上取决于上述参数。它会根据系统和所有参数而改变。

O(logn)是衡量任何代码运行时性能的多项式时间复杂度之一。

我希望你已经听说过二进制搜索算法。

假设您必须在大小为N的数组中找到一个元素。

基本上,代码执行如下N不适用于2不适用于4N/8…等

如果你把每一级所做的所有工作相加,你将得到n(1+1/2+1/4….),等于O(logn)

如果你在图形计算器或类似的东西上绘制一个对数函数,你会发现它的上升速度非常慢——甚至比线性函数还要慢。

这就是为什么对数时间复杂度算法备受追捧的原因:即使对于真正大的n(例如,假设n=10^8),它们的性能也超出了可接受的范围。

这两种情况需要O(log n)时间

case 1: f(int n) {
      int i;
      for (i = 1; i < n; i=i*2)
        printf("%d", i);
    }


 case 2  : f(int n) {
      int i;
      for (i = n; i>=1 ; i=i/2)
        printf("%d", i);
    }