题目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. SaaS系列介绍之十一: SaaS商业模式分析

    1 配置模式 中国企业很多是人治,管理弹性非常大,公司的政策经常变化,管理流程.业务变化也非常大,发展也非常快;一个公司今年是10个人,明年是100个人,后年可能是1000人.管理机制.方法处于经常变 ...

  2. Java学习笔记之:java运算符

    一.介绍 计算机的最基本用途之一就是执行数学运算,作为一门计算机语言,Java也提供了一套丰富的运算符来操纵变量.我们可以把运算符分成以下几组: 算术运算符 关系运算符 位运算符 逻辑运算符 赋值运算 ...

  3. Linux下禁用、启用SeLinux

    一些Linux默认都是启用SeLinux的,在安装操作系统的时候我们可以选择开启或者关闭SeLinux,但是在安装完系统之后又如何开启与关闭呢? 在/etc/sysconf下有一个SeLinux文件, ...

  4. muParser公式库使用简介( 转)

    muParser是一个跨平台的公式解析库,它可以自定义多参数函数,自定义常量.变量及一元前缀.后缀操作符,二元操作符等,它将公式编译成字节码,所以计算起来非常快. 当前版本V1.28,官方网址http ...

  5. NYOJ-253 凸包

    LK的旅行 时间限制:2000 ms  |  内存限制:65535 KB 难度:5   描述 LK最近要去某几个地方旅行,她从地图上计划了几个点,并且用笔点了出来,准备在五一假期去这几个城市旅行.现在 ...

  6. Hibernate框架简述

    Hibernate的核心组件在基 于MVC设计模式的JAVA WEB应用中,Hibernate可以作为模型层/数据访问层.它通过配置文件(hibernate.properties或 hibernate ...

  7. 异常:java.lang.UnsupportedOperationException: Manual close is not allowed over a Spring managed SqlSession

    使用mybatis-3.2.2.jar + mybatis-spring-1.2.0.jar集成时,报以下异常: 15:42:48.538 [Thread-1] DEBUG o.s.b.f.s.Dis ...

  8. git文件未改动pull的时候提示冲突

    今天在mac下使用git工具,出现一个很奇怪的问题. 先声明当前工作目录是干净的,运行 git status 没有任何文件改动,且没有任何需要push的文件. 我执行 git pull 命令,直接提示 ...

  9. git push提示或错误

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

  10. jenkins mac slave 设置

    1.在jenkins上增加节点, 2,在mac系统中将ssh的服务打开在偏好设置- 互联网与无线 - 共享中 3,使用mac root用户修改sshd-config的鉴权方式 首先获取到root用户登 ...