求N!

来源:互联网 发布:mysql存储过程写法 编辑:程序博客网 时间:2024/04/30 04:29

刚开始看到这个题目觉得很简单,一个循环搞定。但是仔细考虑一下,如果N是一个特别大的数呢,它可能是100,1000.....这个时候基本类型数据已经装不下它了,这是时候就需要写一种数据结构来满足大数据的计算,如下代码:

 /** * 定义大数据的乘法 * @param str 乘数(大数据) * @param n 被乘数 * @return */public static String multify(String str,int n){char[] arr=str.toCharArray();StringBuilder sb=new StringBuilder();//特别注意这一行代码int temp=0;//保存进位值int k=0;for(int i=0;i<arr.length;i++){k=(arr[i]-48)*n+temp;//分别把每一位上的字符减去48变成本来的值,然后再作计算temp=k/10;//计算进位并保存sb.append(k%10);//保存余数}/**这里进位可能很大,位数可能不是只有一位,所以这 * 里需要最后的进位一位一位的按顺序加入到结果字符串中*/if(temp>0){char[] temps=(temp+"").toCharArray();for(int i=temps.length-1;i>=0;i--){sb.append(temps[i]);//把进位分解并加入结果}}return sb.toString();//返回结果字符串}
/** *递归调用计算n!  * @param n * @return */public static String factorial(int n){if(n==1){return "1";}/**递归调用,第一个参数为(n-1)!,第二个为n,即返回的是(n-1)!*n=n! */return multify(factorial(n-1), n);}
对于以上代码首先是一个大整数的乘法,用字符串来作为介质,按照最基本的乘法思想来计算的。但是有一点区别,平时算的高位到低位都是从左往右的顺序,但是字符串我为了方便,直接append后面了,所以这里从左往右是低位到高位的顺序,但是没影响。在这个函数的功能是:给一个大整数(这个整数数是一字符串的形式传进来),和一个int类型的整数n,返回他们的乘积。字符串传进来首先被分解成一个字符数组,然后进行循环,让每一位与n相乘,得到一个数,这个数可能有很多位,但是没关系,把它对10取整,结果作为进位;再把它对10取模,结果肯定只有一位,那么就把他放到该为,对应的代码写出啦就是同过append往后加一位。循环结束,最后可能剩下一个比较大的余数,按照顺序直接往后append就好了。这样一个大整数乘法就完成了。
接下来是一个计算阶乘的递归方法。方法写的比较简单,注释写的也很清楚,这里就不赘述了。结果测试,可以运行,但是问题又来了,这个算法只能算到6000多的阶乘,再算更大的数,就会报内存溢出的错误。经过分析发现了原因之所在。在我标注的注释里“特别注意这一行代码”,StringBuilder  sb=new StringBulder();很显然是这一句代码导致了内存溢出。一般情况下有JVM的垃圾回收器来帮我们管理内存,所以很难导致内存溢出,但是里涉及到一个递归方法调用,我们都知道递归方法调用,就是不停的对方法压栈操作,最后获得一个初始值,这时候才会从栈中把这些数据取出来进行计算。所以在以上递归代码中,不停的再调用multiply()方法,所以在这个方法被不停的压入栈中,这也导致了方法体中的sb对象也就是StringBuilder  sb=new StringBulder();它new出来的对象始终都会被引用到,也就是说它在GC眼里,始终都是一个可到达对象,那么GC就不能对它清理,所以它会慢慢的占慢堆内存,最终导致内存溢出。要解决这个办法就是不能用递归。如以下代码:
/** * 计算阶乘 * @param n * @return */public static String factorial(int n){int k=1;String s;//定义临时字符串变量String str="1";//定义开始变量字符串while(k<=n){s=new String(str);//用上一轮计算的str值对s进行初始化str=multify(s, k);//计算新的str值。k++;}return str;}
对于引用类型,想要使用循环来进行自身乘以一个数然后等于自身还是要费点劲的,不能直接像基本类型数据那样,直接m=m*n;需要借助一个临时对象来作为中间值。这个时候虽然在循环体中new了对象(一般不建议在循环体中new对象,因为它会加大GC的工作量,这是迫不得已),但是这个对象new出来,在本次循环结束,它也就成了不可达对象,GC就会对它进行回收。如果愿意可以在后面加上str=null;显示的指定它是一个不可达对象。
通过这中改进,理论上讲是可以算无穷大的整数,就是时间不允许,经测试算一个50000的需要一分多种。当然速度跟cpu也有关系。

                                             
0 0