求N!

刚开始看到这个题目觉得很简单,一个循环搞定。但是仔细考虑一下,如果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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值