C++基础六:C++入门知识、黑盒测试(详解)与复杂度

cin与cout

cin与cout是C++中的输入和输出函数,需要添加头文件

include<iostream>”和“using namespace std;

才能使用,cin和cout不需要像C语言中scanf、printf函数那么指定输入输出的格式,也不需要使用取地址运算符&,而可以直接进行输入输出,相比较而言比较方便

1.cin

cin是c和in的合成词,采用输入运算符“>>”来进行输入,如果想要输入一个整数n,可以cin>>n;

可以发现,cin的输入不指定格式,也不需要加取地址运算符,直接写变量名就可以,同理,读入double型浮点数和char型都是一样的,即

cin>>db;

cin>>c;

如果同时读入多个变量也是一样的写法,只需要往后面使用>>进行扩展即可

同时读入多个变量也是一样的写法,只需要往后面使用>>扩展即可,例如下图

而如果想要读入一整行,则需要使用getline函数,例如下面的代码就把一整行都读入char型数组str[100]中

如果是string容器,则需要用下面的方式输入

2.cout

cout是c和out的合成词,其使用方法和cin几乎是一致的,只不过使用的是输出运算符<<,下面的代码输出了int型变量n,double型变量db,char型变量c,char型数组str[]

输出时中间并没有加空格,因此可以在每个变量之间加上空格

对cout来说,换行有两种方式

(一)和C中相同,也就是使用\n来进行换行

(二)使用endl来表示换行(endl是end line的缩写)

如果想要控制double型的精度,例如输出小数点后两位,那么需要在输出之前加上一些东西,并且要加上#include<iomanip>头文件

不推荐读者使用cin和cout来进行输入和输出,因为它们在输入/输出大量数据的情况下表现得非常糟糕,有时间题目的数据还没有输入完毕就已经超时

因此还是推荐读者使用C语言的scanf和printf函数进行输入/输出,只有在十分必要的时候才使用cin与cout

浮点数的比较

由于计算机中采用有限位的二进制编码,因此浮点数在计算机中的存储并不总是精确的,例如在经过大量计算后,一个浮点型的数3.14在计算机中就可能存储成3.1400000000001,也有可能存储成3.1399999999999,这种情况下会对比较操作带来极大的干扰,于是需要引入一个极小数eps来对这种误差进行修正

1.等于运算符(==)

如上图所示为等于区间示意图

如果一个数a落在了[b-eps,b+eps]的区间中时,就应当判断为a==b成立

经验表示,eps取10^-8是一个合适的数字,对大多数的情况既不会漏判,也不会误判,因此可以将eps定义为常量1e-8

const double eps=1e-8;

为了使比较更加方便,把比较操作写成宏定义的形式

如上面的代码,将a和b相减,如果差的绝对值小于极小量eps,那么就返回true,加上如此多的括号也是为了防止宏定义可能带来的错误

注意:
如果想要使用不等于,只需要在使用时的Equ前面加一个非运算符!即可

2.大于运算符(>)

如上图所示为大于区间示意图

如果一个数a要大于b,那么就必须在误差eps的扰动范围之外大于b,因此只有大于b+eps的数才能判定为大于b

3.小于运算符(<)

如上图所示为小于区间示意图

如果一个数a要小于b,那么就必须在误差eps的扰动范围之外小于b,因此只有小于b-eps的数才能判定为小于b

4.大于等于运算符(>=)

如上图所示为大于等于区间示意图

由于大于等于运算符可以理解为大于运算符和等于运算符的结合,于是需要让一个数a在误差扰动范围内能够判定其为大于或者等于b,因此大于b-eps的数都应当判定为大于等于b

5.小于等于运算符(<=)

如上图所示为小于等于区间示意图

与大于等于运算符类似,小于等于运算符可以理解为小于运算符和等于运算符的结合,于是需要让一个数a在误差扰动范围内能够判定其为小于或者等于b,因此小于b+eps的数都应当判定为小于等于b

6.圆周率π

圆周率π,因为有cos(π)=-1,可知π=arccos(-1),因此只需要把π写成常量acos(-1.0)即可

const double Pi=acos(-1.0);

综上所述,把上面的核心部分汇总起来就是下面这些代码

注意:
(一)由于精度问题,在经过大量运算后,可能一个变量中存储的0是个很小的负数,这时如果对其开根号sqrt,就会因不在定义域内而出错,同样的问题还出现在asin(x)当x存放+1、acos(x)当x存放-1时,这种情况需要用eps使变量保证在定义域内

(二)在某些由编译环境产生的原因下,本应为0.00的变量在输出时会变成-0.00,这个问题是编译环境本身的bug,只能把结果存放到字符串中,然后与-0.00进行比较,如果比对成功,则加上eps来修真为0.00

