用递归程序求解随机产生一个正整数n(n>=100000),确定n是否是它所有因子之和

来源:互联网 发布:聊天室挂机赚钱软件 编辑:程序博客网 时间:2024/06/05 08:34

用递归程序求解随机产生一个正整数n(n>=100000),确定n是否是它所有因子之和(完数)

  1. 了解什么是因子:因子就是所有可以整除这个数的数,不包括这个数自身,例如:6的因子为1,2,3。

  2. 完数:即某正整数的所有因子之和等于该数,即为完数。例:6的因子为1,2,3且6=1+2+3;即称6为完数。

  3. 进行分析,要求利用递归实现,递归是一种直接或间接引用自身的定义方法。一个合法的递归定义包括两部分:基础情况和递归部分。基础情况以直接形式明确列举新事物的若干简单对象,递归部分给出有简单(或较简单)对象定义新对象的条件和方法。

  4. 本题要求编写一个算法实现该功能,算法讲究性能,即时间复杂度和空间复杂度,一个好的算法不仅能够准确运行,而且他的时间复杂度很低。算法具有五个特性:a,有0个或者多个输入;b,至少有1个或者多个输出;c,算法具有确定性;d,算法的指令有限;e,算法具有可行性。

  5. 对于该问题,首先先求一个整数的所有因子,在程序中即对该数依次进行求模运算,结果为0则代表是因子。
/** * 求一个正整数的所有因子之和等于该数 * @author zclong * */public class test {    public static void main(String[] args) {        //产生随机数        int value = (int) (Math.random()*1000000);        //计算因子之和        int sum = 0;        //定义一个list数组存储因子        List<Integer> list = new ArrayList();        //对随机数进行遍历        long startTime = System.currentTimeMillis();        for (int j = 1; j <= value-1; j++) {            if(value % j == 0) {                list.add(j);                sum = sum + j;            }        }        long endTime = System.currentTimeMillis();        if(sum == value) {            System.out.println("因子个数" + list.size() + ", 运行时间:" + (endTime-startTime) + "ms");            System.out.println(value + "是完数,其因子为" + list.toString());        }else {            System.out.println("因子个数" + list.size() + ", 运行时间:" + (endTime-startTime) + "ms");            System.out.println(value + "不是完数,其因子为" + list.toString());        }    }}

这里写图片描述

  1. 上面是普通的计算方法,从程序和结果可以看出,若是超过十万的整数,它将会计算十多万次,花费相当大的内存消耗和时间。

  2. 我们再用递归进行计算。

public class GetFactor {    //产生随机数    final int value = (int) (Math.random()*10000);    //计算因子之和    int sum = 0;    //定义一个list数组存储因子    List<Integer> list = new ArrayList();    //执行次数    int count = 0;    @Test    public void test() {        GetFactor factor = new GetFactor();        long startTime = System.currentTimeMillis();        factor.getFactor(1); // 调用执行方法,从1开始检验是否是因子        long endTime = System.currentTimeMillis();        System.out.println("运行时间:" + (endTime-startTime) + "ms");    }    public void getFactor(int n) {        count++;        if(n > value-1) {            if(sum == value) {                System.out.println("因子个数" + list.size() + ", 执行次数" + count);                System.out.println(value + "是完数,其因子为" + list.toString());            }else {                System.out.println("因子个数" + list.size() + ", 执行次数" + count);                System.out.println(value + "不是完数,其因子为" + list.toString());            }            return;        } else {            if(value % n == 0) {                list.add(n);                sum = sum + n;            }            getFactor(n+1);        }    }}

这里写图片描述

这里写图片描述

  1. 上面的递归运算可以看出当进行运算的算超过10000。便出现了内存溢出的现象。

  2. 递归的缺点:
    a.递归由于是函数调用自身,而函数调用是有时间和空间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数、返回地址以及临时变量,而往栈中压入数据和弹出数据都需要时间。->效率
    b.递归中很多计算都是重复的,由于其本质是把一个问题分解成两个或者多个小问题,多个小问题存在相互重叠的部分,则存在重复计算,如fibonacci斐波那契数列的递归实现。->效率
    c.调用栈可能会溢出,其实每一次函数调用会在内存栈中分配空间,而每个进程的栈的容量是有限的,当调用的层次太多时,就会超出栈的容量,从而导致栈溢出。->性能

  3. 为了解决递归出现的问题,我们可以通过代数变换,减少子问题的个数。或者通过预处理,以空间换时间的方式进行处理。

  4. 我们选择预处理, 先对要求的正整数进行开平方根处理。

  5. 我们利用对该正整数求平方根来大大减少递归的次数,因为假设存在一个正整数num,使得num=k*M;且k不在(1,sqrt(num))之间,且M为正整数,那么M必在(1,sqrt(num))之间,否则k*M> sqrt(num)* sqrt(num),与num=k*M矛盾。所以我们只要求(1,sqrt(num))之间的因子即可,对应的因子也就出来了。

  6. 拿到要求的正整数的平方根之后,对其进行判断,当进行运算的数大于平方根数即结束,计算所有因子之和,若等于该数,即为完数;否则不断进行递归运算;
public class Test1 {    List<Integer> list = new ArrayList();    int k = 0;    int key  =0;    //产生随机数    int value = (int) (Math.random()*100000000);    @Test    public void factors(){        list.add(1);        if(1 == value) return;        int count1 = 1;// 1 与 N 必是(不包括N)        final int sqrt_N = (int)Math.sqrt(value); // 进行求平方根        long startTime = System.currentTimeMillis();        recursive(sqrt_N, 2, 1);// 从2开始判断是否是因子,因为1是所有正整数的因子,因子之和的初始值sum=1        long endTime = System.currentTimeMillis();        System.out.println("所用时间为:" + (endTime - startTime) + "ms");    }    public void recursive(int sqrt_N, int m, int sum) {        k++;        if(m > sqrt_N) {            Collections.sort(list); // 对list数组进行排序            System.out.println("随机数为:" + value + ", 运行次数:" + k);            System.out.println("其因子为" + list.toString() + ",因子个数:" + list.size());            if(sum == value) {                System.out.println("其和为:" + sum + ", 是完数");            }else {                System.out.println("其和为:" + sum + ", 不是完数");            }        }else {            if(0 == value % m) { // 判断是否整除为因子                list.add(m);                 key = value / m; // 求出另一个因子                 list.add(key);                sum = sum + m + key; // 求因子之和                recursive(sqrt_N,m + 1, sum);  // 递归            }else {                recursive(sqrt_N,m + 1, sum); // 递归            }        }    }}

这里写图片描述
7. 上述做法将一个1000000的数化为1000的数,这样做不仅不会使内存溢出,使得子问题的个数减少,而且节省了时间,大大降低了时间复杂度。
8. 通过递归划分子问题进行子问题的求解,使程序变得简单。但是若递归不当则会引起内存溢出,此时可以进行预处理或者减小子问题的个数,以空间换时间来实现,来解决递归内存溢出的问题。

阅读全文
1 0
原创粉丝点击