Big Theta 是在最坏和最好的情况下时间复杂度相同的符号。
由于这是您手头的情况,您可以在 Big Theta 的大 O 中写下您所拥有的内容。
当涉及其他未知数据(例如大小为 n 的数组,但其中包含您事先不知道的值时,您会在最坏/最佳情况下有所不同。在这种情况下,算法可能具有不同的运行时间,具体取决于该数据。另一个例子是当您涉及一些随机生成器时(这只是获取未知数据的另一种方式)。
但由于您的算法中没有任何未知数据,因此可以使用 Big Theta 表示法来编写时间复杂度。
时间复杂度证明:
然而,复杂性不是 Θ(n.log2(n))。它实际上是一个普通的 Θ(n)。
时间复杂度可以从这个扩展代码中count变量的增量数推导出来,因为对于外循环的每次迭代,中间的循环至少执行一次,并且对于外循环的每次迭代中间一个,最里面的循环也至少执行一次:
for (i = 1; i < n; i++) {
for (j = i; j < n; j *= 2)
for (k = j; k < n; k *= 2)
count++;
内部的两个循环与此替代(伪)代码中的增量相同,其中 log 代表以 2 为底的对数:
for (j = log(i); j < log(n); j++)
for (k = j; k < log(n); k++)
count++;
也可以:
for (j = 0; j < log(n) - log(i); j++)
for (k = j; k < log(n) - log(i); k++)
count++;
将 m 定义为 j 上的循环对给定 i 进行的迭代次数,然后是 m > 是 log(n) - log(i) 向上舍入到最接近的整数。给定 i 的增量数对应于从 m 值中选择 2 个值的方法数,其中第二个值大于或等于第一个值。这个数字是m⋅(m+1)/2。
除去向上舍入,我们可以通过将 log(n) - log(i) 加一来找到 m 的上限。让我们将 m 重新定义为这个上限:
m = log(n) − log(i) + 1。
然后我们得到这个由给定 i 的内部两个循环增加的 count 数量的上限:
½m⋅(m+1)
将其分解为对数部分,我们得到:
½(log(n) − log(i) + 1)⋅(log(n) − log(i) + 2)
= ½log2 (n) + (3/2)⋅log(n) + 1 + (−log(n)−3/2)⋅log(i) + ½log2(i)
加上外循环的效果,我们得到了整体count的上限:
计数 2(n) + (3/2)⋅n⋅log(n) + n + (−log(n)−3/2)⋅ Σilog(i) + ½Σilog2(i)
需要解决 i 的总和。对数之和是乘积的对数,所以我们有这个等式:
Σilog(i) = log(n!)
n来自外循环的迭代次数。其实外层循环并没有迭代那么多次,因为它的最后一次迭代是用i = n − 1,但是为了定义一个上界,我们可以选择取 n 也在船上。
applying the Stirling formula 将一些自然对数(此处记为 ln)转换为基于 2 的对数,我们得到了这个有用的渐近近似:
log(n!) = (n+½)⋅log(n) - n/ln(2) + ½log(2π) + O(1/n)
= n⋅log(n) + (−1/ln(2))⋅n + ½⋅log(n) + ½log(2π) + O(1/n)
对于带有总和的最后一项,Σilog2(i),我们可以使用Euler-Maclaurin formula,并使用integral over this function(另见"sum of log squared terms"),我们得出:
Σilog2(i) = n⋅[log2(n) - (2/ln(2 ))⋅log(n) + 2/ln2(2)] − 2/ln2(2)
+ ½log2(n) + O(log(n)/n)
= n⋅log2(n) - ( 2/ln(2))⋅n⋅log(n) + (2/ln2(2))⋅n + (−2)/ln2(2 ) + ½log2(n) + O(log(n)/n)
将其纳入我们为 count 提供的内容中:
计数 2(n) + (3/2)⋅n⋅log(n) + n
+ (− log(n)−3/2)⋅Σilog(i)
+ ½Σilog2 sup>(i)
我们得到:
计数 2(n) + (3/2)⋅n⋅log(n) + n
+ (− log(n)−3/2)⋅[ n⋅log(n) + (−1/ln(2))⋅n + ½⋅log(n) + ½log(2π) ]
+ ½[ n⋅log2(n) - (2/ln(2))⋅n⋅log(n) + (2/ln2(2 ))⋅n + (−2)/ln2(2) + ½log2(n)]
+ O(log (n)/n)
= ½n⋅log2(n) + (3/2)⋅n⋅log(n) + n
+ (−n −½)⋅log(n) + (1/ln(2))⋅n⋅log(n) + (−½)⋅log(2π)⋅log(n) + (−3n/2−3/4) ⋅log(n) + (3/(2ln(2)))⋅n + (−3/4)⋅log(2π)
+ ½n⋅log2 sup>(n) − (1/ln(2))⋅n⋅log(n) + (1/ln2(2))⋅n + (−1)/ln 2(2) + (1/4)⋅log2(n)
+ O(log(n)/n)
将所有在n方面具有相同意义的术语放在一起,结果是因子为n⋅log2(n) 和 n⋅log(n) 相互消除。所以我们得到:
计数 2(2))⋅n
+ (−1/4)⋅log2(n) + ((−1/2)⋅log(2π)−3/4)⋅log(n)
+ (−3/4)⋅log(2π) + (−1)/(ln2(2))
+ O(log(n)/n)
所以这是 ... Θ(n),并且为 (1 + 3/(2⋅ln(2)) + 1/ln2(2)) 大约是 5.24 并且少数几个 next 表达式具有负系数,count 存在以下上限:
计数
这是一个上限。它不能比 Θ(n) 更好,因为外部循环执行 Θ(n) 次。
测试它
根据经验,您可以看到表达式 count/n 对于大 n 收敛到值 4。这是一个保持增量 n 的小 sn-p,执行算法并输出这个分数:
function f(n) {
var i, j, k, count = 0;
for (i = 1; i <= n; i++) {
for (j = i; j < n; j *= 2)
for (k = j; k < n; k *= 2)
count++;
}
return count;
}
// keep increasing n forever, and print f(n)/n
var n = 2;
setInterval(function () {
document.body.textContent = 'n = ' + n
+ '\nf(n) = ' + f(n)
+ '\nf(n)/n = ' + (f(n)/n);
n = n + 1;
}, 1);
body { white-space: pre }
注意:我觉得必须有一个更简洁和优雅的证明,但这是我现在能想到的。