第六章、亲和数问题--求解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点思路,编写如下代码:

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

运行结果:

@上善若水:

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。同时,任何人对本亲和数问题有任何问题,也可以回复到上述帖子上。

  1. //求解亲和数问题
  2. //copyright@ litaoye
  3. //July、胡滨,updated,2011.05.26。
  4. using System;
  5. using System.Collections.Generic;
  6. namespace CSharpTest
  7. {
  8. class Program
  9. {
  10. public static void Main()
  11. {
  12. int max = 5000000;
  13. DateTime start = DateTime.Now;
  14. int[] counter = CreateCounter(max);
  15. for (int i = 0; i < counter.Length; i++)
  16. {
  17. int num = counter[i] - i;
  18. //if (num < counter.Length && num > i && counter[num] == counter[i])
  19. // Console.WriteLine("{0} {1}", i, num);
  20. }
  21. Console.WriteLine((DateTime.Now - start).TotalSeconds);
  22. Console.ReadKey();
  23. }
  24. static int[] CreateCounter(int n)
  25. {
  26. List<int> primes = new List<int>();
  27. int[] counter = new int[n + 1];
  28. counter[1] = 1;
  29. for (int i = 2; i <= n; i++)
  30. {
  31. if (counter[i] == 0)
  32. {
  33. counter[i] = i + 1;
  34. primes.Add(i);
  35. }
  36. for (int j = 0; j < primes.Count; j++)
  37. {
  38. if (primes[j] * i > n)
  39. break;
  40. if (i % primes[j] == 0)
  41. {
  42. int k = i;
  43. int l = primes[j] * primes[j];
  44. while (k % primes[j] == 0)
  45. {
  46. l *= primes[j];
  47. k /= primes[j];
  48. }
  49. counter[primes[j] * i] = counter[k] * (l - 1) / (primes[j] - 1);
  50. break;
  51. }
  52. else
  53. counter[primes[j] * i] = counter[i] * (primes[j] + 1);
  54. }
  55. }
  56. return counter;
  57. }
  58. }
  59. }
  60. /*
  61. 测试结果:
  62. 0.484375
  63. 0.484375
  64. 0.46875
  65. 单位second。
  66. */

本章完。

(11)求五百万内的亲和数相关推荐

  1. ACMNO.17C语言-筛法求素数 用筛法求之N内的素数。

    题目描述 用筛法求之N内的素数. 输入 N 输出 0-N的素数 样例输入 100 样例输出 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 7 ...

  2. C#筛法求出范围内的所有质数

    科普篇:筛法是一种简单检定素数的算法.据说是古希腊的埃拉托斯特尼(Eratosthenes,约公元前274-194年)发明的,又称埃拉托斯特尼筛法(sieve of Eratosthenes). 说实 ...

  3. 6:求指定范围内的素数

    6:求指定范围内的素数 素数:又称质数,是指除了1和它自身外没有其它因子的正整数(1不是素数). 输入2个正整数m和n(m≤n),输出m~n间的所有素数,并求出个数. 提示:定义一个判断素数的函数,通 ...

  4. 用筛法求之N内的素数

    用筛法求之N内的素数. 时间限制: 1 Sec  内存限制: 64 MB 提交: 127  解决: 105 [提交][状态][讨论版][Edit] [TestData] 题目描述 用筛法求之N内的素数 ...

  5. 蓝桥杯每日一练——用筛法求之N内的素数 python

    题目描述 用筛法求之N内的素数. 输入 N 输出 0-N的素数 样例输入 100 样例输出 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 7 ...

  6. 题目 1084: 用筛法求之N内的素数(数论)

    文章目录 Question Ideas Code Question 题目描述 用筛法求之N内的素数. 输入 N 输出 0-N的素数 样例输入 100 样例输出 2 3 5 7 11 13 17 19 ...

  7. 题目 1084: 用筛法求之N内的素数

    时间限制: 1Sec 内存限制: 64MB 提交: 23410 解决: 13929 题目描述 用筛法求之N内的素数. 输入 N 输出 0-N的素数 样例输入复制 100 样例输出复制 2 3 5 7 ...

  8. 零起点学算法11——求梯形面积

    零起点学算法11--求梯形面积 Time Limit: 1 Sec  Memory Limit: 64 MB   64bit IO Format: %lld Description 水题 Input ...

  9. 五分钟内搭建的混沌电路

    简 介: 针对一种简单的混沌电路进行测试,没有能够复现电路的工作状态.具体原因尚未清楚. 关键词: 混沌电路,相移单管电路 #mermaid-svg-QPl6WYe8NnE6m9yt .label{f ...

最新文章

  1. Lidar-RCNN:基于稀疏点云的3D目标检测网络(CVPR2021)
  2. Spring boot的静态资源映射
  3. 英特尔收购人工智能公司 Nervana,开发深度学习技术
  4. 第三讲:Asp.Net+Autofac+EF/ADO.NET Winform OA(3)-启用DevExpress皮肤功能
  5. c#子线程和主线程创建窗体时顶层显示的区别
  6. centos利用tar包安装phpmyadmin
  7. 《BI项目笔记》数据源视图设置
  8. 从PRISM开始学WPF(九)交互Interaction?
  9. Irrlicht例001--Hello World
  10. channel c3 disabled, job failed on it will be run on another channel
  11. Battery Charging Specification 1.2 中文详解 来源:www.chengxuyuans.com
  12. 32位oracle10,『三思笔记』-- Solaris10下安装32位Oracle10g -- Solaris 10下安装ORACLE10G
  13. 今天,我想和你聊聊读研这件事
  14. 学生机房虚拟化(十二)搭建Clonezilla SE
  15. UOJ449. 【集训队作业2018】喂鸽子 [概率期望,min-max容斥,生成函数]
  16. 网站2008服务器32位好还是64位好,win server 2008 32位与64位区别
  17. windows的hosts文件位置
  18. mysql 存储过程 if !=_mysql 存储过程 if !=
  19. 【Java锁体系】ReadWriteLock读写锁是什么?什么是读写锁
  20. 【算法】Regular Expression Matching 正则匹配

热门文章

  1. 智能触摸导视系统-商场导购一体机-智慧商业综合体解决方案
  2. Zabbix web 监控
  3. SuperMap iMobile for Android定位实现
  4. SQLite源代码分析----------分析器③
  5. 建设部:住房公积金济富论不符合实际
  6. 翻新房子拆除工程是关键,因此拆除时一定要注意这5点!
  7. vb mysql加载控件_VB如何连接ACCESS数据库详解
  8. webService AxisFault
  9. 毛哥的快乐生活(10) 深藏不漏的美工妹子
  10. 毛哥的快乐生活(17) Java语言当年我学的很溜的