二分查找有着查找速度快,平均性能好的优点,但要建立在要查找的数据必须时有序的。
今天我们今天来学习二分查找算法。
有天A和B同时去一家公司面试,只面试管在纸上写下一行数,如:
1,3,5,8,10,15,20。
问:你们谁能用程序在最短的时间找出15?
B:只需要从第一个元素开始往后依次比较,比较六次就可以找到了。
这时A回答:我只需要两次就可以找到。
面试官:A你能说说如何做吗?
A:你给的数字是有序的,所以没有必要一个一个比较,可以逐渐缩小要查找的范围来查找,我们可以先看中间的元素,如果是15,就直接找到了,如果15比中间的元素大,那就应该去中间元素的右边去找,反之,在左边找。
A说完后立即画了个图。
A:我们可以用数组arr去存储这些元素用Left和Right两个变量划定查找的区间,第一次比较15大于中间元素8,那么下一次查找就是在8的下一个位置到20的区间去查找。
这时面试官又问:如果是查找12呢?这个数字不存在我们又该如何完成?
A:如果查找12,同样的思路,第一次查找和15一样,第二次找12小于15,应该去15的左边,8的右边去查找。
当Left和Right指向同一个位置时也就是只剩下一个元素,但是还是不相等,就是找不到了,也就是数据中没有12这个元素。
二分查找代码的实现
面试官在肯定了A的思路后问:那你能写出这个程序的代码吗?
不一会A给出了代码
int binarySrarch(int arr[], int key,int arrlen)
{
int Letf = 0;
int Right = arrlen - 1;
int mid;
while (Letf <= Right)
{
mid = (Letf + Right) / 2;//查找区间的中间位置
if (arr[mid] > key)
{
Right = mid - 1;
}
else if(arr[mid]<key)
{
Letf = mid + 1;
}
else
{
return mid;
}
}
return -1;//没有找到
}
这是B插话问:这个判断循环条件能不能改为Left<Right呢?
A:不行,如果改为Left<Right,就有可能出现来数据中有待查数据却找不到的情况,比如查10、经过两次查找后,Left和Right都指向元素10但此时wile(low<high)不成立. 不进入循环避而找不到。
面式官在看完代码后问A:你觉得你的代码有什么问题吗?
A在看了代码后检查不出什么!尴尬的笑了笑说看不出!
面试官:你看mid=(Left+Right)/2,这行代码,Left和Right都是整形,当我们要 在一个很大的数据量中查找时,这时的Left和Right就会很大,lLeft+Right就容易产生溢出,结果就应为负数那么mid=(Left+Right)/2也就为负数了,程序就错了。
这时AB同问:那如何解决?
面试官:给你两个容量相同的杯子,杯子里都装了半杯以上的水,你怎样才能将两杯水变得体积相同?
你的代码(Left+Right)/2就相当于把一个杯子的水倒入另一个杯子中然后再均分,这样会使水流出。正确的做法是将两杯水中体积大减去体积小的等于水的体积差值,再将这个差值的一半倒入水少的杯子中就行了。也就是mid= Left+(Right - Left)/2。
int binarySrarch(int arr[], int key,int arrlen)
{
int Letf = 0;
int Right = arrlen - 1;
int mid;
while (Letf <= Right)
{
mid = Left+(Right -Left) / 2;//查找区间的中间位置
if (arr[mid] > key)
{
Right = mid - 1;
}
else if(arr[mid]<key)
{
Letf = mid + 1;
}
else
{
return mid;
}
}
return -1;//没有找到
}
二分查找的时间复杂度
在使用二分查找时,如果要查找的数据规模为n,二分查找一次后规模就变成了N/2,二分查找两次后的规模为n/2^2,二分查找m次后规模变成了n/2^m。
最坏情况,数据中没有该值
假设,当二分查找后剩下一个元素,那么此时规模为1,此时已经二分查找了m次,也就是n/2^m=1 m=lg(n)。再循环一次也不到,跳出循环,所以说最多循环m+1次,故二分查找的最坏是间复杂度为O(n)=lg(n)。