这几天学习了一个新的算法—二分查找。进入主题,首先来看个问题:
对于下面这串有序的数组,我们怎么快速找到自己想要的数字的位置呢?
当然,最简单直接的方法是设计一个循环,一个一个地去将里面地数据区取出来,再与目标值进行比对,直到找到我们需要的数字。但这种方法有一个很大的问题:就是当数组很大的时候,我们要找的数字在数组的最后面,那么我们将会经历很多次循环才能找到我们的目标,这会造成工作效率的低下。
所以,为了提高效率,引入一种新的查找算法—二分查找。
首先我们还是先来看看二分的思路:首先拿到一个有序的数表,我们首先访问它的中间元素,将中间元素与我们的目标值x进行比对,不符合条件的一半数组删除,直至数表只有最后一个元素,那么最后这个元素就是我们要找的元素。这里我们可以设计两种不同的比对方法,:
1:当前数表中间值小于等于x
- 即 当目标值大于或等于中间元素,则将数表的下边界修改为中间元素,上边界不变,继续相同操作;
- 当目标值小于中间元素,则将数表的上边界修改为中间元素减一,下边界不变,继续相同操作;
2:当前数表中间值大于等于x
- 即当目标值小于或等于中间元素,则将数表的上边界修改为中间元素,下边界不变,继续相同操作;
- 当目标值大于中间元素,则将数表的下边界修改为中间元素加一,上边界不变,继续相同操作;
通过以上步骤,一步一步将要查找的数组范围缩小,直至上边界不再大于下边界,此时两边界指向的元素就是要查找的元素。而对于这两种不同的比对方法我的理解是分别从左边和从右边分别逼近x。即:
条件arr[mid]>=x查找出来的是第一个最小的满足条件的数
条件arr[mid]<=x查找出来的是最后一个最大的满足条件的数
下面来看看两种实现:
1.第一种实现:
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,7,7,7,9,10 };
int x = 0;
scanf("%d", &x);
int l = 0, r = 9;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (arr[mid] <= x)
{
l = mid;
}
else
r = mid - 1;
}
if(arr[l]==x)
printf("%d ", l);
return 0;
}
2.第二种实现:
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,7,7,7,9,10 };
int x = 0;
scanf("%d", &x);
int l = 0, r = 9;
while (l < r)
{
int mid = l + r >> 1;
if (arr[mid] >= x)
{
r = mid;
}
else
l = mid+1;
}
if (arr[l] == x)
printf("%d ", l);
return 0;
}
按上面的说法,如果我们数表中有相同的数据,那么我们采用两种方式所得到的下标就不同:
arr[mid]>=x,那么得到的7的坐标是7;
arr[mid]<=x,那么得到的7的坐标是5
即:
arr[mid]>=x:
arr[mid]<=x:
优化:
既然我们数表中可能出现重复元素,那么我们就要针对这个元素,设计出一个方案,可以得到这个元素的起始位置和终止位置。为此我们自然而然想出一个结合上面两种思路的方法:输出端设计为两个数,分别是查找元素的起始坐标和末尾坐标,这样就能包含重复元素的情况。而判断性质我们也可以采用原来两个逼近的方式,这样我们就得到两个边界。
最后完整代码如下:
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,7,7,7,7,10 };
int x = 0;
int l = 0, r = 9;
scanf("%d", &x);
while (l < r)
{
int mid = l + r >> 1;
if (arr[mid] >= x) r = mid;
else l = mid + 1;
}
if (arr[l] != x)
printf("Fail\n");
else
{
printf("%d ", l);
int l = 0, r = 9;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (arr[mid] <= x) l = mid;
else r = mid - 1;
}
printf("%d", l);
}
return 0;
}