我遇到了一个需要计算非常大的阶乘值的问题。我用两种不同的方式在C ++中解决了这个问题,但只想知道我的复杂性分析是否准确。
在任何一种方法中,我将非常大的数字表示为向量,其中v[0]
表示最低有效数字,最后一个索引处的值表示最高有效数字。版本1的代码可以在gist中找到。
鉴于上述代码,似乎multiplyVectorByInteger()
为O(log(n*k))
,其中n
是给定的整数,k
是向量表示的数字。我的逻辑是,我们将执行一些与结果数n*k
的长度成比例的步骤,以便生成表示n*k
的向量。 n*k
的长度为O(log(n*k))
有些步骤将在for循环中执行,其他步骤将在while循环中执行。
在这个查找大型因子的程序中,每当我们调用multiplyVectorByInteger()
时,我们将传递整数n
和(n-1)!
的向量表示。这意味着如果我们想要找到6!
,我们会传递整数6
和5!
的向量表示。该函数将返回6!
的向量表示。使用以前的信息,我相信我可以说复杂性是O(log(i!))
,其中i是传入的整数。为了找到大的阶乘,我们必须将这个方法称为O(n)
次,其中n
是我们试图找到的阶乘。我们积累的逻辑将如下所示:
1! = 1!
1!*2 = 2!
2!*3 = 3!
3!*4 = 4!
...
(n-1)!*n = n!
由于我们在每个级别计算i!
,因此我们会在每个级别执行O(log(i!))
步骤。总结如下:
我从第二次总结跳到Big-Oh表示法的逻辑如下......打破这个我们得到以下结果:
1log(1) + 2log(2) + 3log(3) + ... + nlog(n)
很明显,我们得到了O(n^2)
log(1) + log(2) + ... + log(n)
项。日志规则提醒我们log(a) + log(b) = log(ab)
,这意味着此案例中的日志条款会折叠为log(n!)
。因此,我们有O(n^2)log(n!)
。
这将使该程序O(n^2log(n!))
的整体时间复杂度。这种分析是否正确?
为了练习复杂性分析,我想看看效率较低的解决方案。假设我们更改了multiplyVectorByInteger()
函数,以便在k
时间内将n
的向量表示乘以整数O(log(n!))
以生成n!
,而不是multiplyVectorByIntegerNaive()
{1}}函数将数字的向量表示添加到总共n
次。
multiplyVectorByIntegerNaive()
。它使用函数addVectors()
,其复杂度为O(n)
,其中n个大小为两个向量中较大的一个。
很明显,我们仍在调用这个新的乘法函数n
次,但我们需要看看复杂性是否已经改变。例如,给定整数6
和5!
的向量表示,我们将5! + 5! + 5! + 5! + 5! + 5!
添加到6*5! = 6!
。如果乘法函数的给定整数是i
,很明显我们会添加i-1
个。我们可以枚举前一个示例调用我们的朴素乘法函数的步骤。
5! + 5!
2*5! + 5!
3*5! + 5!
4*5! + 5!
5*5! + 5!
现在写出完整的总和应该给出:
考虑到我的计算是准确的,两种方法的渐近复杂性似乎是相同的。这是真的吗?
答案 0 :(得分:4)
您提供的要点中函数的复杂性为O(log10n!)
,其中n
是您传递给方法的数字。
从代码的第一部分可以看出这一点的原因:
for (int i = 0; i < numVector.size(); ++i) {
returnVec[i] = (numVector[i]*n + carry) % 10;
carry = (numVector[i]*n + carry) / 10;
}
传入的numVector
代表(n - 1)!
。即它包含构成该数字的所有数字。但是,该数字的长度只是⌈log10((n-1)!)⌉
。您可以从一个简单的示例中看到这一点:
如果(n-1)!
为100,则numVector
的长度为3,与⌈log10100⌉ = 3
相同。
同样的逻辑也适用于while循环:
while (carry) {
returnVec.push_back(carry%10);
carry /= 10;
}
由于carry
的值不会大于n
(你可以自己证明这一点),那么这个循环运行的最大次数也不会大于{{1然后,函数的总复杂性等同于⌈log10n!⌉
。
因此,要计算O(log10n!)
,代码(包括main)的复杂性将为k!
对于天真的版本,唯一改变的是现在该方法是以加法的形式手动逐步进行乘法。通过明确地将每个值乘以O(klog10k!)
n
这会将函数的复杂性提高到(numVector[i]*n + carry)
,其中O(klog10n!)
因此整个代码的复杂性现在为k! = n
答案 1 :(得分:1)
将k位数乘以整数或加上两个k位数都需要时间与k成正比。
因此,在乘法版本中,总工作量为
Sum[i=1,n]: log(i!) ~ Sum[i=1,n]: i.log(i) ~ n²log(n)
在添加版本中,
Sum[i=1,n]: i.log(i!) ~ Sum[i=1,n]: i².log(i!) ~ n³.log(n)
这些结果可以通过使用斯特林近似和积分而不是求和来确定,
Int x.log(x) dx = x²(log(x)/2 - 1/4)
Int x².log(x) dx = x³(log(x)/3 - 1/9)
正如可以预料的那样,还有一个额外的n
因素。