What would cause an algorithm to have O(log n) complexity?
我对big-o的了解是有限的,当日志项出现在等式中时,它会让我更加困惑。
有人能简单地向我解释一下
当我试图解决这个期中练习问题时,特别出现了这个问题:
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) } |