¥£$ ДЕУΙГ

趣味编程之计算相亲数(下)

    承接上篇,我说找到了“超级”算法并没有忽悠大家,但超级并不意味着复杂,该算法按照SP1的思路改良而来,但却比SP1更加短小精悍,更加环保,未避免废话连篇,马上公布算法二SP2,先上代码再做解释:

  1: /// <summary>
  2: /// 部分分解不求和的空间算法
  3: /// </summary>
  4: public class Algorithm6
  5: {
  6:     public List<int> primeList = new List<int>();
  7:     public int[] firstFactorList;
  8:     public int[] remainingList;
  9:     public int[] resultList;
 10:     public int GetNextFactor(int num)
 11:     {
 12:         var max = (int)Math.Sqrt(num);
 13:         for (int i = 0; i < primeList.Count; i++)
 14:         {
 15:             var p = primeList[i];
 16:             if (p > max) break;
 17:             if (num % p == 0)
 18:                 return p;
 19:         }
 20:         primeList.Add(num);
 21:         return num;
 22:     }
 23:     public int GetSum(int num)
 24:     {
 25:         var factor = firstFactorList[num] = GetNextFactor(num);
 26:         if (factor == num)
 27:         {
 28:             remainingList[num] = 1;
 29:             return 1;
 30:         }
 31:         else
 32:         {
 33:             remainingList[num] = num / firstFactorList[num];
 34:             var r = remainingList[num];
 35:             while (r % factor == 0)
 36:                 r = r / factor;
 37:             return (resultList[remainingList[num]] + remainingList[num]) * factor + (resultList[r] + r) - num;
 38:         }
 39:     }
 40:     public void Run(int limit)
 41:     {
 42:         firstFactorList = new int[limit];
 43:         remainingList = new int[limit];
 44:         resultList = new int[limit];
 45:         int perfertCount = 0;
 46:         int amicablePairCount = 0;
 47:         for (var num = 2; num < limit; num++)
 48:         {
 49:             var result = resultList[num] = this.GetSum(num);
 50:             if (result == num)
 51:             {
 52:                 Console.WriteLine("{0}是完全数", num);
 53:                 perfertCount++;
 54:             }
 55:             else if (result < num && resultList[result] == num)
 56:             {
 57:                 Console.WriteLine("{0}和{1}是一对相亲数", result, num);
 58:                 amicablePairCount++;
 59:             }
 60:         }
 61:         Console.WriteLine("在{0}到{1}中至少有{2}个完全数和{3}对相亲数", 2, limit, perfertCount, amicablePairCount);
 62:     }
 63: }

    从上方的变量定义来看,似乎缓存变量没有发生变化,但代码减少了,少了函数Decomposition和Sum,GetSum函数也发生了变化,没有了复杂的for循环,这难道就比之前的缓存版本快吗?答案是肯定的,不仅快,而且快很多,因为这个算法既没有直接计算约数也没有加和的过程,仅仅是计算了数字的第一个因子,然后利用先前计算的其他数字的结果得出约数之和,听起来很不可思议,听我细细说来,先看一组递推公式,我们记一个自然数n的所有约数之和为S(n),它的第一个因子(大于1的最小因子)为f,它除去第一个因子f(既如果N包含多个f的因子就全部除去)后的值为r。由于相亲数和完全数的计算都是从2开始,但我们的递推可以从1开始,所以我们令S(1)=1,然后我们就可以得出下表:

n

Decomposition

S(n)

f

n/f

r

Expression

Formula

