第六章、亲和数问题--求解500万以内的亲和数

作者:上善若水、July、yansha。
出处:http://blog.csdn.net/v_JULY_v 

前奏
    本章陆续开始,除了继续保持原有的字符串、数组等面试题之外,会有意识的间断性节选一些有关数字趣味小而巧的面试题目,重在突出思路的“巧”,和“妙”。本章亲和数问题之关键字,“500万”,“线性复杂度”。

第一节、亲和数问题
题目描述:
求500万以内的所有亲和数
如果两个数a和b,a的所有真因数之和等于b,b的所有真因数之和等于a,则称a,b是一对亲和数。
例如220和284,1184和1210,2620和2924。

分析:
    首先得明确到底是什么是亲和数?

亲和数问题最早是由毕达哥拉斯学派发现和研究的。他们在研究数字的规律的时候发现有以下性质特点的两个数:
220的真因子是:1、2、4、5、10、11、20、22、44、55、110;
284的真因子是:1、2、4、71、142。
而这两个数恰恰等于对方的真因子各自加起来的和(sum[i]表示数i 的各个真因子的和),即
220=1+2+4+71+142=sum[284],
284=1+2+4+5+10+11+20+22+44+55+110=sum[220]。
得284的真因子之和sum[284]=220,且220的真因子之和sum[220]=284,即有sum[220]=sum[sum[284]]=284。

如此,是否已看出丝毫端倪?

