题目1069:查找学生信息

这篇文章中提到的问题主要是由于调试平台Visual Studio和测试平台Online Judge的一些小差异,造成在Visual Studio中调试通过的代码,在输入OJ时要手动修改函数,后知后觉,可以设置一下编译参数来让Visual Studio尽量使用标准C++库中的函数。这样解决已经能够极大缓解这个矛盾,但是由于最新的Visual Studio 2015的缘故,有些函数完全不被支持了,例如gets()(后来发觉,这个函数似乎确实不那么好用),这时,会有静态编译错误提示,在Visual Studio中按下F1键进入微软的官方文档查询相关函数的使用说明是最快的解决办法,百度谷歌此时并不会比MSDN更有帮助,当然,更不要像我一样怀疑自己:忘记了这么多?

要看某个函数在标准库中什么位置,这里是个好去处

遇到这个问题的,估计都是和我一样的入门级选手,不要怀疑自己,一个逻辑,只有自己实现过、调试过,才能真正理解;一个逻辑,只有多犯几次错误并一次次修正,才能真正掌握。

最初的想法

typedef struct {
int no; //【错误】学号是整型最好,但这是你简化问题的想法,看样例中学号似乎不是整型
char name[10];
char gender[2];
int age;
} info; info data[1000];
int query[10000]; int biSearch(info data[], int length, int queryNo); int main()
{
int n, m; scanf_s("%d", &n);
for (int i = 0; i < n; i++) //【错误】过度简化问题,样例中的学号是升序的,不代表所有样例都是升序输入的。应该考虑将输入的数据排序。
{ //【错误】如果升序输入了学号,就不要查找了,直接放入info[]中,直接使用下标就可以访问相应元素。
scanf_s("%d %s %s %d", &data[i].no, &data[i].name, &data[i].gender, &data[i].age);
} scanf_s("%d", &m);
for (int i = 0; i < m; i++)
{
scanf_s("%d", &query[i]);
}
return 0;
} int biSearch(info data[], int length, int queryNo)
{
int start = 0;
int end = length - 1;
int middle = (start + end) / 2; while (start < end)
{
if (middle < queryNo) //【错误】根据上面分析,学号为字符串,这里的比较就是错误的,于是改为if (middle < (int)queryNo),仍然【错误】
{
start = middle;
}
if (middle > queryNo)
{
end = middle;
}
if (middle == queryNo)
{
return middle; //【错误】写到这里感觉有些奇怪,这里的几个分支似乎是没有意义的,因为做了一个过度简化:输入的学号是升序的,见上面注释。
}
} }

调试中遇到的问题

  • scanf_s的使用

    • 在Visual Studio中,为加强安全性,使用scanf_s代替scanf,更安全的版本在输入字符串时,有讲究。
    • 若按照scanf_s("%s%s", buf1, buf2);使用,会提示错误:C4477,“scanf_s”: 格式字符串“%s”需要类型“int”的参数,但可变参数 2 拥有了类型“char *”
    • 原因是,没有为存储字符串的变量指定其长度。
    • 指定了格式%s后,可变参数中对应一个字符串指针char buf[10]后面跟一个存储该字符串的变量的长度,可以使用_countof(buf)来获取该变量的长度。
    • 这样就要对字符串的长度做仔细的考虑,比如这题中有个字符串存放性别,char gender[2]会引起错误,因为汉语的男或女占用2B,这里的长度刚刚够存放字符串而没有空间存放结束符\0,于是出现错误。char gender[5]就可以正确保存。
  • 从文件中读取fscanf_s()
    • 调试过程肯定要多次启动程序,不想每次都输入非常长的测试用例,于是想把测试用例存入文件,将文件注入标准输入流。
    • 因此尝试了在Visual Studio中使用fscanf_s,由于安全原因,在Visual Studio中使用fscanf与其他环境中也不太一样。参数类型不一样。
    • 【TODO】需要好好读一下MSDN,了解这个用法。
FILE* fp;
errno_t err;
err = fopen_s(&fp, "student", "r"); //在标准C++中,第一个参数直接使用FILE*即可,这里要使用FILE** if (err == 0) //fopen_s返回0则说明读取文件正确
{
...
}
else
{
printf_s("无法读取文件");
}
  • 在控制台直接粘贴

    • 后来发现,与上面从文件读取相比,还是直接在控制台右键粘贴测试用例更方便。
    • 唯一遇到的小问题是,粘贴时,最后一行没有换行符,调试时到了这里就不动了,也没有错误提示。
    • 是什么问题卡住了?很简单——到了最后一组测试数据的最后一行时,按下Enter键将换行符补充上即可!
  • scanfscanf_s
    • 在VS中总提示scanf不安全,代码调试好后在OJ上验证,又要把所有的scanf_s换成scanf
    • 所谓的不安全,是scanf不会对字符串的长度做检查,有可能一直从控制台的缓存中读数据而超出字符串的长度限制,自己注意这一点就是安全的。
    • 避免这种繁琐的步骤,在VS的编译选项中添加\D _CRT_SECURE_NO_WARNINGS ,“右击工程-属性-配置属性-C/C++-命令行”,从而在VS中也使用标准的函数scanf
  • Visual Studio中一个奇怪的错误
    • stdio.h中出现了一个错误???!!!错误代码是_CRT_END_C_HEADER,错误提示是此类型没有存储类或类型说明符,这是什么问题???
  • OJ上遇到的问题
    • 总提示错误,添加空格,修改输出方式,都没用。
    • 结果原因是,存放学号的字符数组太小了!char no[50]不够,修改为cha no[100]就通过了。
    • 测试样例会很大,不要太小气,内存要求很宽裕时,尽量使用大数组!