1 / 1 / / / / /
2 2^1 3 2 1 1 n + 1 S(n/f) * f + S(r)
3 3^1 4 3 1 1 n + 1 S(n/f) * f + S(r)
4 2^2 7 2 2 1 S(2) * 2 + S(1) S(n/f) * f + S(r)
5 5^1 6 5 1 1 n + 1 S(n/f) * f + S(r)
6 2^1 * 3^1 12 2 3 3 S(3) * 2 + S(3) S(n/f) * f + S(r)
7 7^1 8 7 1 1 n + 1 S(n/f) * f + S(r)
8 2^3 15 2 4 1 S(4) * 2 + S(1) S(n/f) * f + S(r)
9 3^2 13 3 3 3 S(3) * 2 + S(3) S(n/f) * f + S(r)
10 2^1 * 5^1 18 2 5 5 S(5) * 2 + S(5) S(n/f) * f + S(r)
11 11^1 12 11 1 1 n + 1 S(n/f) * f + S(r)
12 2^2 * 3^1 28 2 6 3 S(6) * 2 + S(3) S(n/f) * f + S(r)
13 13^1 14 13 1 1 n + 1 S(n/f) * f + S(r)
14 2^1 * 7^1 24 2 7 7 S(7) * 2 + S(7) S(n/f) * f + S(r)
15 3^1 * 5^1 24 3 5 5 S(5) * 3 + S(5) S(n/f) * f + S(r)
16 2^4 31 2 8 1 S(8) * 2 + S(1) S(n/f) * f + S(r)
17 17^1 18 17 1 1 n + 1 S(n/f) * f + S(r)
18 2^1 * 3^2 39 2 9 9 S(9) * 2 + S(9) S(n/f) * f + S(r)
19 19^1 20 19 1 1 n + 1 S(n/f) * f + S(r)
20 2^2 * 5^1 42 2 10 5 S(10) * 2 + S(5) S(n/f) * f + S(r)
…… …… …… …… …… …… …… ……

    你是否观察到规律了呢?其实很简单,一个数字的所有约数的和其实本质上是一些质数之间的加法和乘法不断迭加的结果,上表反映了可供程序使用的表达式Expression以及归纳的公式Formula,总结起来是这样的:S(n) = S(n/f) * f + S(r)

    抓个典型12来说事吧,12的第一个因子是2(f=2),12/2=6(n/f=6),12又包含2个2因子(2^2 * 3^1),所以它除去因子2后的值为12/2^2=3(r=3),所以根据递推公式S(12) = S(n/f) * f + S(r) = S(6) * 2 + S(3) = 12 * 2 + 4 = 28;再比如18,18的第一个因子是2(f=2),18/2=9(n/f=9),18只包含1个2因子(2^1 * 3^2),所以它除去因子2后的值为18/2=9(r=9),所以根据递推公式S(18) = S(n/f) * f + S(r) = S(9) * 2 + S(9) = 13 * 2 + 13 = 39;而程序中对质数做了特殊处理直接使用n + 1,这样可以减少一些n/f的除法运算。

    看到这你一定会问,你是怎么得出这个公式的,说来惭愧,我以前很喜欢数学,也参加过数学竞赛之类的,也拿过小名次,但都是学生时代的事情了,工作后几乎接触不到很“高级”的数学问题,数学在我脑子里已经消失大半,所以上面的公式是我一点一点“试”出来的,我把1到50的相关数据列出来,通过观察和试验得出了这个结论,虽然很不科学,但它的确是对的,只是没有经过严谨的数学论证,我觉得用“数学归纳法”肯定可以论证出来,但我早就忘记怎么论了,如果园子里有哪位数学达人可以帮我补上,小弟不胜感激:)

    有了这个公式,根本不用计算约数了,本质上只要计算质数了,然后把这些质数加加乘乘,应该会快很多吧,于是就有了上面的代码,是在SP1的基础上改进的,速度果然快的惊人,从2计算到5000000只需要2.8秒,只要你内存够用,可以尝试更大数字,我的是2G,我算到了62000000(再大就overflow了),花费时间68秒,速度很可观,更兴奋的是,这些数据都是在Debug模式下获得的,我把程序发布成Release版本直接运行,从2计算到5000000只需要1.3秒从2计算到62000000只需要40秒,而基于算法二SP2还是有一些优化和节约空间的方法的,这些就留给有兴趣的读者吧,毕竟优化无极限嘛,如果改成c/c++版本可能速度会更加惊人……

    最后,附上一些本机测试数据和结果供参考:

