什么会导致算法具有O(log n)复杂度?

时间:2012-02-05 20:49:51

标签: algorithm big-o time-complexity logarithm

我对big-O的了解是有限的,当日志术语出现在等式中时,它会让我失望。

有人可以用简单的语言向我解释一下O(log n)算法是什么吗?对数来自哪里?

当我试图解决这个中期练习题时,这个问题就出现了:

  

设X(1..n)和Y(1..n)包含两个整数列表,每个整数以非递减顺序排序。给出O(log n)-time算法以找到所有2n个组合元素的中值(或第n个最小整数)。例如,X =(4,5,7,8,9)和Y =(3,5,8,9,10),那么7是组合列表的中位数(3,4,5,5,7) ,8,8,9,9,10)。 [提示:使用二分搜索的概念]

6 个答案:

答案 0 :(得分:272)

我必须同意,第一次看到O(log n)算法时,它很奇怪......这个对数来自何处?然而,事实证明,有几种不同的方法可以让你的日志术语以big-O表示法出现。以下是一些:

重复除以常数

取任意数字n;比方说,16。在得到小于或等于1的数字之前,你可以将n除以2的次数?对于16岁,我们有那个

16 / 2 = 8
 8 / 2 = 4
 4 / 2 = 2
 2 / 2 = 1

请注意,最终需要完成四个步骤。有趣的是,我们还有那个日志 2 16 = 4.嗯...... 128怎么样?

128 / 2 = 64
 64 / 2 = 32
 32 / 2 = 16
 16 / 2 = 8
  8 / 2 = 4
  4 / 2 = 2
  2 / 2 = 1

这需要七个步骤,并且记录 2 128 = 7.这是巧合吗?不!这是一个很好的理由。假设我们将数n除以2次i次。然后我们得到数字n / 2 i 。如果我们想要求解这个值最多为1的i的值,我们得到

  

n / 2 i ≤1

     

n≤2 i

     

log 2 n≤i

换句话说,如果我们选择一个整数i使得i≥log 2 n,那么在将n分为i次后,我们将得到一个至多为1的值。保证这个的最小i大致是log 2 n,所以如果我们有一个除以2的算法直到数字变得足够小,那么我们可以说它终止于O(log n) )步骤。

一个重要的细节是,将你除以n的常数并不重要(只要它大于1);如果除以常数k,则log k n步骤达到1.因此,任何重复将输入大小除以某个分数的算法都需要O(log n)次迭代才能终止。那些迭代可能需要花费很多时间,因此净运行时不需要是O(log n),但步数将是对数的。

那么这会出现在哪里?一个典型的例子是 binary search ,这是一种用于在排序数组中搜索值的快速​​算法。该算法的工作方式如下:

  • 如果数组为空,则返回元素不存在于数组中。
  • 否则:
    • 查看数组的中间元素。
    • 如果它与我们正在寻找的元素相同,则返回成功。
    • 如果它大于我们正在寻找的元素:
      • 丢掉阵列的后半部分。
      • 重复
    • 如果它小于我们正在寻找的元素:
      • 扔掉阵列的前半部分。
      • 重复

例如,要在数组中搜索5

1   3   5   7   9   11   13

我们首先看一下中间元素:

1   3   5   7   9   11   13
            ^

从7开始> 5,并且由于数组是排序的,我们知道数字5不能在数组的后半部分,所以我们可以丢弃它。这留下了

1   3   5

现在我们来看看这里的中间元素:

1   3   5
    ^

从3< 5,我们知道5不能出现在数组的前半部分,所以我们可以抛出前半部分来离开

        5

我们再次看一下这个数组的中间部分:

        5
        ^

由于这正是我们正在寻找的数字,我们可以报告5确实在数组中。