编程实践中需要改进的地方

  • 小心使用强制类型转换

    • char queryNo[50]对这个字符串强制转换为整型,会出现什么结果?将得到一个非常大的整数,因为这会把50B的长度截取4B然后转换???
    • 因此if (middle < (int)queryNo)将整型的middle和这个转换来的未知的整数比较大小,会得到错误的结果。
    • 更好的做法是,是使用strcmp(),将字符串形式的学号直接使用该函数比较。
  • 字符串数组
    • 有一个问题,每读入一个查询学号,就输出一个学生的信息。样例中是把所有的学生信息放在一起输出。(后来发现,无需这样做!)
    • 因此,想将待查询的学号存入一个字符串数组,这里出现较大问题。字符串一直都没有掌握好,总出现内存越界的问题。
    • _countof()是计算一个数组的元素个数的,如果是二维数组,将返回行数。若希望得到二维数组的列数(即字符串长度),就引入sizeof(),二者配合得出。
    • scanf_s("%s", ..., sizeof(array) / _countof(array))中指定字符串长度的参数的正好就是字符串数组中每个字符串的长度。
char query[10000][50];
scanf_s("%s", query[i], 50);
/*_countof(query));起初想用VS中的宏来获得字符串的长度
*这里使用`_countof(query)`无法计算出字符串数组的单个元素的长度,会出现编译错误,因此采用硬编码,将50直接作为`scanf_s`的参数。
*后来自己测试了一下宏`_countof()`究竟是什么含义:_countof()计算一个数组的元素的个数
*/ #include <stdio.h>
#include <stdlib.h> //_countof()在这里定义 int main()
{
char array[10][30];
printf_s("%d", sizeof(array) / _countof(array));
return 0;
}

完整代码

// 机试指南-例2.10.cpp : 二分查找。
// #include "stdafx.h"
#include <stdio.h>
#include <string.h>
#include <algorithm> using namespace std; struct info {
char no[50];
char name[20];
char gender[5];
int age; bool operator < (const info & A) const
{
return strcmp(no, A.no) < 0; //【学习】使用strcmp
}
} buf[1000]; char query[10000][50];
int biSearch(int length, char queryNo[]); int main()
{
int n, m; scanf_s("%d", &n);
for (int i = 0; i < n; i++)
{
scanf_s("%s%s%s%d", buf[i].no, _countof(buf[i].no),
buf[i].name, _countof(buf[i].name),
buf[i].gender, _countof(buf[i].gender),
&buf[i].age);
} //【错误】VS中scanf输入字符串与标准C++不太一样,多一个参数来指定字符串的长度 sort(buf, buf + n); scanf_s("%d", &m);
//输入要查询的学号
for (int i = 0; i < m; i++)
{
scanf_s("%s", query[i], 50);//, _countof(query)); //【疑问】保存字符串数组,如何指定存入何处???不是地址的问题,是scanf_s需要字符串的长度
} for (int j = 0; j < m; j++)
{
int rank = biSearch(n, query[j]);
if (rank == -1)
{
printf_s("No Answer!\n");
}
else
{
printf_s("%s %s %s %d\n", buf[rank].no, buf[rank].name, buf[rank].gender, buf[rank].age);
}
}
/*
*while (m-- != 0) //每输入一个学号,二分查找一次;得到的输出与样例不符
*{
* char query[50];
* //scanf_s("%s", query, _countof(query));
* int rank = biSearch(n, query);
* if (rank == -1)
* {
* printf_s("No Answer!\n");
* }
* else
* {
* printf_s("%s %s %s %d\n", buf[rank].no, buf[rank].name, buf[rank].gender, buf[rank].age);
* }
*}
*/
return 0;
} //二分查找,返回找到的目标所在的下标
int biSearch(int length, char queryNo[])
{
int start = 0;
int end = length - 1;
int middle = (start + end) / 2; while (start <= end) //【错误】这里必须有等号!
{
if (strcmp(buf[middle].no, queryNo) < 0) //【错误】使用库函数strcmp。起初二分的逻辑搞错了。
{
start = middle + 1;
middle = (start + end) / 2;
}
else if (strcmp(buf[middle].no, queryNo) > 0)
{
end = middle - 1;
middle = (start + end) / 2;
}
else
{
return middle;
}
} return -1;
}

