关于big o:什么会导致算法具有O(log n)复杂度?

What would cause an algorithm to have O(log n) complexity?

我对big-o的了解是有限的,当日志项出现在等式中时,它会让我更加困惑。

有人能简单地向我解释一下O(log n)算法是什么吗?对数是从哪里来的?

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

Let X(1..n) and Y(1..n) contain two lists of integers, each sorted in nondecreasing order. Give an O(log n)-time algorithm to find the median (or the nth smallest integer) of all 2n combined elements. For ex, X = (4, 5, 7, 8, 9) and Y = (3, 5, 8, 9, 10), then 7 is the median of the combined list (3, 4, 5, 5, 7, 8, 8, 9, 9, 10). [Hint: use concepts of binary search]


我不得不承认,当你第一次看到O(log n)算法时,这很奇怪…这个对数到底是从哪里来的?然而,事实证明有几种不同的方法可以让日志项以大O符号显示。这里有几个:好的。反复除以常数

取任意数n,比如16。在得到一个小于或等于1的数之前,可以将n除以2几次?我们有16个好的。

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

请注意,这最终需要采取四个步骤来完成。有趣的是,我们也有这个日志216=4。六羟甲基三聚氰胺六甲醚。。。128怎么样?好的。

1
2
3
4
5
6
7
128 / 2 = 64
 64 / 2 = 32
 32 / 2 = 16
 16 / 2 = 8
  8 / 2 = 4
  4 / 2 = 2
  2 / 2 = 1

这采取了七个步骤,并记录2128=7。这是巧合吗?不!这是一个很好的理由。假设我们把n个数除以2次。然后,我们得到数n/2i。如果我们想解决I值,其中这个值最多为1,我们得到。好的。

n / 2i ≤ 1

Ok.

n ≤ 2i

Ok.

log2 n ≤ i

Ok.

换句话说,如果我们选择一个整数i,i和GE;log <子> 2 /Sub > n,然后在将n除以I倍之后,我们将得到最多1的值。这个保证的最小i大约是log<2>/Sub>n,所以如果我们有一个除以2的算法,直到数字变得足够小,那么我们可以说它终止于O(log n)步骤。好的。

一个重要的细节是,不管你把n除以什么常数(只要它大于1),如果按常数k除以,则log>k>k<子>n步可达1。因此,任何迭代算法将输入大小分成若干部分将需要O(log n)迭代终止。这些迭代可能需要很多时间,因此网络运行时不需要是O(log n),但步骤的数量将是对数。好的。

这是怎么回事?一个经典的例子是二进制搜索,这是一种快速的搜索值排序数组的算法。算法的工作原理如下:好的。

  • 如果数组为空,则返回该元素不在数组中。
  • 否则:
    • 看看数组的中间元素。
    • 如果它等于我们正在寻找的元素,那么就返回成功。
    • 如果它大于我们要寻找的元素:
      • 扔掉阵列的后半部分。
      • 重复
    • 如果它小于我们要寻找的元素:
      • 扔掉阵列的前半部分。
      • 重复

例如,在数组中搜索5好的。

1
1   3   5   7   9   11   13

我们首先看中间的元素:好的。

1
2
1   3   5   7   9   11   13
            ^

因为7>5,而且数组是排序的,所以我们知道数字5不能在数组的后半部分,所以我们可以丢弃它。这片叶子好的。

1
1   3   5

现在我们来看中间的元素:好的。

1
2
1   3   5
    ^

由于3<5,我们知道5不能出现在数组的前半部分,所以我们可以将前半部分数组丢弃。好的。

1
        5

我们再来看看这个数组的中间部分:好的。

1
2
        5
        ^

因为这正是我们要查找的数字,所以我们可以报告5确实在数组中。好的。

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

遵循分而治之的一般技术的算法(将问题分割成多个部分,解决这些部分,然后将问题放回一起)倾向于使用对数项,原因是相同的——不能将某个对象分割超过0(log n)次的一半。您可能希望将合并排序视为这方面的一个很好的例子。好的。一次处理一位数

10位数N有多少位数?如果数字中有k个数字,那么我们的最大数字是10K的倍数。最大的k位数是999…9,k倍,这等于10K+ 1到1。因此,如果我们知道n有k个数字,那么我们知道n的值最多是10K+1—1。如果我们想用n来求解k,则得到好的。

n ≤ 10k+1 - 1

Ok.

n + 1 ≤ 10k+1

Ok.

log10 (n + 1) ≤ k + 1

Ok.

(log10 (n + 1)) - 1 ≤ k

Ok.

从中我们得到k大约是n的以10为底的对数。换句话说,n中的位数是o(对数n)。好的。

