算法竞赛入门经典授课教案第3章数组和字符串精心排版并扩充部分内容.docx

上传人:b****7 文档编号:9734148 上传时间:2023-02-06 格式:DOCX 页数:53 大小:41.19KB
下载 相关 举报
算法竞赛入门经典授课教案第3章数组和字符串精心排版并扩充部分内容.docx_第1页
第1页 / 共53页
算法竞赛入门经典授课教案第3章数组和字符串精心排版并扩充部分内容.docx_第2页
第2页 / 共53页
算法竞赛入门经典授课教案第3章数组和字符串精心排版并扩充部分内容.docx_第3页
第3页 / 共53页
算法竞赛入门经典授课教案第3章数组和字符串精心排版并扩充部分内容.docx_第4页
第4页 / 共53页
算法竞赛入门经典授课教案第3章数组和字符串精心排版并扩充部分内容.docx_第5页
第5页 / 共53页
点击查看更多>>
下载资源
资源描述

算法竞赛入门经典授课教案第3章数组和字符串精心排版并扩充部分内容.docx

《算法竞赛入门经典授课教案第3章数组和字符串精心排版并扩充部分内容.docx》由会员分享,可在线阅读,更多相关《算法竞赛入门经典授课教案第3章数组和字符串精心排版并扩充部分内容.docx(53页珍藏版)》请在冰豆网上搜索。

算法竞赛入门经典授课教案第3章数组和字符串精心排版并扩充部分内容.docx

算法竞赛入门经典授课教案第3章数组和字符串精心排版并扩充部分内容

第3章数组和字符串

【教学内容相关章节】

3.1数组3.2字符数组3.3最长回文子串

3.4小结与习题

【教学目标】

(1)掌握一维数组的声明和使用方法;

(2)掌握二维数组的声明和使用方法;

(3)掌握字符串的声明、赋值、比较和连接方法;

(4)熟悉字符的ASCII码和ctype.h;

(5)正确认识++、+=等能修改变量的运算符;

(6)学会编译选项-Wall获得更多的警告信息;

(7)掌握fgetc和getchar的使用方法;

(8)了解不同操作系统中换行符的表示方法;

(9)掌握fgets的使用方法并了解gets的“缓冲区溢出”的漏洞;

(10)理解预处理和迭代开发的技巧。

【教学要求】

(1)掌握一维数组和二维数组的声明和使用方法;

(2)掌握字符串的声明、相关的操作;

(3)掌握fgetc和getchar的使用方法;

(3)掌握fgets和gets的使用方法。

【教学内容提要】

通过对第2章的学习,了解了计算机的计算优势,但没有发挥出计算机的存储优势——只用了屈指可数的变量。

尽管有的程序也处理了大量的数据,但这些数据都只是“过客”,只参与了计算、并没有被保存下来。

本章介绍数组和字符串,二者都能保存大量的数据。

字符串是一种数组(字符数组),但由于其应用的特殊性,适用一些特别的处理方式。

【教学重点、难点】

教学重点:

(1)掌握一维数组和二维数组的声明和使用方法;

(2)掌握字符串的声明、相关的操作;

(3)掌握fgetc和getchar的使用方法;

(3)掌握fgets和gets的使用方法。

教学难点:

(1)掌握一维数组和二维数组的声明和使用方法;

(2)掌握字符串的声明、相关的操作及字符串处理函数的使用。

【课时安排(共5学时)】

3.1数组3.2字符数组3.3最长回文子串

3.4小结与习题

 

3.1数组

下面从一个问题出发,说明一下为何使用数组。

问题:

读入一些整数,逆序输出到一行中,已知整数不超过100个。

【分析】

首先通过循环来读取100个整数的输入,然后把每个数都存下来,存放在数组中,最后输出。

程序3-1逆序输出

#include

#defineMAXN100+10

inta[MAXN];

intmain(){

inti,x,n=0;

while(scanf("%d",&x)==1)

a[n++]=x;

for(i=n-1;i>=1;i--)

printf("%d",a[i]);

printf("%d\n",a[0]);

return0;

}

说明:

语句inta[100]声明了一个包含100个整型变量的数组,它们是:

a[0],a[1],a[2],…,a[99]。

注意,没有a[100]。