【九度OJ】题目1096-二分查找的更多相关文章

  1. 九度oj 题目1173:查找

    题目描述: 输入数组长度 n 输入数组      a[1...n] 输入查找个数m 输入查找数字b[1...m]  输出 YES or NO  查找有则YES 否则NO . 输入: 输入有多组数据. ...

  2. 九度oj 题目1177:查找

    题目描述: 读入一组字符串(待操作的),再读入一个int n记录记下来有几条命令,总共有2中命令:1.翻转  从下标为i的字符开始到i+len-1之间的字符串倒序:2.替换  命中如果第一位为1,用命 ...

  3. 九度oj 题目1096:日期差值

    题目描述: 有两个日期,求两个日期之间的天数,如果两个日期是连续的我们规定他们之间的天数为两天 输入: 有多组数据,每组数据有两行,分别表示两个日期,形式为YYYYMMDD 输出: 每组数据输出一行, ...

  4. 九度OJ 题目1384:二维数组中的查找

    /********************************* * 日期:2013-10-11 * 作者:SJF0115 * 题号: 九度OJ 题目1384:二维数组中的查找 * 来源:http ...

  5. hdu 1284 关于钱币兑换的一系列问题 九度oj 题目1408:吃豆机器人

    钱币兑换问题 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Sub ...

  6. 九度oj题目&amp;吉大考研11年机试题全解

    九度oj题目(吉大考研11年机试题全解) 吉大考研机试2011年题目: 题目一(jobdu1105:字符串的反码).    http://ac.jobdu.com/problem.php?pid=11 ...

  7. 九度oj 题目1007:奥运排序问题

    九度oj 题目1007:奥运排序问题   恢复 题目描述: 按要求,给国家进行排名. 输入:                        有多组数据. 第一行给出国家数N,要求排名的国家数M,国家号 ...

  8. 九度oj 题目1087:约数的个数

    题目链接:http://ac.jobdu.com/problem.php?pid=1087 题目描述: 输入n个整数,依次输出每个数的约数的个数 输入: 输入的第一行为N,即数组的个数(N<=1 ...

  9. 九度OJ题目1105:字符串的反码

    tips:scanf,cin输入字符串遇到空格就停止,所以想输入一行字符并保留最后的"\0"还是用gets()函数比较好,九度OJ真操蛋,true?没有这个关键字,还是用1吧,还是 ...

  10. 九度oj题目1009:二叉搜索树

    题目描述: 判断两序列是否为同一二叉搜索树序列 输入:                        开始一个数n,(1<=n<=20) 表示有n个需要判断,n= 0 的时候输入结束. 接 ...

随机推荐

  1. 【Linux高频命令专题(6)】mkdir

    简述 用来创建指定的名称的目录,要求创建目录的用户在当前目录中具有写权限,并且指定的目录名不能是当前目录中已有的目录. 命令格式 mkdir [选项] 目录... 命令参数 -m, --mode=模式 ...

  2. unigui判断浏览器内核、操作系统以及是否移动终端函数

    function GetDeviceType(var OsName, BrowserName: string; var IsMobileDevice: Boolean): string; var I: ...

  3. Android yyyymmdd转成yyyy-MM-dd格式

    //把yyyymmdd转成yyyy-MM-dd格式 public static String formatDate(String str){ SimpleDateFormat sf1 = new Si ...

  4. MyEclipse 2013 开发WebService

    1.在Package Explorer窗口右键File新建WebService Project项目,我的名称为:TestWebService 2.WebService Framework选择JAX-W ...

  5. HDU 3038 How Many Answers Are Wrong 并查集带权路径压缩

    思路跟 LA 6187 完全一样. 我是乍一看没反应过来这是个并查集,知道之后就好做了. d[i]代表节点 i 到根节点的距离,即每次的sum. #include <cstdio> #in ...

  6. 网站TCP链接暴增

    昨天上线后,TCP链接暴增,红点增多. 问题在查.其中有一部分,多线程修改,突破了线程数 64的限制.线程内,会发起网络请求. 怀疑是热点之一.其他的部分也有修改,也被怀疑.准备下次,2部分分开上线. ...

  7. html5 touch事件实现触屏页面上下滑动(二)

    五一小长假哪都没去,睡了三天,今天晕晕沉沉的投入工作了,但还是做出了一点点效果 上周用html5的touch事件把简单的滑动做出来了,实现了持续页面上下滑动的效果,参考之前 的文章及代码html5 t ...

  8. Java开发之反射的使用

    通过类名获取类. Class serviceManager = Class.forName("android.os.ServiceManager"); 获取方法 Method me ...

  9. CFF前端沙龙总结

    一. -OOCSS + Sass ——大漠 1. OOCSS 结构<=>皮肤 分离 容器<=>内容 分离 2. Sass 工具.处理器 SCSS(CSS风格)<=> ...

  10. git push提示或错误

    当 git 和 gerrit 一起使用的时候,你创建了一个 tag,现在需要 push 到远程仓库,当你没有权限的时候,会出现如下提示: $ git push origin v20150203 Tot ...