如何编程实现“求一个数的平方根”?要求精确到小数点后 6 位。
先把求小数问题转换成求整数问题, 精度为1, 再除以10^6, 转换成小数
- 先把要求的数 n*1012
- 二分法求出这个数 n*1012 的平方根整数x, 要求 x2<=n*1012 并且 (x+1)2> n*1012
- 将 x / 106 得到精确到小数点后6位的平方根
/**
* 求一个数的平方根
* @param value 数
* @param accuracy 小数位数
* @return
*/
public static double square(double value, int accuracy){
//把它转换成求整数问题, 精度为1, 再转换为小数
double bigValue = value * Math.pow(10, accuracy * 2);
int low = 0;
int high = (int) Math.ceil(bigValue);
//二分法查找i i^2<bigValue&&(i+1)^2>bigValue
while(low <= high){
int mid = ((high - low) >> 1) + low;
if (Math.pow(mid, 2) <= bigValue && Math.pow(mid + 1, 2) > bigValue) {
return mid / Math.pow(10, accuracy);
}else if(Math.pow(mid, 2) > bigValue){
high = mid - 1;
}else{
low = mid + 1;
}
}
return -1D;
}
如何在 1000 万个整数中快速查找某个整数?
这个问题并不难。我们的内存限制是 100MB,每个数据大小是 8 字节,最简单的办法就是将数据存储在数组中,内存占用差不多是 80MB,符合内存的限制。借助今天讲的内容,我们可以先对这 1000 万数据从小到大排序,然后再利用二分查找算法,就可以快速地查找想要的数据了。
看起来这个问题并不难,很轻松就能解决。实际上,它暗藏了“玄机”。如果你对数据结构和算法有一定了解,知道散列表、二叉树这些支持快速查找的动态数据结构。你可能会觉得,用散列表和二叉树也可以解决这个问题。实际上是不行的。
虽然大部分情况下,用二分查找可以解决的问题,用散列表、二叉树都可以解决。但是,我们后面会讲,不管是散列表还是二叉树,都会需要比较多的额外的内存空间。如果用散列表或者二叉树来存储这 1000 万的数据,用 100MB 的内存肯定是存不下的。而二分查找底层依赖的是数组,除了数据本身之外,不需要额外存储其他信息,是最省内存空间的存储方式,所以刚好能在限定的内存大小下解决这个问题。
如果数据使用链表存储,二分查找的时间复杂就会变得很高,那查找的时间复杂度究竟是多少呢?
使用数组的时候, 因为数组随机访问的特性, 访问中间节点的时间复杂度是O(1), 二分查找的时间复杂度是O(1) + O(1) + … + O(1), 而需要访问中间节点的次数是k次, k = log2n, 所以用数组二分查找时间复杂度O(logn)。
类比到使用链表, 链表长度是n时, 访问中间节点的时间复杂度是O(n/2)=O(n), 相应的二分查找时间复杂度是 O(n/2) + O(n/4) + … + O(n/2k), k=log2n, 等比数列求和, 二分查找的时间复杂度是O(n)
我一开始是这么推断的, 但这其实是错的。你有没有发现, 之所以是等比数列是要求每次查找的目标区间都落于左半部分, 然而实际上目标有可能落于右半部分。
实际上基于链表的二分查找的时间复杂度是 O(n) + O(n) +…+ O(n), k = log2n, 最终时间复杂度是O(nlogn)