二分法查找有序数组里的元素
设数组arr有n个元素,且从小到大有序排列,要查找的数num一定在数组中,把数组看作一个区间段,并且设区间段左端的元素为arr[left],右端的元素为arr[right],设middle=(left+right)/2。
当n为奇数时,把数组分成前,后两段,且arr[middle]在前半段,前半段元素个数比后半段个数多1。
当n为偶数时,把数组分成前,后两段,且arr[middle]在前半段,前,后两段元素个数相等。
因为要查找的值一定在数组中,所以要查找的元素要么在前半段,要么在后半段,故比较num与arr[middle]的值。
若num<arr[middle],则说明num在前半段,即在arr[left]至arr[middle-1]中。 若num>arr[middle],则说明num在后半段,即在arr[middle+1]至arr[right]中。 若num==arr[middle],则说明直接找到。
若没有找到,当num<arr[middle]时,即num在区间段arr[left]至arr[middle-1]中,则继续划分,重复以上的步骤,即区间段左端元素仍为arr[left],右端元素变为arr[left]=arr[middle-1];同理,当num>arr[middle]时,区间段左端元素变为arr[left]=arr[middle+1],右端元素仍为arr[right]。
以上步骤一直重复,如果一直都没找到所要查找的元素,则逐渐地,区间段的长度就会原来越短,会出现这样的情况:
……
区间段含4个元素:
区间段含3个元素:
区间段含2个元素:
区间段含1个元素:
当区间段含一个元素:此时这个元素必然就是要找的数num,此时有arr[left]==arr[right]==num,即有left==right。
当区间段含两个元素:则此时继续将区间段一分为二,前半段与后半段都只含一个元素,此时要么查找的数就是前半段那个唯一的元素,要么查找的数就是后半段那个唯一的元素。当查找的元素在前半段时,此时有arr[middle]==arr[(left+right)/2]==arr[left]==num<arr[right],即有left<right;当要查找的元素在后半段时,此时arr[left]=arr[middle+1],即有arr[left]==arr[right]==num,即有left==right;
当区间段含三个元素时:继续划分一下,要么直接找到num==arr[middle],要么区间段减小变为含一个元素的情况。
当区间段含四个元素时,继续划分一下,要么直接找到num==arr[middle],要么区间段减小变为含一个元素或含两个元素时的情况。
……
综上,会发现要查找num时,无论数组元素个数n为奇数或者n为偶数对结果都没有影响。并且我们还会发现,当查找到要找的数num时,都一定有left<=right; 特别地,如果要查找的数num不在数组中,则最后区间段的长度必定会变为1,此时有arr[left]==arr[right]!=num,即有left==right。
以下是代码实现
int left=0;
int middle=0;
int right=sizeof(arr)/arr[0]-1;
while(left<=right)
{
middle=(left+right)/2;
if(num<arr[middle])
{
left=left; //其实这一行是多余的,加上来可以方便我们理解查找过程
right=middle-1;
}
else if(num>arr[middle])
{
left=middle+1;
right=right; //同样是多余的一行
}
else
{
//若找到要查找的数,则直接跳出循环,该数下标就为middle,
//即有arr[middle]==num,若找不到,则最后arr[middle]!=num。
break;
}
}
以上数组元素从小到大排列的查找方式,若排序方式为从大到小,只需把上述if—else语句的判断条件改成相反的即可。