这有多高效?好吧,在每次迭代中,我们都会丢弃至少一半剩余的数组元素。一旦数组为空或我们找到我们想要的值,算法就会停止。在最坏的情况下,元素不存在,所以我们将数组的大小减半,直到元素耗尽为止。这需要多长时间?好吧,既然我们一遍又一遍地将数组切成两半,我们将在大多数O(log n)迭代中完成,因为我们不能将数组切割成O(log n)次的一半以上我们用完了数组元素。

遵循 divide-and-conquer 的一般技术(将问题分解成碎片,解决这些碎片,然后将问题重新组合在一起)的算法往往会出于对数条件出于同样的原因 - 你不能将对象切割成O(log n)次的一半。您可能希望将 merge sort 视为一个很好的例子。

一次处理一位数值

基数为10的数字是多少?好吧,如果数字中有k个数字,那么我们认为最大数字是10 k 的某个倍数。最大的k位数是999 ... 9,k次,这等于10 k + 1 - 1.因此,如果我们知道n中有n个数字,那么我们知道n的值最多为10 k + 1 - 1.如果我们想用n求解k,我们得到

  

n≤10 k + 1 - 1

     

n +1≤10 k + 1

     

log 10 (n + 1)≤k+ 1

     

(log 10 (n + 1)) - 1≤k

从中我们得到k大约是n的基数-10对数。换句话说,n中的位数是O(log n)。

例如,让我们考虑添加两个太大而不适合机器字的大数字的复杂性。假设我们在基数10中有这些数字,我们将数字m和n称为。添加它们的一种方法是通过小学 - 学校方法 - 一次写出一位数字,然后从右到左工作。例如,要添加1337和2065,我们首先将数字写为

    1  3  3  7
+   2  0  6  5
==============

我们添加最后一位数字并带有1:

          1
    1  3  3  7
+   2  0  6  5
==============
             2

然后我们添加倒数第二个("倒数第二个")数字并携带1:

       1  1
    1  3  3  7
+   2  0  6  5
==============
          0  2

接下来,我们添加倒数第三个("倒数第二个")数字:

       1  1
    1  3  3  7
+   2  0  6  5
==============
       4  0  2

最后,我们添加倒数第四个(" preantepenultimate" ...我爱英语)数字:

       1  1
    1  3  3  7
+   2  0  6  5
==============
    3  4  0  2

现在,我们做了多少工作?我们每个数字总共进行O(1)工作(即,一定量的工作),并且有需要处理的O(max {log n,log m})总数。这给出了总共O(max {log n,log m})的复杂度,因为我们需要访问这两个数字中的每个数字。