复杂度

一般来说,复杂度可以分为时间复杂度和空间复杂度,有时还会提到编码复杂度,学习它们非常重要

1.时间复杂度

简单的多,时间复杂度是算法需要执行基本运算的次数所处的等级,其中基本运算就是类似加减乘除这种计算机可以直接实现的运算

时间复杂度是评判算法时间效率的有效标准

示例如下,下面是一个for循环,用来计算数组a中元素的和

for循环执行了n次,因此总共有n次加法运算,若在上述for循环中sum加了两次a[i],则总共有2n次加法运算

显然n次基本运算与2n次基本运算当n的规模增大时的增长趋势是相同的(都是线性增长),于是把O(n)称作上面两段代码的时间复杂度,表示两段代码消耗的时间随着规模n的增大而线性增长

显然,是否是线性增长与前面的系数无关,因此O(n)与O(2n)是等价的

如下图所示,该段代码将二维数组a中的所有元素相加,其基本运算的次数为n^2,因此称这段代码的时间复杂度为O(n^2),表示其消耗的时间随着规模n的增大呈现平方级增长

在时间复杂度中,高等级的幂次会覆盖低等级的幂次,因此O(3n^2+n+2)=O(3n^2)=O(n^2)成立,显然O(3n^2+n+2)会趋近于O(cn^2),其中c是一个常数,我们把这个常数称为算法时间复杂度的常数,当有些算法实现较为复杂时,其常数会比较大,这是即便时间复杂度相同,其性能也会有较大差距

注意:将时间复杂度时一般不带系数

例如二分查找的时间复杂度是O(logn),表示对数的时间复杂度(对数复杂度书写时一般省略底数),常数复杂度O(1)则表示算法消耗的时间不随规模的增长而增长,显然有O(1)<O(logn)<O(n)<O(n^2)成立

读者在写程序时要特别注意分析算法的时间复杂度,因为较高的时间复杂度会让测评体系返回运行超时(Time Limit Exceeded)

一般来说,只需要大致估计算法的时间复杂度在哪个等级即可,例如对时间复杂度为O(n^2)的算法来说,当n的规模为1000时,其运算次数大概为10^6级别,而当n的规模为1e5时,其运算次数就会有10^10级别

对一般的OJ系统来说,1s能承受的运算次数大概是1e7~1e8,因此O(n^2)的算法当n的规模为1000是可以承受的,而当n的规模是1e5时则是不可以承受的

2.空间复杂度

和时间复杂度类似,空间复杂度采用相同的写法,表示算法需要消耗的最大数据空间

例如对某个算法来说,如果其消耗的最大数据空间是一个二维数组,那么这个算法的空间复杂度就是O(n^2)

在一般的应用中,一般来说空间都是足够使用的,不开1e7以上的数组即可,因此其重要性一般没有时间复杂度高

另外,O(1)的空间复杂度是指算法消耗的空间不随数据规模的增大而增大

考虑到空间一般够用,因此常常采用以空间换时间的策略

3.编码复杂度

编码复杂度是一个定性的概念,并没有什么量化的标准,对一个问题来说,如果使用了较长的算法思想,那么代码量将会非常巨大,1其编码复杂度就会非常大

黑盒测试

1.单点测试

对单点测试来说,系统会判断每组数据的输出结果是否正确,如果输出正确,那么对该组数据来说就通过了测试,并获得了这组数据的分值

在这种情况下,题目的总得分等于通过的数据的分值之和(例如PAT采用了单点测试)

从代码编写上来说,单点测试只需要按正常的逻辑执行一遍程序即可,是一次性的写法,即程序只需要对一组数据能够完整执行即可

2.多点测试

与单点测试相对,多点测试要求程序能一次运行出所有的数据,并要求所有输出结果都必须完全正确,才能算作这题通过,而只要有其中一组数据的输出错误,本题就只能得0分

大部分在线评测系统都采用了这种方式,因为只有这种方式才能严格考验做题人的代码是否严谨,对多点测试来说,由于要求程序能运行所有数据,因此必须保证程序有办法反复执行代码的核心部分,因此要用到循环,题目一般有3中输入的格式,需要采用不同的输入方式

(一)while...EOF型

如果题目没有给定输入的结束条件,那么就默认读取到文件末尾

对黑盒测试来说,所有输入数据都是放在一个文件里的,系统会让程序去读取这个文件里的输入数据,然后执行程序并输出结果。那么如果题目没有指定何时结束输入,一般都是指输入完所有数据为止(到达文件末尾)。

如何解决这种输入要求呢?

