【算法系列】-开根号

一 序 

  本篇属于算法整理系列。
  不能否认,有的大公司重视基础,算法可能就是重要考察点。
  题目:实现函数: double sqrt ( int v, double t)
假设函数的返回结果为 r,  要求 r 要满足一定的误差条件, 用公式表达就是:    ,     其中 开根号V是真实的值 ,    t 为给定的一个误差, 例如0.1 。

二 循环法

  如果面试时候,面试官问你这个问题,加上一个限制:不能使用math.sqrt.
  其实脑子第一反应就是脑子有病,为啥不用,实际工作中也没这样的需求啊,我用计算器也能算。但是还是得冷静下来。
  想想面试官是为了考察啥。
 要是卡住或者说不会就太尴尬了。
  简单的思路就是让程序模拟大脑计算。比如我们习惯性的记住整数的,比如4的是开根号是2,9的开根号是3,很少有人去专门记忆2的是1.414.。。
所以 先用一个循环找到 r, 使得 r^2 是离给定 v 最近的平方数。用精度小的去作为step.
没循环一次,就累加一下步长。
其实是能算出来的,也能满足精度需求,就是不是最优解。因为毕竟暴力循环耗时长一些。
有个矛盾:要想精度高,步长就要小,步长小了,循环次数就多,所以有个均衡考虑。
推广思考下:如果有个业务场景,不断尝试推送消息给用户,用户不在线就收不到,消息的数量有很多。
所以你要每秒去推送,可能推送不过来。所以对于重试推送的,可以适当的调大时间间隔step.
30秒再试。30分钟再试,3小时再试。等等,几次都失败就废弃,认为不可达。

三 二分查找法:

  二分搜索或者折半查找很多人在大学是学过的,数据结构就涉及这块。
 当然注意一下边界条件,就不展开了,可以看下demo
  当然能做到这一步,基本上面试就算过了。

四 牛顿迭代法

 我是不懂的,数学学得一塌糊涂。从百科上给自己科普一下:https://baike.baidu.com/item/%E7%89%9B%E9%A1%BF%E8%BF%AD%E4%BB%A3%E6%B3%95


设r是
   
的根,选取
   
作为r的初始近似值,过点
   
做曲线
   
的切线L,L的方程为
   
,求出L与x轴交点的横坐标
   
,称x 1 为r的一次近似值。过点
   
做曲线
   
的切线,并求该切线与x轴交点的横坐标
   
,称
   
为r的二次近似值。重复以上过程,得r的近似值序列,其中,
   
称为r的
   
次近似值,上式称为 牛顿迭代公式

直接对应的开根号公式:

demo:
package com.daojia.math;

public class SquereTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		long t1 =System.currentTimeMillis();     
		double srt = Math.sqrt(100000000.0);
		System.out.println("jdkuse:"+(System.currentTimeMillis()-t1)+":"+srt);
		double jingdu = 0.001;
		long t2 =System.currentTimeMillis();     
		double srt1 =  sq1(100000000.0,0.001);
		System.out.println("循环use:"+(System.currentTimeMillis()-t2)+":"+srt1);
		System.out.println(Math.abs(srt1-srt)<jingdu);
		long t3 =System.currentTimeMillis();
		double srt2 =  sq2(100000000.0,0.001);
		System.out.println("二分法use:"+(System.currentTimeMillis()-t3)+":"+srt2);
		System.out.println(Math.abs(srt2-srt)<jingdu);
		long t4 =System.currentTimeMillis();
		double srt3 = sq3(100000000.0,0.001);
		System.out.println("牛顿法use:"+(System.currentTimeMillis()-t4)+":"+srt3);
		System.out.println(Math.abs(srt3-srt)<jingdu);
	}
    /**
     * 暴力循环法
     * @param num
     * @param jingdu
     * @return
     */
	public static double sq1(double num,double jingdu){		
		if(num<0)
		{
			return -1;
		}
		double step =jingdu/5;
		System.out.println(step);
		double res =0.000;
	
		while(res*res<(num-jingdu))
		{
			res =res+step;
		}
		return res;		
	}
	/*
	 * 二分法
	 * 
	 */
	public static double sq2(double num,double jingdu){		
		if(num<0)
		{
			return -1;
		}
		
		double last =0.0000;
		double low=0;
		double mid;
		double up=(num>=1?num:1); 
		  mid=(low+up)/2; 
		  do{
			  if(mid*mid>num)
			  {
				  up = mid;
			  }else{
				  low = mid;
			  }
			  last=mid;
			  mid=(up+low)/2;        
		  }while(Math.abs(mid-last)>jingdu);
		  
		
		return mid;		
	}
	
	/*
	 * 牛顿迭代法
	 * 
	 */
	public static double sq3(double num,double jingdu){		
		if(num<0)
		{
			return -1;
		}
		
		double x=num,y=0.0;
	    while(Math.abs(x-y)>0.00001){
	        y=x;
	        x=0.5*(x+num/x);
	    }
	    return x;	
	}
}
jdkuse:0:10000.0
2.0E-4
循环use:147:10000.000011057207
true
二分法use:0:9999.999747378752
true
牛顿法use:0:10000.0
true
我们可以看出,现在计算机还是很快的,对于1亿,精度要求0.001的情况下。用了147毫秒。
二分法时间个jdk的math一样。但是结果不如牛顿迭代法的高。
因为jdk的math是直接调用了public static native double sqrt(double a);
这个native的方法时比Java更快的C语言编写的。我们不可见源码。但是从效果对比来看,牛顿迭代法显然是最优的选择。
************************************************************
还是懂数学的好,
底层还是博大精深。有太多的东西需要学习。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值