许多算法在某些基础上一次只能处理一个数字,从而得到一个O(log n)项。一个典型的例子是 radix sort ,它一次对一个数字进行整数排序。有许多种基数排序,但它们通常在时间O(n log U)中运行,其中U是被排序的最大可能整数。这样做的原因是排序的每次传递花费O(n)时间,并且处理被排序的最大数字的每个O(log U)数字所需的总共O(log U)次迭代。许多高级算法(例如Gabow's shortest-paths algorithmFord-Fulkerson max-flow algorithm的缩放版本)都有一个日志术语,因为它们一次只能处理一个数字。


关于你如何解决这个问题的第二个问题,你可能想看看this related question,它探讨了一个更高级的应用程序。鉴于此处描述的问题的一般结构,您现在可以更好地了解在您知道结果中的日志条件时如何思考问题,因此我建议不要查看答案,直到您# 39;我已经考虑过了。

希望这有帮助!

答案 1 :(得分:7)

当我们谈论大哦描述时,我们通常会讨论解决给定大小问题所需的时间。通常,对于简单的问题,这个大小只是以输入元素的数量为特征,并且通常称为n或N.(显然,这并不总是正确的 - 图形的问题通常以顶点数量为特征,V和边数E;但是现在,我们将讨论对象列表,列表中有N个对象。)

当且仅当:

我们说一个问题“是(N的某些功能)的大哦”

对于所有N>一些任意的N_0,有一些常数c,这样算法的运行时间小于那个常数c次(N的某个函数)。

换句话说,不要考虑设置问题的“持续开销”很重要的小问题,考虑大问题。在考虑大问题时,(O的某些函数)的大哦意味着运行时总是小于函数的某些常数。总是。

简而言之,该函数是一个上限,直到一个常数因子。

所以,“log-n的大哦”意味着我上面说的相同,除了“N的某些功能”被“log(n)”替换。

所以,你的问题告诉你要考虑二元搜索,所以让我们考虑一下。假设您有一个按递增顺序排序的N个元素的列表。您想知道该列表中是否存在某个给定的数字。一种方法是二进制搜索只是扫描列表中的每个元素,看看它是否是您的目标号码。你可能会很幸运,并在第一次尝试时找到它。但在最坏的情况下,你会检查N次。这不是二进制搜索,它不是log(N)的大哦,因为没有办法强制它进入我们在上面勾勒出的标准。

你可以选择那个任意常数为c = 10,如果你的列表中有N = 32个元素,你就可以了:10 * log(32)= 50,大于32的运行时间。但是如果N = 64,10 * log(64)= 60,小于64的运行时间。你可以选择c = 100,或1000,或者gazillion,你仍然可以找到一些违反它的N.需求。换句话说,没有N_0。

但是,如果我们进行二分搜索,我们选择中间元素并进行比较。然后我们扔出一半的数字,然后再次,再次,等等。如果您的N = 32,那么您只能执行约5次,即log(32)。如果你的N = 64,你只能这样做6次左右等等。现在你可以选择那个任意常数c,这样就可以满足大N值的要求。< / p>

在所有背景下,O(log(N))通常意味着你有一些方法可以做一件简单的事情,这可以将问题的大小减少一半。就像上面的二元搜索一样。一旦你将问题减半,你可以再次将它切成两半,一次又一次。但是,关键的是,你不能做的事情是一些预处理步骤需要比O(log(N))时间更长的时间。因此,举例来说,除非你能在O(log(N))时间找到一种方法,否则你不能将两个列表混合到一个大的列表中。

(注意:几乎总是,Log(N)表示log-base-two,这是我上面假设的。)

答案 2 :(得分:4)

在以下解决方案中,所有具有递归调用的行都已完成 X和Y子阵列的给定大小的一半。 其他线路在恒定时间内完成。 递归函数是T(2n)= T(2n / 2)+ c = T(n)+ c = O(lg(2n))= O(lgn)。

您从MEDIAN(X,1,n,Y,1,n)开始。

MEDIAN(X, p, r, Y, i, k) 
if X[r]<Y[i]
    return X[r]
if Y[k]<X[p]
    return Y[k]
q=floor((p+r)/2)
j=floor((i+k)/2)
if r-p+1 is even
    if X[q+1]>Y[j] and Y[j+1]>X[q]
        if X[q]>Y[j]
            return X[q]
        else
            return Y[j]
    if X[q+1]<Y[j-1]
        return MEDIAN(X, q+1, r, Y, i, j)
    else
        return MEDIAN(X, p, q, Y, j+1, k)
else
    if X[q]>Y[j] and Y[j+1]>X[q-1]
        return Y[j]
    if Y[j]>X[q] and X[q+1]>Y[j-1]
        return X[q]
    if X[q+1]<Y[j-1]
        return MEDIAN(X, q, r, Y, i, j)
    else
        return MEDIAN(X, p, q, Y, j, k)

答案 3 :(得分:2)

当解决方案基于n上的迭代时,我们称时间复杂度为O(log n),其中每次迭代中完成的工作是前一次迭代的一小部分,因为算法适用于解决方案。

答案 4 :(得分:1)

还不能评论......这是坏消息! Avi Cohen的回答不正确,请尝试:

X = 1 3 4 5 8
Y = 2 5 6 7 9

没有条件是真的,因此MEDIAN(X,p,q,Y,j,k)将同时削减五个。这些是非递减序列,并非所有值都是不同的。

同时尝试使用不同值的偶数长度示例:

X = 1 3 4 7
Y = 2 5 6 8

现在MEDIAN(X,p,q,Y,j + 1,k)将削减四个。

相反,我提供此算法,使用MEDIAN(1,n,1,n)调用它:

MEDIAN(startx, endx, starty, endy){
  if (startx == endx)
    return min(X[startx], y[starty])
  odd = (startx + endx) % 2     //0 if even, 1 if odd
  m = (startx+endx - odd)/2
  n = (starty+endy - odd)/2
  x = X[m]
  y = Y[n]
  if x == y
    //then there are n-2{+1} total elements smaller than or equal to both x and y
    //so this value is the nth smallest
    //we have found the median.
    return x
  if (x < y)
    //if we remove some numbers smaller then the median,
    //and remove the same amount of numbers bigger than the median,
    //the median will not change
    //we know the elements before x are smaller than the median,
    //and the elements after y are bigger than the median,
    //so we discard these and continue the search:
    return MEDIAN(m, endx, starty, n + 1 - odd)
  else  (x > y)
    return MEDIAN(startx, m + 1 - odd, n, endy)
}

答案 5 :(得分:1)

在算法复杂度分析中经常会弹出Log项。以下是一些解释:

1。你如何代表一个数字?

让我们取数字X = 245436.“245436”这个符号有隐含的信息。明确地说明这些信息:

  

X = 2 * 10 ^ 5 + 4 * 10 ^ 4 + 5 * 10 ^ 3 + 4 * 10 ^ 2 + 3 * 10 ^ 1 + 6 * 10 ^ 0

这是数字的十进制扩展。因此,我们需要表示此数字的最小信息量 6 位数。这并非巧合,因为任何小于 10 ^ d 的数字都可以用 d 数字表示。

那么代表X需要多少位?这等于X中最大指数10加1。

  

==&GT; 10 ^ d> X
  ==&GT; log(10 ^ d)&gt; log(X)
  ==&GT; d * log(10)&gt; log(X)
  ==&GT; d> log(X)//再次出现日志......
  ==&GT; d = floor(log(x))+ 1

另请注意,这是表示此范围内数字的最简洁方法。任何减少都会导致信息丢失,因为丢失的数字可以映射到其他10个数字。例如:12 *可以映射到120,121,122,...,129。

2。如何在(0,N-1)中搜索数字?

N = 10 ^ d,我们使用最重要的观察结果:

  

唯一标识0到N = 1 = log(N)位数范围内的值的最小信息量。

这意味着,当要求在整数行上搜索数字时,范围从0到N - 1,我们需要至少 log(N)尝试找到它。为什么?任何搜索算法都需要在搜索数字时选择一个接一个的数字。

需要选择的最小位数是log(N)。因此,在大小为N的空间中搜索数字所需的最小操作次数是log(N)。

您能猜出二分搜索,三元搜索或十进制搜索的顺序复杂性吗?
它的 O(log(N))!

第3。你如何对一组数字进行排序?

当被要求将一组数字A排序到数组B中时,这就是它的样子 - &gt;

Permute Elements

原始数组中的每个元素都必须映射到排序数组中的相应索引。所以,对于第一个元素,我们有n个位置。要在0到n - 1的范围内正确找到相应的索引,我们需要... log(n)操作。

下一个元素需要log(n-1)个操作,下一个log(n-2)等等。总数为:

  

==&GT; log(n)+ log(n - 1)+ log(n - 2)+ ... + log(1)

使用log(a)+ log(b)= log(a * b) ,

= =&gt; log(n!)

这可以是approximated到nlog(n) - n。
哪个 O(n * log(n))!

因此我们得出结论,没有排序算法可以做得比O(n * log(n))更好。一些具有这种复杂性的算法是流行的Merge Sort和Heap Sort!

这些是我们在算法的复杂性分析中经常看到log(n)弹出的一些原因。同样可以扩展到二进制数。我在这里制作了一个视频。
Why does log(n) appear so often during algorithm complexity analysis?

干杯!