6是完全数
28是完全数
220和284是一对相亲数
496是完全数
1184和1210是一对相亲数
2620和2924是一对相亲数
5020和5564是一对相亲数
6232和6368是一对相亲数
8128是完全数
10744和10856是一对相亲数
12285和14595是一对相亲数
17296和18416是一对相亲数
66928和66992是一对相亲数
67095和71145是一对相亲数
63020和76084是一对相亲数
69615和87633是一对相亲数
79750和88730是一对相亲数
122368和123152是一对相亲数
100485和124155是一对相亲数
122265和139815是一对相亲数
141664和153176是一对相亲数
142310和168730是一对相亲数
171856和176336是一对相亲数
176272和180848是一对相亲数
196724和202444是一对相亲数
185368和203432是一对相亲数
280540和365084是一对相亲数
308620和389924是一对相亲数
356408和399592是一对相亲数
319550和430402是一对相亲数
437456和455344是一对相亲数
469028和486178是一对相亲数
503056和514736是一对相亲数
522405和525915是一对相亲数
643336和652664是一对相亲数
600392和669688是一对相亲数
609928和686072是一对相亲数
624184和691256是一对相亲数
635624和712216是一对相亲数
667964和783556是一对相亲数
726104和796696是一对相亲数
802725和863835是一对相亲数
879712和901424是一对相亲数
898216和980984是一对相亲数
998104和1043096是一对相亲数
1077890和1099390是一对相亲数
947835和1125765是一对相亲数
1154450和1189150是一对相亲数
1185376和1286744是一对相亲数
1156870和1292570是一对相亲数
1280565和1340235是一对相亲数
1175265和1438983是一对相亲数
1392368和1464592是一对相亲数
1328470和1483850是一对相亲数
1358595和1486845是一对相亲数
1511930和1598470是一对相亲数
1466150和1747930是一对相亲数
1468324和1749212是一对相亲数
1798875和1870245是一对相亲数
1669910和2062570是一对相亲数
2082464和2090656是一对相亲数
2236570和2429030是一对相亲数
2723792和2874064是一对相亲数
2739704和2928136是一对相亲数
2652728和2941672是一对相亲数
2802416和2947216是一对相亲数
2728726和3077354是一对相亲数
2803580和3716164是一对相亲数
3276856和3721544是一对相亲数
3606850和3892670是一对相亲数
3805264和4006736是一对相亲数
3786904和4300136是一对相亲数
4238984和4314616是一对相亲数
4259750和4445050是一对相亲数
4246130和4488910是一对相亲数
4482765和5120595是一对相亲数
4604776和5162744是一对相亲数
5459176和5495264是一对相亲数
5123090和5504110是一对相亲数
5357625和5684679是一对相亲数
5232010和5799542是一对相亲数
5385310和5812130是一对相亲数
5147032和5843048是一对相亲数
5730615和6088905是一对相亲数
4532710和6135962是一对相亲数
5726072和6369928是一对相亲数
6329416和6371384是一对相亲数
6377175和6680025是一对相亲数
6993610和7158710是一对相亲数
6955216和7418864是一对相亲数
7275532和7471508是一对相亲数
5864660和7489324是一对相亲数
7489112和7674088是一对相亲数
7677248和7684672是一对相亲数
7800544和7916696是一对相亲数
7850512和8052488是一对相亲数
7288930和8221598是一对相亲数
8262136和8369864是一对相亲数
7577350和8493050是一对相亲数
9363584和9437056是一对相亲数
9071685和9498555是一对相亲数
9199496和9592504是一对相亲数
8619765和9627915是一对相亲数
9339704和9892936是一对相亲数
9660950和10025290是一对相亲数
8826070和10043690是一对相亲数
10254970和10273670是一对相亲数
8666860和10638356是一对相亲数
9206925和10791795是一对相亲数
10572550和10854650是一对相亲数
8754130和10893230是一对相亲数
10533296和10949704是一对相亲数
9491625和10950615是一对相亲数
9478910和11049730是一对相亲数
10596368和11199112是一对相亲数
9773505和11791935是一对相亲数
11498355和12024045是一对相亲数
10992735和12070305是一对相亲数
11252648和12101272是一对相亲数
11545616和12247504是一对相亲数
11693290和12361622是一对相亲数
12397552和13136528是一对相亲数
11173460和13212076是一对相亲数
11905504和13337336是一对相亲数
13921528和13985672是一对相亲数
10634085和14084763是一对相亲数
12707704和14236136是一对相亲数
13813150和14310050是一对相亲数
14311688和14718712是一对相亲数
15002464和15334304是一对相亲数
13671735和15877065是一对相亲数
14443730和15882670是一对相亲数
16137628和16150628是一对相亲数
15363832和16517768是一对相亲数
14654150和16817050是一对相亲数
15938055和17308665是一对相亲数
17257695和17578785是一对相亲数
17908064和18017056是一对相亲数
14426230和18087818是一对相亲数
18056312和18166888是一对相亲数
17041010和19150222是一对相亲数
18655744和19154336是一对相亲数
16871582和19325698是一对相亲数
17844255和19895265是一对相亲数
17754165和19985355是一对相亲数
20308995和20955645是一对相亲数
20014808和21457192是一对相亲数
18194715和22240485是一对相亲数
20022328和22823432是一对相亲数
21448630和23030090是一对相亲数
22508145和23111055是一对相亲数
22227075和24644925是一对相亲数
23389695和25132545是一对相亲数
23358248和25233112是一对相亲数
22249552和25325528是一对相亲数
25596544和25640096是一对相亲数
22608632和25775368是一对相亲数
26090325和26138475是一对相亲数
25966832和26529808是一对相亲数
23628940和27428276是一对相亲数
28118032和28128368是一对相亲数
28608424和29603576是一对相亲数
24472180和30395276是一对相亲数
30830696和31652704是一对相亲数
31536855和32148585是一对相亲数
30724694和32174506是一对相亲数
33550336是完全数
32205616和34352624是一对相亲数
34364912和34380688是一对相亲数
32685250和34538270是一对相亲数
31818952和34860248是一对相亲数
32642324和35095276是一对相亲数
34256222和35997346是一对相亲数
33501825和36136575是一对相亲数
35472592和36415664是一对相亲数
34765731和36939357是一对相亲数
38400512和38938288是一对相亲数
37848915和39202605是一对相亲数
35390008和39259592是一对相亲数
37784810和39944086是一对相亲数
35373195和40105845是一对相亲数
35361326和40117714是一对相亲数
38637016和40678184是一对相亲数
38807968和40912232是一对相亲数
38783992和41654408是一对相亲数
35115795和43266285是一对相亲数
38663950和43362050是一对相亲数
44139856和44916944是一对相亲数
37363095和45663849是一对相亲数
45263384和46137016是一对相亲数
43096904和46715896是一对相亲数
46991890和48471470是一对相亲数
48641584和48852176是一对相亲数
46271745和49125375是一对相亲数
50997596和51737764是一对相亲数
48639032和52967368是一对相亲数
46521405和53011395是一对相亲数
49215166和55349570是一对相亲数
46555250和55880590是一对相亲数
52695376和56208368是一对相亲数
56055872和56598208是一对相亲数
46237730和61319902是一对相亲数
59497888和61953512是一对相亲数
在2到62000000中至少有5个完全数和198对相亲数
计算完成共花费40.3723054秒

    完整代码下载

posted @ 2010-08-23 15:54  devil0153  阅读(2545)  评论(2编辑  收藏  举报