查找可以按照简单的直接进行遍历,当然最好的方法是先排序,然后按照二分搜索的方法进行查找。先来看题目:
题目描述:
输入 N 个学生的信息,然后进行查询。
输入:
输入的第一行为 N,即学生的个数(N<=1000)
接下来的 N 行包括 N 个学生的信息,信息格式如下:
01 李江 男 21
02 刘唐 男 23
03 张军 男 19
04 王娜 女 19
然后输入一个 M(M<=10000),接下来会有 M 行,代表 M 次查询,每行输入
一个学号,格式如下:
02
03
01
04
输出:
输出 M 行,每行包括一个对应于查询的学生的信息。如果没有对应的学生信息,则输出“No Answer!”
样例输入:
4
01 李江 男 21
02 刘唐 男 23
03 张军 男 19
04 王娜 女 19
5
02
03
01
04
03
样例输出:
02 刘唐 男 23
03 张军 男 19
01 李江 男 21
04 王娜 女 19
03 张军 男 19
首先有一个很奇怪的点,也是我与答案有所不同的地方,那就是题目到底是每次输入一个字符串就打印它的相关查找信息,还是最后一次打印 m 条数据的查找信息, 答案给的是最后一并打印,这也是有所不同的原因。注意这里不能用binary_search() stl函数,因为是字符串型的比较,而该函数应该默认是整型的,所以包括double 型的都最好不要使用,容易出现错误。
首先是代码:
#include<iostream>
#include<cstdio>
#include<stdlib.h>
#include<algorithm>
#include<vector>
#include<string.h>
using namespace std;
struct Student {
char sno[20];
char sname[20];
char ssex[6];
int age;
}buf[1000];
//二分stl函数要求非递减的
bool cmp(Student a, Student b)
{
return strcmp(a.sno,b.sno) < 0;
}
int b_search(Student a[], int n, char s[])
{
int low = 0;
int high = n - 1;
while (low <= high)
{
int mid = (low + high) / 2;
int c = strcmp(a[mid].sno, s);
if (c == 0)
return mid;
else if (c > 0)
high = mid - 1;
else if (c < 0)
low = mid + 1;
}
//没有找到时返回下标为 -1 ,不能用 0 表示
return -1;
}
int main()
{
int n;
while (scanf("%d", &n) != EOF)
{
for (int i = 0; i < n; i++)
scanf("%s%s%s%d",buf[i].sno,buf[i].sname,buf[i].ssex,&buf[i].age);
int m;
scanf("%d",&m);
char s2[1000][20];
sort(buf, buf + n, cmp);
//for (int i = 0; i < n; i++)
//strcpy(s1[i],buf[i].sno);
for (int j = 0; j < m; j++)
scanf("%s",s2[j]);
for (int k = 0; k < m; k++)
{
int res = b_search(buf,n,s2[k]);
if (res == -1)
printf("no answer!\n");
else
printf("%s %s %s %d\n",buf[res].sno,buf[res].sname,buf[res].ssex,buf[res].age);
}
}
return 0;
}
运行结果(调试过程证实了sort函数是起作用的) :
6
03 l l 23
04 l l 24
05 l l 25
01 l l 21
06 l l 26
02 l l 22
3
06 01 00
06 l l 26
01 l l 21
no answer!
程序要注意的是几点,首先是自定义cmp函数的写法, 传入参数数组的写法 ; 还有二分搜索法的high开始是 n - 1; 没用二分搜索的时间复杂度是 O(n * m),最多可到千万级别, 使用二分搜索方法的时间复杂度为 O(n * logn + m * logn).
王道中提出了一个问题就是找到一个下标使得对于一个已经排序的数组,该下标(包含该下标在内)前面的所有数组元素都小于所给的目标数字,而后面的都大于或等于该目标数字。
首先这个问题可以很容易的通过lower_bound函数完成,lower_bound函数返回的是大于等于目标数字的最小的元素,那么它的下标 - 1即为满足条件的值,实际上lower_bound函数就是通过二分搜索法实现的。另外要注意的是int型和浮点型是可以比较大小的 4 > 3.5 && 3 < 3.5是成立的,当然实际上要求返回的下标可以等于目标数字,所以应该用upper_bound函数才对。
代码为:
#include<iostream>
#include<cstdio>
#include<stdlib.h>
#include<algorithm>
#include<vector>
#include<string.h>
using namespace std;
int main()
{
int a[] = {0,1,2,3,4,5,6,7,8};
int pos = lower_bound(a, a + 9, 4) - a - 1;
printf("{0,1,2,3,4,5,6,7,8}小于3.5的最大的下标是(理论值是3): %d\n",pos);
return 0;
}
输出结果:
{0,1,2,3,4,5,6,7,8}小于3.5的最大的下标是(理论值是3): 3
请按任意键继续. . .
实际上用upper_bound - 1得到的才是题目所要求的结果,下面是upper_bound函数的使用,以及完整的算法:
#include<iostream>
#include<cstdio>
#include<stdlib.h>
#include<algorithm>
#include<vector>
#include<string.h>
using namespace std;
int m_bound(int a[], int n, int index)
{
int low = 0;
int high = n - 1;
while (low < high)
{
int mid = (low + high) / 2;
if (a[mid] <= index)
low = mid + 1;
else if (a[mid] > index)
high = mid - 1;
}
//必须要 - 1,因为当mid > 符合的结果坐标时,经过循环,最终会回到 mid ,即结果坐标 + 1
//当while (low <= high) 结束循环时,一定是low = high + 1, high 此时的值就是结果mid - 1即 low - 1
//所以和书上的方法基本一致,结果也一样,low = mid + 1很巧妙
//要注意的是书上 top = size这点不太对,应该是size - 1
return low - 1;
}
int main()
{
int a[] = {0,1,2,3,4,5,6,7,8};
//upper_bounde - 1 得到的一定是小于或等于的最大值的下标
int pos = upper_bound(a, a + 9, 4) - a - 1;
printf("小于或等于目标的最大数组元素的下标是: %d\n",pos);
int ans = m_bound(a, 9, 4);
printf("手写算法得到的结果下标为: %d\n",ans);
return 0;
}
运行结果:
小于或等于目标的最大数组元素的下标是: 4
手写算法得到的结果下标为: 4
请按任意键继续. . .