Euler项目的问题12的措辞与以前的问题有些不同。但是,正如我们将看到为该问题推导出有效的解决方案,我们可以使用与以前的一些问题非常相似的理论。问题在于
通过添加自然数生成三角数的序列。所以第7 个三角形数字是1 + 2 + 3 + 4 + 5 + 6 + 7 = 28.前十个术语是:
1,3,6,10,15,21,28,36,45,55 ......
让我们列出前七个三角形数字的因子:
1:1 3:1,3 6:1,2,3,6 10:1,2,5,10 15:1,3,5,15 21:1,3,7,21 28:1,2, 4,7,14,28 我们可以看到28是第一个有超过五个除数的三角形数。
拥有超过500个除数的第一个三角形数的值是多少?
当我第一次阅读问题描述时,我很快就看到了一个直接的方法来找到问题的答案,我使用试验除法来找到一个数字的除数。这是我将在本博文中描述的第一种方法。之后,我将描述对解决方案策略的两个修改以加速算法。
审判部门非常直截了当。代码的主要部分看起来像
<span style="color:#333333">int number = 0; int i = 1; while(NumberOfDivisors(number) < 500){ number += i; i++; } </span>“智能”在于它所谓的NumberOfDivisors函数。该函数使用所有数字的试验除法,包括原始数字的平方根。
该代码看起来像
<span style="color:#333333">private int NumberOfDivisors(int number) { int nod = 0; int sqrt = (int) Math.Sqrt(number); for(int i = 1; i<= sqrt; i++){ if(number % i == 0){ nod += 2; } } //Correction if the number is a perfect square if (sqrt * sqrt == number) { nod--; } return nod; } </span>我没有太多理由去了解描述代码的细节,因为它代表了自己。结果是
<span style="color:#333333">The first triangle number with over 500 divisors is: 76576500 Solution took 214 ms </span>因此,虽然代码易于阅读,但执行起来并不是非常快。
现在我们已经建立了一个找到答案的方法。问题是如何加快速度。很明显,代码的耗时部分是NumberOfDivisors函数。那么让我们看看我们是否可以改进它。
为了找到一个有效的方法来推导出一个数的除数,我们需要的属性是基于我们在问题3中处理的数的素因子化。 任何数字N由其素因子化唯一地描述
其中p n是不同的素数,n是素数的指数,K是小于或等于N的平方根的所有素数的集合。采用任何素数因子的唯一组合并乘以它们,将产生N的唯一除数。这意味着我们可以使用组合学来确定基于素因子分解的除数的数量。素数p n可以选择0,1,...,n次。这意味着在所有我们可以选择p ñ在一个ñ +1不同的方式。
N,D(N)的除数总数可表示为
现在我们只需要一个函数来执行分解。
我们在问题3的解决方案中使用了素因子化,我将其修改为仅使用素数而不是奇数,并且我已经修改它以实际计算除数而不是返回除数。
代码看起来像
<span style="color:#333333">private int PrimeFactorisationNoD(int number, int[] primelist) { int nod = 1; int exponent; int remain = number; for (int i = 0; i < primelist.Length; i++) { // In case there is a remainder this is a prime factor as well // The exponent of that factor is 1 if (primelist[i] * primelist[i] > number) { return nod * 2; } exponent = 1; while (remain % primelist[i] == 0) { exponent++; remain = remain / primelist[i]; } nod *= exponent; //If there is no remainder, return the count if (remain == 1) { return nod; } } return nod; } </span>它需要一个素数列表,它从最低素数开始,并找到该素数的指数。正如在问题3中一样,如果剩余部分达到1,则没有更多的除数,如果有剩余部分,那么剩下的也将是一个素数除数。
在主循环中,我们还需要生成素数列表。这是使用问题10中开发的Eratosthenes筛子完成的。随着代码行
<span style="color:#333333">int[] primelist = ESieve(75000); </span>我不确定素数除数的上限,所以我试图将它设置为75000,以便有一个相当保守的界限。
使用素因子分解产生相同的结果,但明显快于使用试验分裂。
<span style="color:#333333">The first triangle number with over 500 divisors is: 76576500 Solution took 16 ms </span>我们今天要处理的算法的最后一个改进是将问题分解为两个较小的问题,这些问题明显更容易解决。
在问题6中,我们确定了自然数的总和可以通过分析来编写,这样就可以了
其中N k是第k个三角形数。
我们将使用的属性是k和k + 1是互质(我们在问题9中也处理过)。你可以使用欧几里得的算法在k的一般情况下说服你自己。 由于k和k + 1是互质的,这意味着它们的素数因子是不同的,并且
如果k是偶数,则D(N k)= D(k / 2)D(k + 1),如果k是奇数,则 D(N k)= D(k)D((k + 1)/ 2)。
在第一种情况下,如果k和k + 1是互逆的,则k / 2和k + 1也是如此,因为2将在k的素因子集中,因此不是k + 1。否则它们不会是互质的。因子分解的问题现在已被分为两个较小数字的素数分解,这是一项非常容易的任务。
此外,如果我们编码有点聪明,我们可以在后续迭代中重用k + 1的素因子化,因此我们只需要在每次迭代中分解一个数。
我们已经实现的素数生成器和素因子化可以重复使用,但我们需要重新编写主算法。我选择了一个看起来像的实现
<span style="color:#333333">int number = 1; int i = 2; int cnt = 0; int Dn1 = 2; int Dn = 2; int[] primelist = ESieve(1000); while (cnt < 500) { if (i % 2 == 0) { Dn = PrimeFactorisationNoD(i + 1, primelist); cnt = Dn * Dn1; } else { Dn1 = PrimeFactorisationNoD((i + 1) / 2, primelist); cnt = Dn*Dn1; } i++; } number = i * (i - 1) / 2; </span>根据解释,代码应该是相当自我解释的。其中一个更明显的观察结果是我已经明显收紧了素数代的上限。
算法的执行产生
<span style="color:#333333">The first triangle number with over 500 divisors is: 76576500 Solution took 1 ms </span>对于具有500个除数的数字,没有太大区别,但是我在1200除数时失去了对第二算法的耐心。这个版本在超过5000的除数下仍能顺利运行。因此,对于可伸缩性,最后一个版本是一个重大改进。
我介绍了算法的三次迭代,每次迭代都通过使用数论和组合学的不同区域来显着改善算法的执行。像往常一样,源代码可供下载。它有意义吗?