我正在学习大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,但我不知道如何识别具有对数时间的函数。


当前回答

我无法理解如何使用日志时间标识函数。

对数运行时间函数最常见的属性是:

选择下一个要执行某些操作的元素是多种可能性之一,并且只需要选择一个。

or

执行操作的元素是n的数字

这就是为什么,例如,在电话簿中查找人是O(logn)。你不需要检查电话簿上的每个人,就能找到合适的人;相反,你可以简单地根据他们的名字的字母顺序进行划分和征服,在每个部分中,你只需要探索每个部分的一个子集,就可以最终找到某人的电话号码。

当然,一本更大的电话簿仍然需要更长的时间,但它的增长速度不会像增加电话簿的比例那样快。


我们可以扩展电话簿示例,以比较其他类型的操作及其运行时间。我们将假设我们的电话簿中有具有唯一名称的业务(“黄页”)和可能没有唯一名称的人员(“白页”)。电话号码最多分配给一个人或一家公司。我们还将假设翻到特定页面需要恒定的时间。

以下是我们可能在电话簿上执行的一些操作的运行时间,从最快到最慢:

O(1)(在最坏的情况下):给定企业名称所在的页面和企业名称,找到电话号码。O(1)(在一般情况下):给定一个人的名字和他们的名字所在的页面,找到电话号码。O(log n):给定一个人的名字,在书中你还没有搜索到的部分的中途随机抽取一个点,然后检查这个人的名字是否在这个点上,从而找到电话号码。然后在书中人名所在的部分重复这个过程。(这是对人名的二进制搜索。)O(n):查找电话号码包含数字“5”的所有人。O(n):给定一个电话号码,找到拥有该号码的人或企业。O(n log n):打印机的办公室出现了混乱,我们的电话簿上的所有页面都以随机顺序插入。通过查看每一页上的名字,然后将该页放在新的空电话簿中的适当位置,修正顺序,使其正确。

对于以下示例,我们现在在打印机的办公室。电话簿等待邮寄给每位居民或企业,每个电话簿上都有一个标签,标明应该邮寄到哪里。每个人或企业都有一本电话簿。

O(n log n):我们想让电话簿个性化,所以我们将在他们指定的副本中找到每个人或企业的名字,然后在电话簿中圈出他们的名字,并为他们的惠顾写一封简短的感谢信。O(n2):办公室发生了一个错误,每个电话簿中的每个条目在电话号码末尾都有一个额外的“0”。取出一些白色,去掉每个零。O(n·n!):我们准备好把电话簿装到码头上了。不幸的是,原本要装书的机器人已经失控了:它正在把书按随机顺序放在卡车上!更糟糕的是,它把所有的书都装到卡车上,然后检查它们的顺序是否正确,如果不正确,它就把它们卸下来,重新开始。(这是可怕的bogo类型。)O(nn):你把机器人修好,这样它就能正确地装载东西。第二天,你的一个同事对你开了个玩笑,把装卸台机器人连接到自动打印系统上。每次机器人去装载一本原版书时,工厂打印机都会对所有的电话簿进行重复打印!幸运的是,机器人的错误检测系统足够复杂,当它遇到要加载的复制书时,它不会尝试打印更多的副本,但它仍然必须加载已打印的每一本原始和复制书。

其他回答

完整的二进制示例是O(ln n),因为搜索结果如下:

1 2 3 4 5 6 7 8 9 10 11 12

搜索4个会产生3次命中:6次,3次,然后4次。而log2 12=3,这是一个很好的近似值,以多少命中需要。

O(logN)基本上意味着时间线性上升,而N指数上升。因此,如果计算10个元素需要1秒,则计算100个元素需要2秒,计算1000个元素需要3秒,依此类推。

​当我们进行分而治之的算法(如二进制搜索)时,它是O(logn)。另一个例子是快速排序,每次我们将数组分成两部分,每次都需要O(N)时间才能找到一个枢轴元素。因此,N O(log N)

这两种情况需要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);
    }

O(logn)有点误导,更准确地说,它是O(log2n),即(以2为底的对数)。

平衡二叉树的高度是O(log2n),因为每个节点都有两个(注意log2n中的“两个”)子节点。因此,具有n个节点的树的高度为log2n。

另一个例子是二进制搜索,它的运行时间为O(log2n),因为在每一步中,您都将搜索空间除以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的《数学家的喜悦》一书。