我遇到了一个问题,需要计算非常大的阶乘值。我用 C++ 用两种不同的方式解决了这个问题,但我只想知道我的复杂度分析是否准确。
在任一方法中,我将非常大的数字表示为向量,其中v[0]
表示最不重要的数字,最后一个索引处的值表示最重要的数字。版本1的代码可以在此代码片段中找到。
根据上述代码,似乎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!))
步骤。展示这一点的求和公式如下:
我从第二个求和符号跳到大O符号的逻辑如下...将其拆分,我们得到以下内容:
1log(1) + 2log(2) + 3log(3) + ... + nlog(n)
很明显,我们可以得到log(1)+log(2)+...+log(n)
的O(n^2)
项。对数法则提醒我们,log(a)+log(b)=log(ab)
,这意味着在这种情况下,对数项会合并为log(n!)
。因此,我们有O(n^2log(n!))
。
这将使该程序的总时间复杂度为O(n^2log(n!))
。这个分析正确吗?
朴素版本时间复杂度
为了练习复杂性分析,我想看一下似乎不那么高效的解决方案。假设我们改变我们的multiplyVectorByInteger()
函数,使它不再以O(log(n!))
的时间将k
的向量表示乘以整数n
来生成n!
,而新的multiplyVectorByIntegerNaive()
函数将一个数字的向量表示相加共计n
次。
multiplyVectorByIntegerNaive()
在这个gist中。它使用一个addVectors()
函数,其复杂度为O(n)
,其中n是两个向量中较大的那个的大小。
很明显,我们仍然调用这个新的乘法函数n
次,但我们需要看看复杂度是否发生了变化。例如,给定整数6
和5!
的向量表示,我们将5!+5!+5!+5!+5!+5!
相加,以得到6*5!=6!
。如果要将给定的整数传递给我们的乘法函数,则很明显我们要进行i-1
次加法。我们可以枚举针对先前示例调用朴素乘法函数的步骤。
5! + 5!
2*5! + 5!
3*5! + 5!
4*5! + 5!
5*5! + 5!
现在写出完整的求和式应该是:
根据我的计算,这两种方法的渐近复杂度相同。这是真的吗?
\sum_{i=0}^n (log i!) <= n*log n!
,你怎么会得到n^2
呢? - n. m....似乎那个
i乘以
log(i)的系数产生了
(n*(n+1))/2项,这是
O(n^2),这就是为什么我有一个
n` 的系数。 - Dominic Farolinon
。显然我的分析是错误的,但是如果你看一下我在写出第一个求和之后的陈述,从第二个求和跳到大O符号的逻辑是... 你能告诉我这里有什么问题吗?简而言之,似乎有(n*(n+1))/2
个log(i)
的项。由于log(i)
的求和等于log(n!)
,似乎应该是O(n^2log(n!))
,但正如你所说,这里有些问题。 - Dominic Farolino