《算法笔记》学习笔记(1)
2021/4/7号 晚上21:36开始学习
第二章 c++/c快速入门
有的时候不要在一个程序中同时使用cout 和 printf 有的时候会出现问题。
头文件和主函数
头文件:
.h是头文件的文件格式
Math.h负责数学函数
String.h负责跟字符有关的函数
在c++中推荐使用cstdio来作为头文件,也就是说#include
主函数:
主函数是一个程序的入口位置
一个程序最多只可以有一个主函数
注意:在提交的时候选择c++提交,因为c++向下兼容c所以,这样提交可以一定程度上防止因为c++和c之间的语句的区别而导致的编译错误。
/*--------2021/4/8 上午10:05开始学习笔记------*/
2.1 基本数据库类型
2.1.1 变量的定义
变量不可以是C语言的标识符
变量名的第一个字母必须是字母或下划线;除第一个字母之外的其他字符必须是字母数组或下划线
C/C++中区分大小写
2.1.2 变量类型
基本数据类型分为整型 浮点型 字符型 c++中又包含布尔型
整型数据类型
短整型 整形 长整型(long long)
对int 类型来说 一个整数占用32bit,即4byte。取值范围: -2 ^ 31 - +(2 ^ 31 - 1) 也就是说在范围是10 ^ 9 内的整数都可以使用int型
对长整型来说long long,一个整数占用的是64bit,即8byte,取值范围是:- 2 ^ 63 - +(2 ^ 63 - 1)
如果题目要求的范围超过了:10 ^ 10 或者 10 ^ 18,那么就使用long long 型来存储。
注意:如果long long 型的值是大于 2 ^ 31 - 1 的值,那么需要在初值后面加上 LL,否则会编译错误。
%d 输出的是int型的数。
总的来说:看到题目说:10 ^ 9 以内或者 32位整数,用int型存放。
如果是10 ^18以内或者说是64位整数。就用long long 型来存放。
浮点型:
浮点数就是小数(通俗来说)。
一般分类单精度和双精度。
单精度是float 来说,一个浮点数占用32bit其中1bit作为符号位,8bit作为指数位,23bit作为尾数位。其有效精度只有6-7位。
对于双精度double来说,一个浮点数占用64位,其中1bit作为符号位,11bit作为指数位,52bit作为尾数位。其有效精度是15 - 16 位。
%f是float 和 double 输出格式。
做题时,注意使用double来定义。
字符型
字符常量可以向字符变量赋值。
在C语言中,字符常量使用ASCII码统一编码。标准ASCII码的范围是:0 - 127。
只需要知道:小写字母比大写字母的ASCII码值大32即可。
注意:字符常量需要使用单引号标注起来。
c语言中的字符常量必须是的单个字符,并且必须使用单引号标注。
可以直接将数字赋值给一个字符变量,然后输出的时候是字符。
ASCII码中有一部分是控制字符,是不可显示的。想换行 删除 TAB 等都是控制字符。
换行 使用 \n 来表示。
Tab键通过 \t来表示。
上述的 \字母 的形式就是转义字符。
实际做题时,最常用的是字符是:\n 代表换行和 \0 表示空字符,其ASCII码是0,注意\0 不是空格。
其中ASCII码为7的控制字符是响铃字符。(运行到这的时候计算机会响一下)
在C语言中没有一种特定的类型可以存储字符串类型的。
在C++中又string类型的可以存储字符串。
字符串常量是使用双引号标记的字符集,且字符串常量可以作为初值赋值给字符数组。并使用%s输出。
注意:不可将字符串常量赋值给字符变量。
4.布尔型变量
可以在C++中直接使用。
但是在C中必须添加 stdbool.h头文件才可以使用。
可以使用整型变量赋值。
非零包括正整数和负整数,也就是说只要不是零都会转为true。
只有0是false。
注意:当使用%d输出布尔类型变量的时候,true 和 false 会输出1 和 0;
2.1.3 强制类型转换
有的时候需要将浮点数的小数切掉来变成整数,或者是将整型变为浮点型来方便做除法。
强制类型转换的格式如下:(新类型名)变量名
注意:%.1f表示保留一位小数输出。
2.1.4 符号常量和const常量
符号常量通俗地讲就是“替换”,即用一个标识符来替代常量。
又称为:宏定义 或者 宏替换
定义格式如下:(末尾不加分号)
#define 标识符 常量
那么在程序执行前,程序中的所有标识符替换为常量。
另一种定义常量的方法是使用const
Const 数据类型 变量名 = 常量。
推荐使用const来定义常量。
define除了可以定义常量外,还可以定义任何语句和片段。
#define 标识符 任何语句和片段
例如:#define ADD(a, b) ((a) + (b))
实际上,宏定义是直接将对应的部分替换。然后才进行编译和运行的。
2.1.5 运算符
/**------------------------2021.4.8日 下午 17:08开始学习笔记**/
常用的运算符有:算术运算符 关系运算符 逻辑运算符 条件运算符 位运算符
算术运算符 :、/除法运算符 %模运算符
++自增运算符
--自减运算符
注意:对于除法运算符,当被除数和除数都是整型时,会向下取整。
此外,如除数是0的话回导致程序运行异常。
取模运算符返回被除数和除数相除得到的余数。
自增运算符两种写法。i ++ 和 ++i。
I++是先使用i再将i++,而 ++i是先将i加1然后在使用。
常用的有三种:&& || !分别对应的是:与 或 非
有六种 但是使用较少
由于int 型数的范围是2 ^ 31 - 1,那么程序中无穷大的数INF可以设置成:
(1 << 31) - 1. (注意:需要加括号,因为位运算符的优先级没有算数运算符高)
下面两个式子是等价的:
Const int INF = (1 << 31) - 1;
Const int INF = 0x3fffffff;
2.2.1 赋值表达式
使用n = m = 5;表示给n m 赋相同的值。
赋值运算符可以通过将其他运算符放在前边来实现赋值操作的简化。
2.2.2 使用scanf 和 printf输入输出
scanf函数的作用:
&n的解释:在C语言中,当变量定义之后会有一块内存,该内存空间的地址就是变量的地址。
&为取地址运算符。
对于int类型 %d
Long long类型 %lld
Float 类型 %f
Double %lf
Char %c
字符串 %s
数组比较特殊,数组名称本来就是代表了数组第一个元素的地址,所以不需要再加取地址运算符。
其实,scanf中双引号的内容其实就是整个输入。
如果要输入的是的字符之间有空格,那么在scanf中不需要加上空格。
原因是:scanf再读到空格 或者是 tab的时候,直接跳过(除非是用的是%c)
此外,字符数组%s读入的时候以空格跟换行为读入结束的标志。
注意下面的图片:
在输出的时候不需要写取地址符号。
对于double类型的变量,printf中变成了使用%f即可。
注意:printf输出”%或者是”\”,可以使用printf(“%%”); print(“\\”);
最常用的三种实用的输出格式:
2.2.3 使用getchar 和 putchar输入和输出字符
getchat用来输入得到的那个字符。
Putchar 用来输出单个字符。
注意:getchar可以识别换行符
2.2.4 注释
使用/**/注释,可以连续注释若干连续行的内容。
使用//单行注释
2.2.5 typeof 可以给复杂的数据类型起一个别名,这样就可以使用新的名字来代替原来的写法了。
例如:typeof long long ll;
2.2.6 常用的Math函数
需要加上math.h文件
Fabs(double x)表示去变量x的绝对值。
Floor(double x) 和 ceil(double x)分别用于double 型变量的向下取整 和 向上取整
Pow(double r,double p)该函数返回r^p,要求r和p都是double 型。
Sqrt(double x) 该函数用来返回变量的算数平方根。
Log(double x) 该函数用来返回变量x以自然对数为底的对数
注意:需要注意的是C语言中没有对任意底数求对数的函数。需要使用换底公式来得到。
Sin(double x)
Cos(double x)
Tan(double x)
Acos (-1.0)因为 cos (pi) = -1;
Asin()
Acos()
Atan()
分别得到变量的反正弦值 反余弦值 反正切值
Round()函数用来将x四舍五入返回的类型也是double 类型的,需要进行取整。
2.3 选择结构
2.3.1 if语句
实现判断逻辑
如果在else{其中的语句只有一个句子,那么可以去掉大括号使外挂简洁一点}
可以使用else if 写法增加判断逻辑
技巧:
如果在if()中需要!=0, ==0,那么可以省略,使用写法:if(n)
2.3.2 if语句的嵌套
2.3.3 switch 语句
适用于条件较多时的判断,但是在分支提交较少时用得不多。
Switch(){
Case xxx:
Break;
Case xxx:
Break;
Default:
}
Case中默认将两个case 之间的内容作为上一个case的内容。
如果删break语句后,程序会将删除位置之后的所有语句都输出。
2.4 循环结构
2.4.1 while语句
While(条件A){} 其中在写条件的时候使用的表达式技巧都可以使用。
N % 10表示得到n的最后一位。
N / 10表示删除n的最后一位。
2.4.2 do .... while 语句
Do{
}while(条件 A);
注意:do while 会先执行一次循环体
但是while 会先判断条件。
推荐使用while循环
2.4.3 for语句
使用频率是三种循环中最高的。
For循环中只有一个语句的时候可以不加{}。
注意:在C语言中不允许在foru语句中表达式1中定义变量。
但是在C++中可以。
注意:在训练的时候尽量将文件的扩展名保存为.cpp。
2.4.4 break 和 continue语句
Break使用在循环中的时候可以直接退出循环。
continue使用在循环中表示推出当前循环轮回进入下一个轮回。
2.5 数组
2.5.1 一维数组
数组就是将相同数据类型组合在一起产生的数据集合。
数组就是从某一个位置开始连续若干个位置形成的元素集合。
数据类型 数组名 [数组大小] 数组大小必须是整数常量
数组使用:数组名 [下标]访问
对于数组的初始化来说,对于不同的编译器具有不同的初值实现。(一般情况下默认是0,但是也可能是很大的随机数)
注意:如果数组一开始没有赋初值,数组中的每一个元素都可能是一个随机数,但不一定默认是0.
所以要是想给一个数组的初值都设置为0.可以使用int a[10] = {0}; 或 int a [10] = {}; 即可。
递推分为:顺推 逆推
重点 - 2.5.2 冒泡排序
排序算法中最基础的一种。
冒泡排序的本质在于交换。
每一次通过交换的方式将当前剩余元素的最大值移动到一端,而当剩余元素减少为0时,排序结束。
一般来说交换两个数需要中间变量。
冒泡排序中执行了n-1趟,每一趟从左到右依次比较相邻的两个数。
2.5.3 二维数组
二维数组就是一维数组的扩展。
数据类型 数组名 [第1维大小] [第2维度大小]
注意:如果数组大小是比较大(大概是:10 ^ 6级)那么需要将其定义在主函数的外面,否则会使程序异常退出。原因是:函数内部申请的局部变量来自系统栈,允许申请的空间较小。而函数外部申请的全局变量来自静态存储区,允许申请的空间较大。
2.5.4 memset 对数组中每一个元素赋相同的值
一般来说,给数组赋相同的值有两种方法:memset函数和fill函数。
Memset(数组名,值,sizeof(数组名));
使用memset函数需要在程序开头添加string.h头文件。
并且只建议初学者使用memset赋0或-1。
原因:memset使用的是按字节赋值,即对每一个字节赋同样的值,这样组成int型的4个字节就会被赋成相同的值。而由于0的二进制补码为全0,-1的二进制补码为全1。
如果要对数组赋其他的数字,那么使用fill函数。
但是memset执行的速度更快。
同时,对于二维数组或多维数组的赋值方法是一样的。
2.5.5 字符数组
字符数组的初始化:和普通数组赋值方法一样。
此外。字符数组可以通过直接赋值字符串来初始化(仅限于初始化,程序其他位置不可以直接赋值整个字符串)。
字符数组的输入输出
二维数组即若干个字符串。
scanf输出 printf输出
使用scanf对字符类型有:%c和%s两种格式。其中,%c用来输入输出单个字符,%s用来输入一个字符串并存放在字符数组中。
其中%c可以识别空格跟换行并将其输入,而%s通过空格或换行来识别一个字符串的结束。
getchar输入 putchar输出 分别用来输入和输出单个字符。
gets输入 puts 输出
gets用来输入一行字符串(注意:gets识别换行符\n作为输入结束)
puts用来输出一行字符串,即将一维数组在界面上输出并紧跟一个换行。
字符数组的存放方式:
在一维字符数组的末尾都有一个空字符\0,以表示存放的字符串的结尾。
空字符\0在使用gets或scanf输入字符串时会自动添加在输入的字符串后边,并且占用一个字符位。
而puts 或 printf就是通过识别\0作为字符串的结尾来输出的。
注意:字符数组的实际长度一定要比存储字符串的长度至少多1.
注意:int 型数组的末尾不需要加\0.只有char型数组需要。
还需要注意的是\0和空格不是一个东西。
空格的ASCII码是32。
如果使用的是gets或 getchar来获得输入的字符,那么请一定要每一个字符串后面加入一个\0 否则,printf或 puts输出字符串会因无法识别字符串末尾而输出一大堆乱码。
2.5.6 string.h 头文件
该头文件中包含了许多用于字符数组的函数。
Strlen()函数:可以得到一个字符数组中第一个\0之前的字符的个数。
Strcmp()函数:返回两个字符串大小比较的结果,比较原则是按照字典顺序。
注意:字典顺序就是字符串在字典中的顺序。
例如:”aaaa” 的字典序 < “aab”
其中该函数返回的结果如下:
如果字符数组1 < 字符数组2,那么返回一个负整数。(不一定是-1)
如果字符数组1 == 字符数组2,那么返回0;
如果字符数组1 > 字符数组2,那么返回一个正整数。(不一定是+1)
Strcpy()函数:可以把一个字符串复制给另一个字符串。
这里的复制包含了结束符\0。
Strcat()函数:可以将一个字符串接到另一个字符串后面。
2.5.7 sscanf 和 sprintf
sscanf可以理解为:string + scanf
sprintf可以理解为:string + printf
两个均在stdio.h头文件下
例子:
scanf(screen,”%d”,&n);
printf(screen,”%d”,n); (将n中的内容输出到screen上)
/**-------------------------2021.4.11 下午 13:39学习笔记----------------------------**/
其中的sscanf(str,”%d”,n)表示将Str表示的字符串以%d的格式输出到n中。(其中的str是字符数组)。
其中的sprintf(str,”%d”,n)表示将n中内容输入到str中。
使用sscanf()与sprintf()可以进行复杂输出:
2.6 函数
2.6.1 函数的定义
函数的语法格式:
返回类型 函数名称(参数类型 参数){
函数主体
}
返回类型void的含义表示空。
无参函数和有参函数
是否是有参函数根有没有返回类型无关。
c语言中使用return 返回数据。
全局变量是指定义之后的所有程序段都有效的变量(定义在所有函数之前)。
局部变量只在函数内部生效。函数结束时局部变量销毁。
实际上如果在函数参数中仅仅使用x作为参数,那么这个x只是全局变量的一个副本。同时,使用&x的话就可以使得全局变量发生改变。
上述的副本方式的传递叫做值传递,函数定义的小括号内的参数称为形式参数或形参。
而将实际调用函数的时候传递的参数叫做实际参数或实参。
函数的参数不只有一个,可以使用都好分隔开。
2.6.2 再谈main函数
主函数对一个函数来说只有一个。无论主函数写到程序段中那个位置,整个程序都是从主函数的第一个语句开始执行的。
其中return 0;表示程序正常终止。
2.6.3 以数组作为函数参数
函数的参数是数组时,数组的第一维不需要填写长度。
如果是二维数组,那么第二维需要填写长度。
实际调用时,也只需要填写数组名。
重点:数组作为参数时,在函数中对数组元素的修改就等同于对原数组元素修改。
数组不可以作返回类型出现。
2.6.4 函数的嵌套调用
2.6.5 函数的递归调用
递归是函数自己调用自己的过程。
重点:2.7 指针
2.7.1 什么是指针
在计算机中,内存中的每一块都有编号(也就是地址),计算机通过地址找到某一个变量。
变量的地址指它占用的字节中第一个字节的地址。
C语言中使用指针表示内存地址。(如果这个地址正好是某一个变量的地址,那么可以说指针指向了该变量)。
简单的理解为:指针就是变量的地址。
使用&取地址运算符可以得到变量的地址。
指针实际上是一个unsigned类型的整数。
2.7.2 指针变量
指针变量用来存放指针(或者可以理解成地址)。
可以将地址当作常量,然后存放在指针变量中。
在某种数据类型后加*号来表示这是一个指针变量。
例如:int * p;
注意:*号的位置在数据类型之后或者是变量名之前都是可以的。
重点:如果一次定义多个指针变量,如:int * p1,p2;此时,只有p1是指针变量类型的;而p2是int型的。
如果都需要定义为指针变量,那么后边的都需要加上*号。
Int *p1,*p2;
给指针变量赋值的方式是将变量的地址取出来,然后赋给对应类型的指针变量。
Int *p = &a;
需要注意的是:int * p = &a;中的int * 是指针变量的类型,后边的p才是变量名,用来存放地址。
重点:星号*是类型的一部分!
重点:通过指针变量来获得实际对应的元素,需要使用*p来得到。(其中p是int*型的指针变量)。
使用*p = newVal;表示改变了p指向的地址中存放的数据。
指针变量可以进行加减法,其中减法的结果是两个地址偏移的距离。
对于 int * p = &a;来说,p + 1 表示p所指的int型变量的下一个int型变量地址。
这表示跨越了一整个int型。
指针变量支持自增和自减操作。
对于指针变量来说,将其存储的地址的类型称为基类型。L例如:int * p = 100;
这里的int就是基类型。
基类型必须和指针变量存储的地址类型相同。
2.7.3 指针与数组
由于数组中的元素在地址上是连续的。
可以在数组名前边加&来获得其地址。
数组a的首地址就是&a[0];
C语言中,数组名称也作为数组的首地址使用。
a == &a[0];是成立的。
输出a[0]就是输出*p;(其有:int * p = &a[0]; )
其中的a + i等同于&a[i]; 也就是有:*(a + i);和a[i]等价。
所以输入数组元素可以使用:scanf(“%d”,a + i); (其中a 是数组的第一个元素的地址)
两个指针变量之间的差值是两个地址之间的距离,这个距离当以int为单位的时候,由于一个int类型的占用4byte那么这样的话20 / 4 = 5;最后返回的是5。
通俗来说,就是两个int型的指针相减,就相当于再求两个地址之间相差了几个Int。
2.7.4 使用指针变量作为函数参数
可以看为将变量的地址传入函数。
如果在函数中对这个地址中的元素进行改变,原先的数据就会确实地被改变。
将指针变量作为参数传给函数是地址传递。
2.7.5 引用(C++中的语法)
引用的含义
引用不产生副本,而是给原变量起一个别名。
对引用变量的操作就是对原变量的操作。
引用的使用方法很简单,是需要在函数的参数类型后边加上&即可。
例如:void change(int& x){};
此时对于传入的参数的修改就会导致对原变量的修改。
不管是否使用引用,函数的参数名和实际传入的参数名可以不同。
将引用&和取地址&符号需要分开来。
指针的引用
使用引用之后就可以函数中交换两个指针变量的地址来实现元素的交换了。
例如:void swap(int * &p1,int * &p2){
Int * temp = p1;
P1 = p2;
P2 = temp;
}
引用是产生变量的别名,因此常量不可以使用引用。
2.8 结构体(struct)的使用
2.8.1 结构体的定义
Struct Name {
//一些基本的数据结构或者是自定义的数据类型
}xxxx1,xxxx2;
其中的xxxx1和 xxxx2代表两个结构体变量。
定义结构体变量也可以按照基本数据类型定义:
StudentInfoAlice;
StudentInfostu[1000];
结构体中可以定义自己本身之外的任何数据类型,但是可以定义自身类型的指针变量。
Struct node {
Node * next;
};
2.8.2 访问结构体内的元素
访问的两种方法:
.操作
->操作
例如:stu.id stu.name stu.next
访问指针变量p中的元素的写法如下:
(*p).id (*p).name (*p).next
也可以使用p -> id p -> name p -> next来访问元素
2.8.3 结构体的初始化
读入时进行赋值:
Scanf(“%d”,&stu.id);
还可以使用构造函数的方法进行初始化。构造函数就是用来初始化结构体的一种函数。
直接定义在结构体中。
构造函数的特点是:不需要写返回类型,且函数名与结构体名相同。
在函数中定义有参构造函数时,一定注意参数不要和已经有的变量名重名。
构造函数可以简化成一行:
StudentInfo(int _id,char _gender) : id(_id), gender (_gender){}
如果自己重新定义了构造函数,那么必须初始化定义结构体变量。
也就是说默认生成的构造函数被覆盖了。
重点:只要参数类型和个数不完全相同就可以定义任意多个构造函数。
以适应不同的初始化场合。
构造函数在结构体内的变量较多时方便使用,可以不需要临时变量就初始化一个结构体。
2.9 补充
2.9.1 cin与cout
Cin 和 cout 是c++中的输入和输出函数。
需要添加头文件#include
cin
Cin是c 和in的合成词。
采用输入 >>来进行输入。
cin不需要指定格式 不需要加取地址运算符& 直接写变量名就可以了。
如果想要读入一整行,则需要使用getline()函数。
Cin.getline(str,100); //表示将一整行读入到str数组中。
如果是string容器,那么需要试用下面的方式输入:
String str;
Getline(cin,str);
cout
其是c 和 cout 合成词。
如果想要控制double的精度,例如输出小数点后两位,那么需要在输出之前加上一些东西:
并且需要加上#include
Cout << setiosflags(ios:fixed) << setprecision(2) << 123.4567 << endl;
但是在考试的时候不建议使用cin 和 cout 进行输入和输出,会超时。
2.9.2 浮点数的比较
C/C++中的==是完全相同的时候才能判定为true;
于是需要引入一个极小数eps来对这种误差进行修正。
等于运算符 ==:
如果a落在了[b-eps,b+eps]之间就可以判定a和 b是相等的。
并且eps根据经验取10 ^ -8是一个合适的数字。所以,可以将eps 定义为:1e-8
使用==表示忽略的精度的损失,有的时候会导致返回false。这个时候使用定义的eq宏定义比较好。(可以有一个eps误差在内)
大于运算符
如果一个数A要大于B,那么就必须在误差eps的扰动范围之外大于B。
因此只有大于B+eps的数才能判定大于B。
小于运算符
如果一个数A要小于B,那么就必须在误差eps的扰动范围之外小于B。
因此只有小于B-eps的数才能判定大于B。
大于等于运算符
大于b-eps的数都应当判定为大于等于b
小于等于运算符
小于b+eps的数都应当判定为小于等于b
圆周率Π
Π写成常量acos(-1.0)即可。
Const double PI = acos(-1.0);
上述的运算符号包含的代码如下:
需要注意的是:
2.9.3 复杂度
一般来说,复杂度可以分为:时间和空间复杂度,有时,还会有编码复杂度。
时间复杂度
简单地说,时间复杂度是算法需要执行基本运算的次数所处的等级。
对于一个for循环来说:
For(int i = 0;i < n;i ++){ sum = sum + a[i]}
上述for循环共执行了n次加法代码。显然,n次和2n次基本运算当n的规模增大时的增长趋势是相同的(都是线性增长),于是将O(n)称作上面两端代码的时间复杂度,表示两段代码消耗的时间随着n的增长而线性增长。
另外例子:
For (){
FOR(){}
}
这样来说基本运算次数为n ^ 2,所以时间复杂度是O(n ^ 2);
在时间复杂度中,高等级的幂次会覆盖低等级的幂次。所以,如果有一个算法时间复杂度是O(3 * n ^ 2 + n + 2) = O(3n ^ 2) = O(n ^ 2);
其中,O(c * n ^ 2)中的c称为时间复杂度的常数。
有的时候不同算法之间的C常数差距较大,即便时间复杂度相同,算法之间的性能也会有较大差距。
二分查找的时间复杂度是O(log n); 对数复杂度书写时一般省略底数。
常数复杂度O(1)则表示算法消耗的时间不随规模的增长而增长,显然有O(1) < O(log n) < O(n) < O(n ^ 2) 成立。
在做题时只需要大概估计一下复杂度即可。例如:n =1000,那么对于O(n ^ 2)的复杂度来说10 ^ 6 级别。
一般可以承受的运算次数是:10 ^ 7 - 10 ^ 8 级别。
空间复杂度
表示算法需要消耗的最大数据空间。
从重要性来说空间没有时间复杂度那么大。
/**-------------------------2021.4.12号 下午15:12学习笔记--------------------------**/
O(1)的空间复杂度表示消耗的空间不随着数据规模的增大而增大。
常常采用空间换时间的方法。
编码复杂度
是一个定性的概念。
对于一个问题来说,如果使用了比较冗长的算法思想,那么代码量将会非常巨大。
2.10 黑盒测试
准备好很多测试用例,然后执行程序去测试这些数据,看输出结果和正确答案是否完全相同(字符串上的比较),如果完全相同那么表示通过了这次的黑盒测试。
此外,黑盒测试根据是否对每一组数据都单独测试或是一次性测试所有测试数据,可以分为单点测试和多点测试。
2.10.1 单点测试
系统会判断每一组测试数据是否正确。
PAT采用的就是这种单点测试。
2.10.2 多点测试
多点测试要求程序可以一次运行完所有数据,并要求所有的输出结果都正确,才可以算作完全正确。
三种不同的方法执行代码的核心部分:
(1)While ....EOF 型:
表示如果没有给定输入的结束条件,那么就默认读取到文件的末尾。
Scanf()如果成功读到了一个数,那么返回的是1;而如果读入了两个数据,那么scanf()返回的是2。
读入失败的现象发生在文件的末尾比较多,这个时候scanf()函数返回的是-1而不是0。(C语言中的EOF代表-1)
启发:
当题目中没有说读入多少数据时,需要有以下接受输入代码:
While(scanf(“%d”,&n) != EOF) {
...
}
上述代码的含义是:在只要scanf()函数的返回值不是EOF,那么就继续反复读入n。
此外,在黑框里输入数据时,并不会出发EOF状态。(如果想在黑框里触发EOF,可以使用ctrl + z键)这个时候就会显示一个^z,这个时候按enter键即可。
其中上述使用Scanf()函数还可以写成gets(str) !=NULL
(2)while ... break型
当题目要求输入的数据满足某一个条件时停止输入,可以使用本方法。
这种写法的break的条件是在while循环内部进行判断的。
另一种更简洁的写法是:将退出条件的判断放到while语句中,令其与scanf用逗号隔开。
While(scanf(“%d”,&n) != EOF, a|| b) {}
上述的判断条件的全写是:a != 0 || b != 0
题目会给出测试数据得组数,然后才给出相应数量的输入数据。
使用变量T来存储循环得次数。
上述就是多点测试的输入类型,下面是三种常见的输出类型:
这种输出没有额外的空行
只需要在每一组输出之后加上一个换行符号\n即可。
一般在判断T是否减少到0来判断当前输出是否应当输出额外的换行。
重点:在多点测试中,每一次循环都要重置一下变量和数组,否则在下一组数据来临的时候变量和数组的状态就不是初始状态了。
而重置数组一般使用memset 函数 或者 fill函数。
模拟题是题目怎么说你就怎么做的题目。
完全只是根据题目的描述来进行代码的编写,所以考察的是代码能力。
例1:害死人不偿命的(3 * n + 1)猜想。
例2:挖掘机技术哪家强
思路:
设置一个数组用来保存每一个学校的总分。初值都设置为0。
设置变量k 来记录学校的编号;设置MAX来记录最高总分。(初始值都设置为-1)
解决A+B题感悟:
使用char可以利用数字和字母之间的ascii码之间的关系进行换算。
使用while循环的时候如果是一直接受输入那么使用:while(scanf() != EOF){} 或者是 while(scanf() != -1) {}即可。
如果范围比较小直接遍历即可。
如果数据范围比较大那么可以使用二分查找。
3.3 图形输出
做法:
例2:跟奥巴马一起编程
思路:
当列数是奇数的时候行数是column / 2 + 1;
当列数是偶数的时候行数是column / 2;
重点:整数除以2进行四舍五入的操作可以通过判断它是否是奇数来解决,以避免浮点数的介入而导致的精度达不到要求。(这个是重要的思想)
3.4 日期处理
由于需要处理平年和闰年(2月的天数区别),大月和小月的问题。
细节需要认真处理。
思路:
不妨假设第一个日期早于第二个日期。(否则交换即可)
很直接的思路:将第一个日期一直加一天,直到等于第二个日期为止。
当天数增加了一天之后天数超过了本月的最大天数,那么将月份加1,然后令天数设置为1。
同理,如果月份超过12那么将月份置为1月,然后年数增加1,然后天数设置为1。
可以设置一个二维数组表示月份的天数,第一维是月份,第二维中的0下标是平年,1下标是闰年。
注意:还可以直接将第一个日期的年份加到第二个日期的年份的上一年。
然后根据其中的平年还是闰年来增加对应的天数(平年:365天;闰年:366天)。
打印日期已经练习
//截止到2021.4.12号晚上 20:07