求开根号之后的值,本身实现不难,但是运行速度有待提高。
题目:
Implement int sqrt(int x)
.
Compute and return the square root of x, where x is guaranteed to be a non-negative integer.
Since the return type is an integer, the decimal digits are truncated and only the integer part of the result is returned.
Example 1:
Input: 4
Output: 2
Example 2:
Input: 8
Output: 2
Explanation: The square root of 8 is 2.82842..., and since
the decimal part is truncated, 2 is returned.
最开始直接用了sqrt函数。。。
int mySqrt(int x) {
int t;
t=(int)sqrt(x);
return t;
}
然后发现其实人家是让你自己实现这个函数。。
于是用1~x/2的for循环遍历,写了自己的一版代码,实现起来很慢。。
int mySqrt(int x) {
int i;
if(x<0){return -1;}
if(x==0){return 0;}
if(x==1){return 1;}
for(i=1;i<=x/2;i++){
if(pow(i,2)==x||(pow(i,2)<x&&pow(i+1,2)>x)){break;}
}
return i;
}
因为对于算法不是特别了解,所以直接百度了其他人的代码答案,大概有两个思路:
1.二分法查找
对于一个非负数n,它的平方根取整 ⌊√x⌋ ≤ (⌊x/2⌋+1),如下图所示,有x=1、2、4共3个整数交点,x>4以后⌊√x⌋ 恒小于 (⌊x/2⌋+1).
由于int sqrt(int x)
接受的参数与返回值均为int型,故⌊√x⌋ ≤ (⌊x/2⌋+1)即等价于强数据类型语言(比如:C++、C、Java等)中的√x(目标值)≤ x/2+1 (x为自然数,非负整数). 于是在[0, x/2+1]这个范围内进行二分搜索,可以求出n的int型平方根,mid=(low+up)/2,其初值为x/2,结果应在[low, up]的mid或up处取得。如果用弱数据类型的语言(比如:PHP、Python、JavaScript等)实现此方法,需先自行ceiling或ceil进行下取整!
但此法不适用于double,因为此法利用了int型的特点。
2.牛顿迭代法
Newton's Method(牛顿切线法)是由艾萨克·牛顿在《流数法》(Method of Fluxions,1671年完成,在牛顿死后的1736年公开发表)中最早提出的。约瑟夫·拉弗森也曾于1690年在Analysis Aequationum中提出此方法。它是牛顿在17世纪提出的一种在实数域和复数域上近似求解方程的方法。
因为对于自己而言二分法比较好理解,所以选择二分法作为参考代码,具体如下:
1.为什么初始范围是[1, (x/2) + 1]?
要实现一个sqrt函数,可以使用二分法,首先确定一个范围[begin, end],这个范围的中间数mid,看mid的平方是否等于x,如果相等,则返回mid,如果不等则缩小[begin,end]的范围,为原来的一半。这里的初始范围可以是[1, x],也可以是更精确一些的[1, (x/2) + 1]。(因 (x/2) + 1 的平方等于 x+1+(x^2/4),它一定大于x,所以,x的平方根一定在[1, (x/2) + 1]范围内)
2.如何缩小范围?
范围是[low,high],最开始为[1, (x/2) + 1]。
取low和high的中间值mid=low + (high - low) / 2,如果中间值mid的平方小于x,那就在后半段;如果大于x,那就在前半段。
3.如何确定是哪个值?
确定具体值是靠着缩小范围得到的最终mid值,这个值应该满足mid^2=x,或者mid^2<x且(mid+1)^2>x,两个或关系的条件合并起来应该是mid^2<=x<(mid+1)^2,返回这个mid值就是要求的开方值。
具体代码如下:
int mySqrt(int x) {
if(x == 0) return 0;
int low = 1, high = x / 2 + 1;
while(low <= high){//终止循环的条件是low和high两个值汇合,等于的时候仍然应该循环,否则覆盖不全面,所以只有low>high跳出循环,两个值的改变不再是++;而是用中间值的替换其中一个值(即不断的取前半段或者后半段)
int mid = low + (high - low) / 2;//mid取中间值,因为中间值随着high和low改变,所以一定要写在while循环内部
if(mid > x / mid) high = mid - 1;//如果mid^2>x则取前半段,此时high直接等于mid-1,而不是等于mid
else{
if(mid + 1 > x / (mid + 1)) return mid;//正常的return返回值
else low = mid + 1;//如果mid^2<=x则取后半段,令low = mid + 1而不是mid,在这之前,插入对mid+1值的判断,如果满足mid^2<=x<(mid+1)^2则直接输出该mid值
}
}
return -1;//异常
}
java版本与这个思路相同。