题目描述
有 n个数(),这 n个数已按从大到小顺序存放在一个数组中,然后有 T次查询,每次输入一个数,要求用折半查找法找出该数在数组中第一次出现的位置。如果不在数组中输出 0。
输入
第一行数组元素的个数 n。
第二行 n个数组元素的值。(10 9 8 7 6 5 4 3 2 1).
第三行输入查询次数 T()。
往下有 T 行,每行输入一个需要查询的数字。
输出
查找的值在数组中的位置。
提示
注意:数组空间为 1000000 和 100000.
tips:使用 scanf 和 printf 会更快哦.
#include<stdio.h>
int main()
{int n;scanf("%d",&n);int a[n];int i;for(i=0;i<n;i++){scanf("%d",&a[i]);}int t;scanf("%d",&t);int b[t];for(i=0;i<t;i++){scanf("%d",&b[i]);}for(i=0;i<t;i++){int left=0,right=n-1;int first_pos=-1;//first_pos用来记录第一次出现位置的关键变量。而-1表示还没找到第一次出现位置。while(left<=right){int mid=left+(right-left)/2;if(b[i]==a[mid]){first_pos=mid;//记录找到的当前位置right=mid-1;//再往左找,看看是否有比第一次的中值位置还往前的}else if(b[i]<a[mid]){left=mid+1;}else if(b[i]>a[mid]){right=mid-1;}}if(first_pos!=-1){printf("%d\n",first_pos+1);}else{printf("0\n");}} return 0;
}
问题一:为什么要使用first_pos=-1,来记录第一次出现位置?
因为题目要求用折半查找法找出该数在数组中第一次出现的位置。折半查找找到的值可能是任意一个匹配的位置,不一定是第一个。用标志变量来最后输出。如果不记录,即为下述
if(a[mid]==b[i]){printf("%d\n",mid+1);break;
}
这样输出的可能是中间的一个值。为了不让输出中间的值,则一定要定义一个变量来记录位置,如果在前面还能找到该值,则if语句成立,不断将变量的值更新,最后不成立的时候,即为第一次出现的位置,最后就可以输出了。而如果哪一个条件都不满足,最后first_pos=-1,不变,仍为初始值,既没有找到符合条件的位置,则输出0.
同时,因为题目有数组空间限制,所以如果遍历的话容易超时,此时定义一个标志变量才不会使数组溢出或时间超限。
综上所述,有个标志变量是最正确的选择。
问题二:为什么是 int mid=left+(right-left)/2;而不是mid=(left+right)/2;?
第一种mid定义会避免整数溢出,当left和right都很大时,如下述:
int left=1000000;
int right=1000000;
int mid2=(left+right)/2;//相加之后变成两倍,会溢出。
int mid1=left+(right-left)/2;//相减之后变成0,再加上left,值没有变得很大,所以不会导致值溢出。
因此,以后碰到会出现数组溢出或者时间超限的时候就要考虑加法是否会变的特别大。
这种折中查找刚好不会隔开任何数,可以跳跃遍历所有可能正确的值,这正是这种方法的高效之处。正如上述十个数字,如果要查找8,mid=4,a[mid]=a[4]=6<8,所以会right=mid-1=3,之后mid=1;a[mid]=a[1]=9>8,向后查找,left=2,mid=2,此时中值的位置刚好是要找的数字。不会一直朝一个方向,而是根据需要找位置。
特别注意:最后输出的是位置,应该是下标加1.