首先,scanf函数可以直接作为一条语句使用,但是它也是有返回值的,scanf函数的返回值为其成功读入的参数的个数,即如果语句scanf("%d",&n)成功读入了一个整数n,那么scanf的返回值就是1;如果语句scanf("%d%d",&n,&m)成功读入了两个整数n、m,那么scanf的返回值就是2.

那什么时候会停止,即输入失败?

读入失败时scanf函数是否返回0,?正常的控制台(屏幕黑框)中的输入一般是不会失败的,只有在读取文件时到达文件末尾导致的无法读取现象,才会产生读入失败,此时scanf函数返回的值是-1而不是0,且C语言中使用EOF(End Of File)来代表-1

因此,当题目没有说明有多少数据需要读入时,就可以利用scanf的返回值是否为EOF来判断输入是否结束,写法如下

上述代码的含义是:是要scanf的返回值不为EOF(即文件中的数据没有读完),就反复读入n,执行while函数体的内容,当读入失败(到达文件末尾)时,结束while循环

当在黑框里输入数据时,并不会触发EOF状态,因此如果想要在黑框里面手动触发EOF,可以按<Ctrl+Z>组合键,这时就会显示一个^Z,按<Enter>键就可以结束while了

如果读入字符串,则有scanf("%s",str)与gets(str)两种方式可以用,写法如下

(二)while...break型

该种类型是while...EOF型的延伸,题目要求当输入的数据满足某个条件时停止输入

例如当输入的两个a和b都为0时结束输入,这种类型有两种写法,一种是在while...EOF的内部进行判断,当满足退出条件时中断(break)当前while循环,写法如下

另一种更简洁的写法是,把退出条件的判断放到while语句中,令其余scanf用逗号隔开,写法如下

上面的循环条件的含义为,当a和b中有一个不为零时就进行循环(循环条件a||b的全写为a!=0||b!=0)

(三)while(T--)型

该种类型中,题目会给出测试数据的组数,然后才给出相应数量组数的输入数据

由于给定了测试数据的组数,因此需要一个变量T来储存,并在程序开始时读入,在读入T后,下面就可以进行T次循环,每次循环解决一组数据的输入和输出,while(T--)就是循环执行T次的含义,写法如下

以上就是多点测试的三种测试类型,下面讲解三种常见的输出类型

1.正常输出

这种输出类型要求需要每两组输出数据中间没有额外的空行,即输出数据时连续的多行

2.每组数据输出之后都额外加一个空行

该要求很容易实现,只需要在每组输出结束之后额外输出一个换行符\n即可

3.两组输出数据之间有一个空行,最后一组数据后面没有空行

该要求一般在第三种输入类型while(T--)的情况下,只需要通过判断T是否已经减小到0来判断是否应当输出额外的换行,写法如下

与上述要求类似的要求是:输出一行N个整数,每两个整数之间用空格隔开,最后一个整数后面没后空格,写法如下

当i循环到N-1,即最后一个元素,把空格改为换行

最后,在多点测试中,每一次循环都要重置一下变量和数组,否则在下一组数据来临的时候变量和数据的状态就不是初始状态了

评测结果分析

常见的评测结果

1.答案正确(Accepted,AC)

所提交的代码通过了所有的数据

2.编译错误(Complie Error,CE)

代码无法通过编译,返回Compile Error,注意是否选择错了语言

3.答案错误(Wrong Answer,WA)

代码又漏洞或者算法是错误的只是刚好能通过样例,需要认真检查代码的逻辑

4.运行超时(Time Limit Exceeded,TLE)

超过运行时间限制,可能是由于算法的时间复杂度过大导致的,也可能是某组数据使得代码中某处地方死循环

5.运行错误(Runtime Error,RE)

常见的有段错误(非法访问了内存,例如数组越界,指针乱指)、浮点错误(除数为0,模数为0)、递归爆栈(递归时层数过深),需要先检查数组大小是否比题目的数据范围大,然后再检查是否有特殊数据可以使除数或模数为0,有递归的情况则检查是否在大数据时递归层数太深

6.内存超限(Memory Limit Exceeded,MLE)

如果程序中使用太多的空间,则会返回MLE,例如数组太大

7.格式错误(Presentation Error,PE)

基本是由多输出了空格或者换行导致的,稍作修改即可

8.输出超限(Output Limit Exceeded,OLE)

程序输出了过量的内容,就会返回OLE。一般是由于输出了大量的调试信息或者特殊数据导致的死循环输出导致的

至此,对老手来说我们的C++和竞赛基础已经复习完毕,对于新手,相信你们也学到了很多有用的知识,接下来会更新算法相关的知识,欢迎大家一起交流与讨论!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值