提示3-1:

语句inta[MAXN]声明了一个包含MAXN个整型变量的数组,即a[0],a[1],a[2],…,a[MAXN-1],但不包含a[MAXN]。

MAXN必须是常数,不能是变量。

在本程序中,声明MAXN为100+10而不是100,主要是为了保险起见。

提示3-2:

在算法竞赛中,常常难以精确计算出需要的数组大小,数组一般会声明得稍大些。

在空间够用的前提下,浪费一点不要紧。

语句a[n++]=x;的等价于两条语句a[n]=x;n++;,循环结束后,数据被存在了a[0],a[1],…,a[n-1],其中变量n个整数的个数。

在数组中存放整数后,依次输出a[n-1],a[n],…,a[1]和a[0]。

一般要求输出的行首行尾均无空格,相邻两个数据间用单个空格隔开,一共需要输出n个整数,但只有n-1个空格,所以只好分两条语句输出。

也可以采用如下程序:

for(i=n-1;i>=0;i--)

{

if(n!

=0)

printf("%d",a[i]);

else

printf("%d\n",a[0]);

}

在上述程序中,数组a被声明在main函数的外面。

简单地说,只有在放外面时,数组a才可以开得很大;放在main函数内时,数组稍大就会异常退出。

分别运行一下以下两个程序,观察运行结果:

#include

#defineMAXN10000000

inta[MAXN];

intmain()

{

return0;

}

#include

#defineMAXN10000000

intmain()

{

inta[MAXN];

return0;

}

提示3-3:

比较大的数组应尽量声明在main函数外。

C语言数组中使用过程中,有一些特殊的情况。

例如,数组不能够进行赋值操作,即不能将一个整体赋值给另外一个数组。

补充:

memcpy函数原型如下:

void*memcpy(void*dest,void*src,unsignedintcount);

功能:

从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中。

,使用memcpy函数要包含头文件string.h。

示例1:

从数组a复制到k个元素到数组b:

memcpy(b,a,sizeof(int)*k);

示例2:

若数组a和b都是浮点型的,复制时要写成:

memcpy(b,a,sizeof(double)*k);

示例3:

如果需要把数组a全部复制到数组b中,可以写成:

memcpy(b,a,sizeof(a));

例3-1开灯问题。

有n盏灯,编号为1~n,第1个人把所有灯打开,第2个人按下所有编号为2的倍数的开关(这些灯将被关掉),第3个人按下所有编号为3的倍数的开关(其中关掉的灯被打开,开着灯将被关闭),依此类推。

一共有k个人,问最后有哪些灯开着?

输入:

n和k,输出开着的灯编号。

k≤n≤1000。

样例输入:

73

样例输出:

1567

【分析】

用a[1],a[2],…,a[n]表示编号为1,2,3,…,n的灯是否开着,模拟这些操作即可。

代码如下:

程序3-2开灯问题逆序输出

#include

#include

#defineMAXN1000+10

inta[MAXN];/*一维数组存储n盏电灯*/

intmain()

{

inti,j,n,k,first=1;

memset(a,0,sizeof(a));/*初始化电灯状态*/

scanf("%d%d",&n,&k);

/*模拟活动过程*/

for(i=1;i<=k;i++)/*第i个人参与活动*/

{

for(j=1;j<=n;j++)/*对第i盏灯进行操作*/

if(j%i==0)a[j]=!

a[j];

}

/*输出活动结果*/

for(i=1;i<=n;i++)

{

if(a[i])

{

if(first)first=0;

elseprintf("");

printf("%d",i);

}

}

printf("\n");

return0;

}

说明:

(1)memset(a,0,sizeof(a))的作用是把数组a清零,它也在string.h中定义。

使用memset比for循环更方便、快捷。

补充:

memset函数原型如下:

void*memset(void*buffer,charc,intcount);

功能:

把buffer所指内存区域的前count个字节设置成字符c,第三个参数指的是字节的个数,返回指向buffer的指针,在程序中需包含头文件:

#include

由memset函数的头文件可以知道,其主要是对字符数组进行设置,当然也可以对数组进行初始赋值(一般是0,-1)。

(2)有一个技巧是在输出:

