二分查找
定义
二分查找,又称折半查找,如果对当前区间查找成功,则直接返回该元素及其数组下标;如果对当前区间查找失败,则在当前空间的中间元素与待查元素进行比较后,将查找区间转变为可能包含该元素的数组区间继续查找,该区间为原区间长度的一半,直至查找成功或者查找失败为止。
时间复杂度
二分查找的时间复杂度为 O(log n)
所需数组类型
所需数组类型为有序数组
算法思想
假设原数组为{4,6,7,8,10,23,24,36},数组为整型数组。我们假定 left 和 right 都是指向区间边界的变量,其值为数组下标的值,其中 left 指向所查找区间的最左边,right 指向所查找区间的最右边。
我们假设对 36 进行查找:
-
首先,我们置:left=0,right=7(数组长度-1),由于 mid 为所查区间的中间值,故 mid=(left+right)/2(mid值为向下取整)。
-
此时,我们对a[mid]值和所查找值进行比较,因为 8<36,并且由于所查数组为增序数组,所以要查找的值应在 [mid,right]之间。此时,我们置 left=mid+1,由于 mid 为所查区间的中间值,故 mid=(left+right)/2(mid值为向下取整)。
-
此时,我们对a[mid]值和所查找值进行比较,因为 23<36,并且由于所查数组为增序数组,所以要查找的值应在 [mid,right]之间。此时,我们置 left=mid+1,由于 mid 为所查区间的中间值,故 mid=(left+right)/2(mid值为向下取整)。
-
此时,我们对a[mid]值和所查找值进行比较,因为 24<36,并且由于所查数组为增序数组,所以要查找的值应在 [mid,right]之间。此时,我们置 left=mid+1,由于 mid 为所查区间的中间值,故 mid=(left+right)/2(mid值为向下取整)。
-
此时,我们对a[mid]值和所查找值进行比较,因为 36=36,所以查找成功,返回查找成功值(a[mid])和所查找值在数组中的位置(mid)。
我们假设对 4 进行查找:
-
首先,我们置:left=0,right=7(数组长度-1),由于 mid 为所查区间的中间值,故 mid=(left+right)/2(mid值为向下取整)。
-
此时,我们对a[mid]值和所查找值进行比较,因为 8>4,并且由于所查数组为增序数组,所以要查找的值应在 [left,mid]之间。此时,我们置 right=mid-1,由于 mid 为所查区间的中间值,故 mid=(left+right)/2(mid值为向下取整)。
-
此时,我们对a[mid]值和所查找值进行比较,因为 6>4,并且由于所查数组为增序数组,所以要查找的值应在 [left,mid]之间。此时,我们置 right=mid-1,由于 mid 为所查区间的中间值,故 mid=(left+right)/2(mid值为向下取整)。
-
此时,我们对a[mid]值和所查找值进行比较,因为 4=4,所以查找成功,返回查找成功值(a[mid])和所查找值在数组中的位置(mid)。
我们假设对 23 进行查找:
- 首先,我们置:left=0,right=7(数组长度-1),由于 mid 为所查区间的中间值,故 mid=(left+right)/2(mid值为向下取整)。
- 此时,我们对a[mid]值和所查找值进行比较,因为 8<23,并且由于所查数组为增序数组,所以要查找的值应在 [mid,right]之间。此时,我们置 left=mid+1,由于 mid 为所查区间的中间值,故 mid=(left+right)/2(mid值为向下取整)。
- 此时,我们对a[mid]值和所查找值进行比较,因为 23=23,所以查找成功,返回查找成功值(a[mid])和所查找值在数组中的位置(mid)。
我们假设对 1 进行查找:
-
首先,我们置:left=0,right=7(数组长度-1),由于 mid 为所查区间的中间值,故 mid=(left+right)/2(mid值为向下取整)。
-
此时,我们对a[mid]值和所查找值进行比较,因为 8>1,并且由于所查数组为增序数组,所以要查找的值应在 [left,mid]之间。此时,我们置 right=mid-1,由于 mid 为所查区间的中间值,故 mid=(left+right)/2(mid值为向下取整)。
-
此时,我们对a[mid]值和所查找值进行比较,因为 6>1,并且由于所查数组为增序数组,所以要查找的值应在 [left,mid]之间。此时,我们置 right=mid-1,由于 mid 为所查区间的中间值,故 mid=(left+right)/2(mid值为向下取整)。
-
此时,我们对a[mid]值和所查找值进行比较,因为 4>1,并且由于所查数组为增序数组,所以要查找的值应在 [left,mid]之间。此时,我们置 right=mid-1,由于 mid 为所查区间的中间值,故 mid=(left+right)/2(mid值为向下取整)。
-
此时,我们对a[mid]值和所查找值进行比较,因为 right<left,所以查找失败,返回-1,提示所查数不在当前数组中。
代码实现
#include<stdio.h>
//接收数组,数组长度,所查值
int Binarylookup(int a[], int length, int targe)
{
//定义left,right,mid
//left指向所查区间的最左边
//right指向所查区间的最右边
int left = 0, right = length - 1, mid;
//当right<left时,说明查找失败,结束查找
while (right >= left) {
//mid指向区间中间值
mid = (left + right) / 2;
//查找成功
if (a[mid] == targe) {
printf("%d在数组中存在,数组下标为%d\n", targe, mid);
return 0;
}
//所查值在mid的右边
else if (a[mid] < targe)
left = mid + 1;
//所查值在mid的左边
else
right = mid - 1;
}
printf("%d在数组中不存在\n", targe);
}
int main()
{
int a[] = { 4,6,7,8,10,23,24,36 };
//调用二分查找函数
Binarylookup(a, 9, 36);
}
优化区间数据溢出
数据溢出原因
我们知道 int 型数据在32位和64位机器中都是占4个字节,一个字节占8位,故 int 型应该占 32 位。在这32位中,最后一位(32位)是符号位,0表示正数,1表示负数,它不表示数据值,而剩下的31位(1~31位)存储数据。
接下来,我们看个数据计算:
我们知道:2147483647 + 1073741823 = 3221225470
但如果把这两个当做 int 型来进行计算的话,则是这样:2147483647 + 1073741823 = -1073741822
毫无疑问,说明计算结果超出了int型范围,发生了溢出,此时计算结果已经变得不可靠了。此时我们看下二分查找的代码:
mid = (left + right) / 2;
因为 left 和 right 两者都是 int 型,倘若输入的两者数据也是接近于2的31次方,那我们计算出来的 mid 结果也是溢出结果,那计算就无法进行下去。
解决溢出
我们知道 left <= right ,所以我们只需把加法变成一个减法运算,使结果不溢出即可。即:
mid = (left + right) / 2
= left / 2 + right / 2
= (left - left / 2) + right / 2
= left - left / 2 + right / 2
= left + (right -left) / 2
由于 >>2 相等于除二,并且右移在计算机中的运行效率要高,所以此处的除二,还可以换成 >>2
mid = = left + (right -left) >> 2
代码实现(优化运算溢出)
#include<stdio.h>
//接收数组,数组长度,所查值
int Binarylookup(int a[], int length, int targe)
{
//定义left,right,mid
//left指向所查区间的最左边
//right指向所查区间的最右边
int left = 0, right = length - 1, mid;
//当right<left时,说明查找失败,结束查找
while (right >= left) {
//mid指向区间中间值
mid = left + (right - left) >> 2;
//查找成功
if (a[mid] == targe) {
printf("%d在数组中存在,数组下标为%d\n", targe, mid);
return 0;
}
//所查值在mid的右边
else if (a[mid] < targe)
left = mid + 1;
//所查值在mid的左边
else
right = mid - 1;
}
printf("%d在数组中不存在\n", targe);
}
int main()
{
int a[] = { 4,6,7,8,10,23,24,36 };
//调用二分查找函数
Binarylookup(a, 9, 36);
}