亲和数问题

看了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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值