例如,让我们考虑添加两个太大而无法放入机器字中的大数字的复杂性。假设这些数字是以10为基数的,我们会把它们称为m和n。加起来的一种方法是通过小学方法——一次写出一个数字,然后从右向左计算。例如,要添加1337和2065,我们首先将数字写为好的。

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

我们加上最后一个数字,然后加上1:好的。

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

然后,我们将第二个到最后一个("倒数第二")数字相加,并携带1:好的。

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

接下来,我们将第三个到最后一个("倒数第二个")数字相加:好的。

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

最后,我们将第四个添加到最后一个("prentepenultimate"……我喜欢英语)数字:好的。

1
2
3
4
5
       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)项。一个经典的例子是基数排序,它对整数一次一位数进行排序。基数排序有很多种风格,但它们通常以时间O(n log u)运行,其中u是正在排序的最大可能整数。这样做的原因是,每次排序都需要O(n)时间,并且总共需要O(log u)迭代来处理被排序的最大数字的每个O(logu)位数。许多高级算法,如Gabow的最短路径算法或Ford Fulkerson Max Flow算法的缩放版本,在其复杂性中都有一个对数项,因为它们一次只能工作一个数字。好的。

关于如何解决该问题的第二个问题,您可能想看看这个相关的问题,它探索了一个更高级的应用程序。考虑到这里描述的问题的一般结构,当您知道结果中有一个日志项时,您现在可以更好地了解如何考虑问题,因此我建议您不要在考虑之前查看答案。好的。

希望这有帮助!好的。好啊。


当我们谈论大的OH描述时,我们通常谈论的是解决给定规模的问题所需的时间。通常,对于简单的问题,这个大小只是以输入元素的数量为特征,通常称为n或n(显然,这并不总是正确的——图的问题通常以顶点的数量、v和边的数量e为特征;但现在,我们将讨论对象列表,其中n个对象在列表中。)

我们说一个问题"是(n的某个函数)的大哦",如果并且仅当:

对于所有n>某些任意n_0,都有一些常量c,这样算法的运行时间小于常量c次(n的某些函数)。

换言之,不要考虑小问题,因为设置问题的"持续开销"很重要,而要考虑大问题。当考虑大问题时,big-oh of(n的某个函数)意味着运行时间始终小于该函数的某个常数倍。总是。

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

所以,"对数的大哦(n)"和我上面说的一样,只是"n的某个函数"被替换为"对数(n)"。

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

你可以选择任意的常数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的大值的要求总是满足的。

在所有这些背景下,O(log(n))通常意味着你有一些方法来做一件简单的事情,这会将你的问题大小减半。就像上面的二进制搜索一样。一旦你把问题一分为二,你就可以一次又一次地把它一分为二。但是,关键的是,您不能做的是一些预处理步骤,它将花费比O(log(n))时间更长的时间。例如,你不能把你的两个列表放到一个大列表中,除非你也能在O(log(n))时间内找到一种方法。

(注:几乎总是,对数(n)表示对数基数2,这是我上面假定的。)


在下面的解决方案中,所有具有递归调用的行都在上完成x和y子数组给定大小的一半。其他行在固定时间内完成。递归函数为t(2n)=t(2n/2)+c=t(n)+c=o(lg(2n))=o(lgn)。

从中位数开始(x,1,n,y,1,n)。

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
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)

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


对数项在算法复杂性分析中经常出现。这里有一些解释:

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加1中10的最大指数。

==> 10 ^ d > X
==> log (10 ^ d) > log(X)
==> d* log(10) > log(X)
==> d > log(X) // And log appears again...
==> d = floor(log(x)) + 1

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

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

取n=10^d,我们使用最重要的观察:

The minimum amount of information to uniquely identify a value in a range between 0 to N - 1 = log(N) digits.

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

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

你能猜出二元搜索、三元搜索或十元搜索的顺序复杂性吗?
其o(log(n))!< BR>

三。如何对一组数字进行排序?

当被要求将一组数字A排序到数组B中时,下面是它的外观->

排列元素

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

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

==> log(n) + log(n - 1) + log(n - 2) + … + log(1)

Using log(a) + log(b) = log(a * b),

==> log(n!)

这可以近似为n log(n)-n.
这是o(n*log(n))!

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

这就是为什么我们在算法的复杂性分析中经常看到日志(n)弹出的一些原因。这可以扩展到二进制数。我在这里做了一个视频。
为什么在算法复杂度分析中日志(n)经常出现?

干杯!


无法评论…天哪!Avi Cohen的答案是错误的,尝试:

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

没有一个条件是正确的,所以中位数(x,p,q,y,j,k)都将减去五。这些不是非递减序列,不是所有的值都是不同的。

还可以使用不同的值尝试此偶数长度示例:

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

现在中位数(x,p,q,y,j+1,k)将减少四个。

相反,我提供了这个算法,称之为中位数(1,n,1,n):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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)
}