看了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; //关键处1 while(i * primeNums.get(j) <= n && j < primeNums.size()) { primes[i * primeNums.get(j)] = false; if(i % primeNums.get(j) == 0) {//关键处2 break; } 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