如上所示,考虑到1是每个整数的因子,把出去整数本身之外的所有因子叫做这个数的“真因子”。如果两个整数,其中每一个真因子的和都恰好等于另一个数,那么这两个数,就构成一对“亲和数”(有关亲和数的更多讨论,可参考这:http://t.cn/hesH09)。

求解:
    了解了什么是亲和数,接下来咱们一步一步来解决上面提出的问题(以下内容大部引自水的原话,同时水哥有一句原话,“在你真正弄弄懂这个范例之前,你不配说你懂数据结构和算法”)。

  1. 看到这个问题后,第一想法是什么?模拟搜索+剪枝?回溯?时间复杂度有多大?其中bn为an的伪亲和数,即bn是an的真因数之和大约是多少?至少是10^13(@iicup:N^1.5 对于5*10^6 , 次数大致 10^10 而不是 10^13.)的数量级的。那么对于每秒千万次运算的计算机来说,大概在1000多天也就是3年内就可以搞定了(iicup的计算: 10^13 / 10^7 =1000000(秒) 大约 278 小时. )。如果是基于这个基数在优化,你无法在一天内得到结果的。
  2. 一个不错的算法应该在半小时之内搞定这个问题,当然这样的算法有很多。节约时间的做法是可以生成伴随数组,也就是空间换时间,但是那样,空间代价太大,因为数据规模庞大。
  3. 在稍后的算法中,依然使用的伴随数组,只不过,因为题目的特殊性,只是它方便和巧妙地利用了下标作为伴随数组,来节约时间。同时,将回溯的思想换成递推的思想(预处理数组的时间复杂度为logN(调和级数)*N,扫描数组的时间复杂度为线性O(N)。所以,总的时间复杂度为O(N*logN+N)(其中logN为调和级数)  )。

第二节、伴随数组线性遍历
依据上文中的第3点思路,编写如下代码:

//求解亲和数问题 //第一个for和第二个for循环是logn(调和级数)*N次遍历,第三个for循环扫描O(N)。 //所以总的时间复杂度为 O(n*logn)+O(n)=O(N*logN)(其中logN为调和级数)。 //关于第一个for和第二个for寻找中,调和级数的说明: //比如给2的倍数加2,那么应该是 n/2次,3的倍数加3 应该是 n/3次,... //那么其实就是n*(1+1/2+1/3+1/4+...1/(n/2))=n*(调和级数)=n*logn。 //copyright@ 上善若水 //July、updated,2011.05.24。 #include<stdio.h> int sum[5000010]; //为防越界 int main() { int i, j; for (i = 0; i <= 5000000; i++) sum[i] = 1; //1是所有数的真因数所以全部置1 for (i = 2; i + i <= 5000000; i++) //预处理,预处理是logN(调和级数)*N。 //@litaoye:调和级数1/2 + 1/3 + 1/4......的和近似为ln(n), //因此O(n *(1/2 + 1/3 + 1/4......)) = O(n * ln(n)) = O(N*log(N))。 { //5000000以下最大的真因数是不超过它的一半的 j = i + i; //因为真因数,所以不能算本身,所以从它的2倍开始 while (j <= 5000000) { //将所有i的倍数的位置上加i sum[j] += i; j += i; } } for (i = 220; i <= 5000000; i++) //扫描,O(N)。 { // 一次遍历,因为知道最小是220和284因此从220开始 if (sum[i] > i && sum[i] <= 5000000 && sum[sum[i]] == i) { //去重,不越界,满足亲和 printf("%d %d/n",i,sum[i]); } } return 0; }

运行结果:

@上善若水:

1、可能大家理解的还不是很清晰,我们建立一个5 000 000 的数组,从1到2 500 000 开始,在每一个下标是i的倍数的位置上加上i,那么在循环结束之后,我们得到的是什么?是 类似埃斯托拉晒求素数的数组(当然里面有真的亲和数),然后只需要一次遍历就可以轻松找到所有的亲和数了。时间复杂度,线性。

2、我们可以清晰的发现连续数据的映射可以通过数组结构本身的特点替代,用来节约空间,这是数据结构的艺术。在大规模连续数据的回溯处理上,可以通过转化为递推生成的方法,逆向思维操作,这是算法的艺术。

3、把最简单的东西运用的最巧妙的人,要比用复杂方法解决复杂问题的人要头脑清晰。

第三节、程序的构造与解释
    我再来具体解释下上述程序的原理,ok,举个例子,假设是求10以内的亲和数,求解步骤如下:

因为所有数的真因数都包含1,所以,先在各个数的下方全部置1

  1. 然后取i=2,3,4,5(i<=10/2),j依次对应的位置为j=(4、6、8、10),(6、9),(8),(10)各数所对应的位置。
  2. 依据j所找到的位置,在j所指的各个数的下面加上各个真因子i(i=2、3、4、5)。
    整个过程,即如下图所示(如sum[6]=1+2+3=6,sum[10]=1+2+5=8.):
    1  2  3  4  5  6  7  8  9  10
    1  1  1  1  1  1  1  1  1  1
               2      2      2      2
                       3          3 
                               4
                                       5
  3. 然后一次遍历i从220开始到5000000,i每遍历一个数后,
    将i对应的数下面的各个真因子加起来得到一个和sum[i],如果这个和sum[i]==某个i’,且sum[i‘]=i,
    那么这两个数i和i’,即为一对亲和数。
  4. i=2;sum[4]+=2,sum[6]+=2,sum[8]+=2,sum[10]+=2,sum[12]+=2...
    i=3,sum[6]+=3,sum[9]+=3...
    ......
  5. i=220时,sum[220]=284,i=284时,sum[284]=220;即sum[220]=sum[sum[284]]=284,
    得出220与284是一对亲和数。所以,最终输出220、284,...

特别鸣谢

      litaoye专门为本亲和数问题开帖子继续阐述,有兴趣的朋友可继续参见:http://topic.csdn.net/u/20110526/21/129c2235-1f44-42e9-a55f-878920c21e19.html。同时,任何人对本亲和数问题有任何问题,也可以回复到上述帖子上。

//求解亲和数问题 //copyright@ litaoye //July、胡滨,updated,2011.05.26。 using System; using System.Collections.Generic; namespace CSharpTest { class Program { public static void Main() { int max = 5000000; DateTime start = DateTime.Now; int[] counter = CreateCounter(max); for (int i = 0; i < counter.Length; i++) { int num = counter[i] - i; //if (num < counter.Length && num > i && counter[num] == counter[i]) // Console.WriteLine("{0} {1}", i, num); } Console.WriteLine((DateTime.Now - start).TotalSeconds); Console.ReadKey(); } static int[] CreateCounter(int n) { List<int> primes = new List<int>(); int[] counter = new int[n + 1]; counter[1] = 1; for (int i = 2; i <= n; i++) { if (counter[i] == 0) { counter[i] = i + 1; primes.Add(i); } for (int j = 0; j < primes.Count; j++) { if (primes[j] * i > n) break; if (i % primes[j] == 0) { int k = i; int l = primes[j] * primes[j]; while (k % primes[j] == 0) { l *= primes[j]; k /= primes[j]; } counter[primes[j] * i] = counter[k] * (l - 1) / (primes[j] - 1); break; } else counter[primes[j] * i] = counter[i] * (primes[j] + 1); } } return counter; } } } /* 测试结果: 0.484375 0.484375 0.46875 单位second。 */

本章完。

3.3续、求给定区间内的第K小(大)元素
第九章、闲话链表追赶问题
第十章、如何给10^7个数据量的磁盘文件排序

面试题征集令

  1. 十三个经典算法研究系列+附、红黑树系列(国内有史以来最为经典的红黑树教程),共计20+6=26篇文章,带目录+标签的PDF文档,耗时近一个星期,足足346页(够一本书的分量了),已在花明月暗的帮助下,正式制作完成。
  2. 想要的,发一道你自认为较好的面试题(c,c++,数据结构,算法,智力题,数字逻辑或运算题)至我的邮箱:zhoulei0907@yahoo.cn,即可。我收到后,三天之内传送此PDF文件。July、20110.5.24.此声明永久有效。

版权所有,本人对本blog内所有任何内容享有版权及著作权。实要转载,请以链接形式注明出处。

程序员编程艺术:第六章、求解500万以内的亲和数相关推荐

  1. 程序员编程艺术 第六章 求解500万以内的亲和数

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 第六章. ...

  2. 程序员编程艺术第十一章:最长公共子序列(LCS)问题

    程序员编程艺术第十一章:最长公共子序列(LCS)问题 0.前言 程序员编程艺术系列重新开始创作了(前十章,请参考程序员编程艺术第一~十章集锦与总结).回顾之前的前十章,有些代码是值得商榷的,因当时的代 ...

  3. 程序员编程艺术:第二章、字符串是否包含问题

    程序员编程艺术:第二章.字符串是否包含及相关问题扩展 作者:July,yansha. 时间:二零一一年四月二十三日. 致谢:老梦,nossiac,Hession,Oliver,luuillu,雨翔,啊 ...

  4. 程序员编程艺术:第九章、闲话链表追赶问题

    程序员编程艺术:第九章.闲话链表追赶问题 作者:July.狂想曲创作组. 出处:http://blog.csdn.net/v_JULY_v . 前奏     有这样一个问题:在一条左右水平放置的直线轨 ...

  5. 程序员编程艺术第二十七章:不改变正负数相对顺序重新排列数组(无解?)

    第二十七章:不改变正负数之间相对顺序重新排列数组.时间O(N),空间O(1) 前言 本文开始之前,顺道说个事:CSDN最近开始评选10大博客专栏,投票地址为:http://event.blog.csd ...

  6. 求解500万以内的亲和数

    原链接:http://blog.csdn.net/v_JULY_v/article/details/6441279 第一节.亲和数问题 题目描述: 求500万以内的所有亲和数 如果两个数a和b,a的所 ...

  7. 程序员编程艺术第一~二十七章集锦与总结(教你如何编程),及PDF免分下载

    程序员编程艺术第一~二十七章集锦与总结(教你如何编程) 作者:July.编程艺术室 出处:结构之法算法之道blog 一.引言 自从去年7月份朋友花明月黯帮我制作了十三个经典算法研究的PDF文档(最新的 ...

  8. 程序员编程艺术第一 二十二章集锦与总结(教你如何编程)

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 程序员编 ...

  9. 程序员编程艺术第一~二十二章集锦与总结(教你如何编程)

    程序员编程艺术第一~二十二章集锦与总结(教你如何编程) 作者:July.编程艺术室. 出处:http://blog.csdn.net/v_JULY_v . 题记 好久没更新博客了,虽只有一个月,但对我 ...

最新文章

  1. [转]xml解析工具的效率比较QDomDocument、TinyXml-2、RapidXml、PugiXml
  2. 7 php程序的调试方法_php程序调试方法总结
  3. linux安装mysql(shell一键安装)
  4. mongodb mysql数据类型_mongodb中数据类型的坑
  5. 顺序表之元素位置互换(改进版)
  6. 线性模型第3讲:Lasso方法
  7. 自动化接口用例从 1 到 1000 过程中的实践和思考
  8. 快速取得三位数的个位,十位,百位
  9. android 蓝牙 录音,Android 实现蓝牙录音
  10. 11 月全国程序员平均工资出炉
  11. 图像美学质量评价技术综述
  12. jQuery 键盘快捷键
  13. [附源码]计算机毕业设计springboot基于Web的软考题库平台
  14. 豪斯曼检验matlab,豪斯曼检验、空间面板模型选择等问题
  15. 16.5 项目:向会员发送会费提醒电子邮件
  16. 抓取空气质量指数AQI_PM2.5历史数据,可视化展示
  17. 短链接生成器,adf.ly、shorte.st、ouo.io、adfoc.us哪个更好,有哪些区别
  18. containerd环境下build镜像
  19. forEach()与each()方法的区别
  20. 2017年山东省第八届ACM程序设计竞赛总结

热门文章

  1. 自媒体人必备,5个免费找短视频素材的网站,赶紧收藏
  2. 目前应用最多的四种制图软件!
  3. 2010浙大报录比及分数
  4. 2023智能家电、智能家居解决方案与技术论坛——CAEE
  5. 6.3 Faddeev-Leverrier算法求特征多项式
  6. 一步一步做ListView滚动固定头部并且头部会变化哦
  7. C#调用斑马打印机打印条码标签(支持COM、LPT、USB、TCP连接方式和ZPL、EPL、CPCL指令)
  8. “爱运动管理系统”培训结业考试卷
  9. mysql qc_MySQL数据库编程中QC的使用方法
  10. 一文看懂“知识蒸馏”技术