为了避免输出多余空格,设置了一个标志变量first,可以表示当前要输出的变量是否为第一个。

第一个变量前不应有空格,但其他都有。

例3-2蛇形填数。

在n*n方阵里填入1,2,…,n*n,要求填成蛇形。

例如n=4时方阵为

1011121

916132

815143

7654

上面的方阵中,多余的空格只是为了便于观察规律,不必严格输出。

n≤8。

【分析】

与数学的矩阵相比,可以用一个所谓的二维数组来存储题目中的方阵。

只需声明一个inta[MAXN][MAXN],就可以获得一个大小为MAXN×MAXN的方阵。

在声明时,两维的大小不必相同。

提示3-4:

用inta[MAXN][MAXM]生成一个整型的二维数组,其中MAXN和MAXM不必相等。

这个数组共有MAXN×MAXM个元素,分别为

a[0][0],a[0][1],…,a[0][MAXM-1],

a[1][0],a[1][1],…,a[1][MAXM-1],

…,

a[MAXN-1][0],a[MAXN-1][1],…,a[MAXN-1][MAXM-1]。

假设从1开始依次填这写。

设“笔”的坐标为(x,y),则一开始x=0,y=n-1,即第0行第n-1列(注意行列的范围是0~n-1,没有第n列)。

“笔”的移动轨迹是:

下、下、下、左、左、左、上、上、上、右、右、下、下、左、上。

总之,先是下,到不能填了为止,然后是左,接着是上,最后是右。

“不能填”是指再走就出界(例如4→5)或者再走就要走到以前填过的格子(例如12→13)。

如果把所有格式初始化为0,就能很方便地加以判断。

程序3-3蛇形填数

#include

#include

#defineMAXN10

inta[MAXN][MAXN];

