计算大的因子时间复杂度

时间:2016-08-23 05:25:13

标签: c++ algorithm time-complexity factorial asymptotic-complexity

我遇到了一个需要计算非常大的阶乘值的问题。我用两种不同的方式在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!,我们会传递整数65!的向量表示。该函数将返回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!))步骤。总结如下:

sum1

我从第二次总结跳到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次。

gist中存在

multiplyVectorByIntegerNaive()。它使用函数addVectors(),其复杂度为O(n),其中n个大小为两个向量中较大的一个。

很明显,我们仍在调用这个新的乘法函数n次,但我们需要看看复杂性是否已经改变。例如,给定整数65!的向量表示,我们将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!

现在写出完整的总和应该给出:

sum2

考虑到我的计算是准确的,两种方法的渐近复杂性似乎是相同的。这是真的吗?

2 个答案:

答案 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因素。