亲和数问题

来源:互联网 发布:中小学网络课程 编辑:程序博客网 时间:2024/05/08 21:13

看了July的《程序员编程艺术》的第六章亲和数问题,看了好长时间才搞清楚,智商拙计,记录下来,原文见:https://github.com/julycoding/The-Art-Of-Programming-by-July/blob/master/ebook/zh/06.0.md

这个问题原文中给出了两种解法,一种是伴随数组线性遍历的方法,另一种是近似与线性的利用素数筛选法,关于素数筛选法首先看看素数筛选法,主要参考了这篇文章http://blog.csdn.net/dinosoft/article/details/5829550

素数筛选法

一般的素数筛选法就是依次遍历数列,在遍历每遇到一个素数就将以这个素数为因子的数剔除,最后数列中所有的合数都被剔除了,所筛选出来的就是素数了,代码如下:

/** * 筛选法求整数n以内的素数,先假定n以内的数都是素数,然后从2开始逐个筛选,每遇到一个素数i,就将n以内i的倍数排除掉,最后筛选出来的就是素数 * @param n * @return */public static boolean[] primeSelector(int n) {boolean[] primes = new boolean[n+1];//布尔数组中的每个元素对应的数是否是素数primes[0] = false;primes[1] = false;//先假设都是素数for(int i = 2; i <= n; i++) {primes[i] = true;}//进行筛选for(int i = 2; i <= n; i++) {if(primes[i]) {int j = 2;while(j * i <= n) {//剔除素数i的倍数primes[i * j++] = false;}}}return primes;}
上面的方法存在重复剔除数据的问题,如合数30,在素数2,3,5的时都进行了依次剔除操作,所以可以针对这种情况进行优化,使用一个数组存储已经得到的素数,然后对于遍历的每个数都,将已经得到的素数乘以这个整数,对这个积值剔除,如遍历到整数4时,得到的素数数组为[2,3],那么就剔除整数4*2即8这个数,然后再剔除整数4*3,为了不重复剔除数,还需要对这个过程进行一定的控制,在遍历到整数6时也同样会剔除整数12,所以使用i % primeNums.get(j) == 0这个条件来结束内层循环。如何证明?引用上面的文章[一般筛法求素数+快速线性筛法求素数]的证明过程:

首先,先明确一个条件,任何合数都能表示成一系列素数的积。 不管 i 是否是素数,都会执行到“关键处1”(下面的代码),①如果 i 都是是素数的话,那简单,一个大的素数 i 乘以不大于 i 的素数,这样筛除的数跟之前的是不会重复的。筛出的数都是 N=p1*p2的形式, p1,p2之间不相等 ②如果 i 是合数,此时 i 可以表示成递增素数相乘 i=p1*p2*...*pn, pi都是素数(2<=i<=n),  pi<=pj  ( i<=j )p1是最小的系数。根据“关键处2”的定义,当p1==prime[j] 的时候,筛除就终止了,也就是说,只能筛出不大于p1的质数*i。 我们可以直观地举个例子。i=2*3*5此时能筛除 2*i ,不能筛除 3*i如果能筛除3*i 的话,当 i' 等于 i'=3*3*5 时,筛除2*i' 就和前面重复了。
/** * 求整数n以内的素数 * @param n * @return */public static boolean[] primeSelectorImp(int n) {boolean[] primes = new boolean[n+1];List<Integer> primeNums = new ArrayList<>();primes[0] = false;primes[1] = false;for(int i = 2; i <= n; i++) {primes[i] = true;}for(int i = 2; i <= n; i++ ) {if(primes[i]) {primeNums.add(i);}int j = 0;//关键处1while(i * primeNums.get(j) <= n && j < primeNums.size()) {primes[i * primeNums.get(j)] = false;if(i % primeNums.get(j) == 0) {//关键处2break;}j++;}}return primes;}

利用素数筛选法求亲和数

每个数都可以表示成素数的乘积,对于正整数N,可以用素数表示成N﹦P1 a1·P2a2·P3a3……PKak,其中P1,P2,P3……Pn-1,Pn都是素数,那么:

N的约数的个数为:(a1+1)×(a2+1)×……×(ak+1);

N的约数和为:(1+P1+ P12+…+ P1a1)×(1+P2+ P22+…+ P2a2)×……×(1+PK+PK2+……+PKak) ;

而根据公式Pn-1=(P-1)*(Pn-1 + Pn-2 + …… + 1)可得到N的约数和为:

利用这个公式可以进行递推,然后类似素数筛选法,可以求解亲和数问题。

代码如下:

import java.util.ArrayList;import java.util.List;public class AmicableNumber {public static void main(String[] args) {int[] counter = amicableNumber(5000000);//如果两个数a和b,a的所有真因数之和等于b,b的所有真因数之和等于a,则称a,b是一对亲和数,而amicableNumber函数计算的所有因子之和,所以应该减去这个数本身for(int i = 0; i < counter.length; i++) {int num = counter[i] - i;if(num < counter.length && num > i && counter[i] == counter[num]) {System.out.println(num + ":" + i);}}}/** * 求出每个数的因子之和 * @return */public static int[] amicableNumber(int n) {//存储每个数的因子之和int[] counter = new int[n+1];//保存计算过程中遇到的素数List<Integer> primeNums = new ArrayList<>();counter[0] = 0;counter[1] = 1;for(int i = 2; i <= n; i++ ) {//如果counter[i] == 0,则i为素数if(counter[i] == 0) {primeNums.add(i);counter[i] = i + 1;}int j = 0; while(i * primeNums.get(j) <= n && j < primeNums.size()) {//如果第j个素数s是i的因子,那么假设s的n次方乘以k等于i,即i=k*s^n,所以i*s=k*s^(n+1),则根据公式有//counger[i*s]=counter[k]*(s^(n+2)-1)/(s-1)if(i % primeNums.get(j) == 0) {int k = i;int l = primeNums.get(j);l = l * l;while(k % primeNums.get(j) == 0) {l = l * primeNums.get(j);k = k / primeNums.get(j);}counter[i * primeNums.get(j)] = counter[k] * (l - 1) / (primeNums.get(j) - 1);break;} else {//如果第j个素数不是i的因子,如素数2不是整数45的因子,则counter[45*2]=counter[45]*(2^2-1)/(2-1)counter[i * primeNums.get(j)] = counter[i] * (primeNums.get(j) + 1);}j++;}}return counter;}}

伴随数组解法

伴随数组解法的原理就是在计算过程中维护一个数组sum,先将数组中所有元素的值都置1(1是所有整数的因子),然后从2开始遍历,遇到一个数a就将这个a的整数倍的数对应的sum数组中的元素加上a,如在遍历到2时就将可以被2整除的所有数(即所有大于2的偶数)所对应的sum元素加2。

实现代码如下:

public class AmicableNumberArr {public static final int MAX_NUM = 5000000;/** * @param args */public static void main(String[] args) {int sum[] = new int[MAX_NUM+10];amicable_pair(sum, MAX_NUM);}static void amicable_pair(int sum[], int n) {int i;int j;for (i = 1; i <= n; i++) {sum[i] = 1;}for (i = 2; i + i <= n; i++) {j = i + i;while (j <= n) {sum[j] += i;j += i;}}for (i = 1; i <= n; i++) {if (sum[i] > i && sum[i] <= n && sum[sum[i]] == i) {System.out.printf("%d:%d\n", sum[i], i);}}}}

--EOF

reference

一般筛法求素数+快速线性筛法求素数

程序员编程艺术第六章

http://bbs.csdn.net/topics/360246918

0 0
原创粉丝点击