intmain(){

intn,x,y,tot=0;

scanf("%d",&n);

memset(a,0,sizeof(a));

tot=a[x=0][y=n-1]=1;

while(tot

while(x+1

a[x+1][y])a[++x][y]=++tot;/*向下走*/

while(y-1>=0&&!

a[x][y-1])a[x][--y]=++tot;/*向左走*/

while(x-1>=0&&!

a[x-1][y])a[--x][y]=++tot;/*向上走*/

while(y+1

a[x][y+1])a[x][++y]=++tot;/*向右走*/

}

for(x=0;x

for(y=0;y

printf("\n");

}

return0;

}

说明:

本程序利用了C语言的简洁的优势。

首先,赋值x=0和y=n-1后马上作为a数组的下标,可以合并完成;tot和a[0][n-1]都要赋值1,也可以合并完成。

用一条语句可以完成多件事情,并且不有牺牲程序的可读性。

提示3-5:

可以利用C语言简洁的语法,但前提是保持代码的可读性。

提示3-6:

在很多情况下,最好是在做一件事之前检查是不是可以做,而不要做完再后悔。

因为“悔棋”往往也比较麻烦。

说明:

本程序对于与x+1

如果x+1

a[x+1][y],也就不会越界了。

 

3.2字符数组

文本处理在计算机应用中占有重要地位。

在C语言中,字符串其实就是字符数组——可以像处理普通数组一样处理字符串,只需要注意输入输出和字符串函数的使用。

例3-3竖式问题。

找出所有形如abc*de(三位数乘以两位数)的算式,使得在完整的竖式中,所有数字都属于一个特定的数字集合。

输入数字集合(相邻数字之间没有空格),输出所有竖式。

每个竖式前应有编号,之后应有一个空行。

最后输出解的总数。

具体格式见样例输出(为了便于观察,竖式中的空格改用小数点显示,但你的程序应该输出空格,而非小数点)。

样例输入:

2357

样例输出:

<1>

..775

x..33

-----

.2325

2325.

-----

25575

Thenumberofsolutions=1

【分析】

本题的解题策略是尝试所有的abc和de,判断是否满足条件。

写出如下的伪代码:

chars[20];

intcount=0;

scanf("%s",s);

for(abc=111;abc<=999;abc++)

for(de=11;de<=99;de++)

if(“abc*de”是个合法的竖式){

printf("<%d>\n",++count);

打印abc*de的竖式和其后的空行

count++;

}

printf("Thenumberofsolutions=%d\n",count);

说明:

(1)chars[20]是一个定义字符数组的语句,scanf("%s",s)表示从键盘输入一个字符串给字符数组s。

(2)char是“字符型”的意思,而字符是一种特殊的整数。

每一个字符都有一个整数编码,称为ASCII码。

C语言中允许用直接的方法表示字符,还有以反斜线开头的字符(转义序列,EscapeSequence)。

(3)在stdlib.h中有一个函数atoi,它的函数原型如下:

intatoi(char*s)

它表示将字符串s中的内容转换成一个整型数返回,如字符串“1234”,则函数返回值是1234。

(4)在stdlib.h中有一个函数itoa,它的函数原型如下:

char*itoa(intvalue,char*string,intradix)

它表示将整数value转换成字符串存入string,radix为转换时所用基数(保存到字符串中的数据的进制基数281016),返回指向转换后的字符串的指针 。

例如,itoa(32,string,10)是将32变成十进制数一个字符串“32”,并返回指向这个字符串的指针;itoa(32,string,16)是将32变成十进制数一个字符串“20”,并返回指向这个字符串的指针。

(5)stdlib头文件即standardlibrary标准库头文件,该文件包含了的C语言标准库函数的定义,声明了数值与字符串转换函数,伪随机数生成函数,动态内存分配函数,进程控制函数等公共函数。

输入样式:

C语言模式:

#include

C++样式:

#include

提示3-7:

C语言中的字符型用char表示,它实际存储的是字符的ASCII码。

字符常量可以用单引号法表示。

在语法上可以把字符当作int型使用。

语句scanf("%s",s);表示读入一个不含空格、TAB和回车符的字符串,存入字符数组s中,s前面没有&符号。

提示3-8:

在scanf("%s",s)中,不要在s前面加上&符号。

如果是字符数组chars[MAXN][MAXL],可以用scanf("%s",s[i])读取第i个字符串。

接下来有两个问题:

判断和输出。

先考虑输出。

首先计算第一行乘积x=abc*e,然后是第二行y=abc*d,最后是总乘积z=abc*de,然后一次性打印出来:

printf("%5d\nX%4d\n-----\n%5d\n%4d\n-----\n%5d\n\n",abc,de,x,y,z);

完整程序如下:

程序3-4竖式问题

#include

#include

intmain()

{

inti,ok,abc,de,x,y,z,count=0;

chars[20],buf[99];

scanf("%s",s);

for(abc=111;abc<=999;abc++)

for(de=11;de<=99;de++)

{

x=abc*(de%10);

y=abc*(de/10);

z=abc*de;

sprintf(buf,"%d%d%d%d%d",abc,de,x,y,z);

ok=1;

for(i=0;i

if(strchr(s,buf[i])==NULL)

ok=0;

if(ok)

{

printf("<%d>\n",++count);

printf("%5d\nX%4d\n-----\n%5d\n%4d\n-----\n%5d\n\n",abc,de,x,y,z);

}

}

printf("Thenumberofsolutions=%d\n",count);

return0;

}

说明:

(1)sprintf函数

sprintf是个变参函数,定义如下:

intsprintf(char*buffer,constchar*format[,argument]...);

除了前两个参数类型固定外,后面可以接任意多个参数。

功能:

把格式化的数据写入字符串buf,它的返回值是字符串长度。

包含此函数的头文件是stdio.h。

例如,本程序中的sprintf(buf,"%d%d%d%d%d",abc,de,x,y,z);语句的功能是将整数abcdexyx打印成字符串存储在串buff中。

可以直接接受其返回值:

intlen=sprintf(buf,"%d%d%d%d%d",abc,de,x,y,z);

(2)strchr函数

strchr函数定义如下:

char*strchr(constchar*s,charc);

功能:

查找字符串s中首次出现字符c的位置。

它的返回值是返回首次出现c的位置的指针,如果s中不存在c则返回NULL。

包含此函数的头文件是string.h。

例如,本程序的if语句中strchr(s,buf[i])的功能是查找字符串s中首次出字符buf[i]的位置。

如果strchr(s,buf[i])==NULL,则表明字符串s中没有buf[i]的字符。

(3)sprintf函数、printf函数、fprintf函数的区别

printf输出到屏幕,fprintf输出到文件,而sprintf输出到字符串。

需要注意是应该保证写入的字符串有足够的空间。

提示3-9:

可以用sprintf把信息输出到字符串,用法和printf、fprintf类似。

但你应当保证字符串足够大,可以容纳输出信息。

字符串的空间应为字符个数加1,这是因为C语言的字符串是以空字符'\0'结尾的。

函数strlen(s)的作用是获取字符串s的实际长度,即函数strlen(s)返回的是结束标记之前的字符个数(需包含头文件string.h)。

因此这个字符串中的各个字符依次是s[0],s[1],…,s[strlen(s)-1],而s[strlen(s)]正是结束标记'\0'。

提示3-10:

C语言中的字符串是'\0'结尾的字符数组,可以用strlen(s)返回字符串s中结束标记之前的字符个数。

字符串中的各个字符是:

s[0],s[1],…,s[strlen(s)-1]。

提示3-11:

由于字符串的本质是数组,它也不是“一等公民”,只能用strcpy(a,b)、strcmp(a,b)、strcat(a,b)来执行“赋值”、“比较”和“连接”操作。

而不能用=、==、<=、+等运算符。

上述函数都在string.h中声明。

除了字符串之外,还要注意++count和count++的用法。

++count本身的值是加1以后的,但count++的值是加1之前的(原来的值)。

注意:

滥用++count和count++会带来很多隐蔽的错误,所以最好的方法是避开它们。

提示3-12:

滥用++、--、+=等可以修改变量值的运算符很容易带来隐蔽的错误。

建议每条语句最多只用一次这种运算符,并且它所修改的变量在整条语句中只出现一次。

提示3-13:

但编译选项-Wall编译程序时,会给出很多(但不是所有)警告信息,以帮助程序员查错。

但并不能解决所有的问题:

有些“错误”程序是合法的,只是这些动作不是你所期望的。

3.3最长回文子串

例3-4回文串。

输入一个字符串,求出其中最长的回文子串。

子串的含义是:

在原串中连续出现的字符串片段。

回文的含义是:

正着看和倒着看相同。

如abba和yyxyy。

在判断时,应该忽略所有标点符号和空格,且忽略大小写,但输出应保持原样(在回文串的首部和尾部不要输出多余字符)。

输入字符串长度不超过5000,且占据单独的一行。

应该输出最长的回文串,如果有多个,输出起始位置最靠左的。

样例输入:

Confuciusssay:

Madam,I'mAdam.

样例输出:

Madam,I'mAdam

【分析】

由于输入的字符比较复杂,首先,不能用scanf("%s")输入字符串,可用下述两种方法解决下列问题:

第1种方法是使用fgetc(fin),它读取一个打开的文件fin,读取一个字符,然后返回一个int值。

因为如果文件结束,fgetc将返回一个特殊标记EOF,它并不是一个char。

如果要从标准输入读取一个字符,可以用getchar(),它等价于fgetc(stdin)。

补充:

fgetc()介绍

功能:

从流中读取字符。

用法:

intfgetc(FILE*stream);

意为从文件指针stream指向的文件中读取一个字符,读取一个字节后,光标位置后移一个字节。

这个函数的返回值,是返回所读取的一个字节。

如果读到文件末尾或者读取出错时返回EOF。

提示3-14:

使用fgetc(fin)可以从打开的文件fin中读取一个字符。

一般情况下应当在检查它不是EOF后再将其转换成char值。

从标准输入读取一个字符可以用getchar(),它等价于fgetc(stdin)。

fgetc()和getchar()将读取“下一个字符”,如果是空格,会正常读取;若是换行,读取到的将是回车符'\n'。

补充:

getchar()介绍

功能:

从stdio流中读字符

用法:

intgetchar(void);

getchar函数的返回值是用户输入的第一个字符的ASCII码,如出错返回-1。

可以利用getchar()函数让程序调试运行结束后等待编程者按下键盘才返回编辑界面,用法:

在主函数结尾,return0;之前加上getchar();

#include

intmain(void)

{

intc;

while((c=getchar())!

=-1)

//while((c=getchar())!

=EOF)

printf("%c",c);

return0;

}

#include

intmain(void)

{

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 总结汇报 > 学习总结

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1