版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第6章数组6.1数组的概念6.2一维数组6.3一维字符数组6.4二维数组6.5多维数组习题66.1数组的概念在实际问题中,常常需要处理大量的有相同性质的数据,比如要登记一个年级250名学生的期末成绩并求平均值,我们不可能定义250个变量来一一地进行赋值,再把250个变量相加来求平均值。解决这个问题的最好办法是使用数组。数组是一种数据类型,它可以把多个相同类型的数据组合在一起,用一个变量名表示,这个变量名称为数组名,如学生数组stud。各个具体的数据用数组名加下标来表示,比如第5号学生的成绩为stud[5]。数组的设置为我们提供了极大的方便。构成数组的单元称为数组元素,数组元素的序号称为下标。C语言中数组的下标从0开始计数,最大下标比数组元素个数少1。比如数组a有5个整数元素,a[0]是它的第0号元素(第1个元素),a[4]是它的第4号元素(第5个元素)。数组a在内存中的形式如图6-1所示。数组元素的序号用方括号括起来。方括号又被称为下标运算符,具有最高的优先级。下标必须是一个整数或一个整型表达式,如i=1,j=2,则a[i+j]就表示a[3]。带了下标的数组名在这里就相当于该类型的一个变量,因此可以作为赋值语句的左值,例如:
a[4]+=3;图6-1数组在内存中的形式6.2一维数组6.2.1一维数组变量的定义只有在声明了数组元素的类型和个数之后,编译器才能为该数组分配合适的内存,这种声明就是数组的定义。对一维数组来说,其定义的一般形式为:
〈类型标识符〉〈数组名〉[〈整型常量表达式〉]其中,类型标识符指数组元素的类型;数组名是个标识符,是数组类型变量;整型常量表达式表示该数组的大小,应该大于0。例如语句#defineM20inta[10];floatb[5];charch[M+6];定义a是有10个整型元素的数组名,b是有5个浮点型元素的数组名,ch是有M+6即26个元素的字符型数组变量名。一个数组中的元素在内存中是连续存放的,数组名代表了这个数组第1个元素的地址,如a也代表了&a[0],b也代表了&b[0]。定义数组变量时最常见的错误有两种:
(1)将下标运算符写成圆括号,如:
inta(10);
(2)将数组变量定义成动态数组,即数组的大小依赖于程序运行时变量的取值,如:intn;scanf(″%d″,&n);inta[n];6.2.2一维数组元素的引用引用数组元素的一般形式是:
〈数组名〉[〈整型表达式〉]例如,对上节定义的数组a,可以用a[2]、a[i+1]、a[i+j]等来表示a的某个元素(其中i、j为已取值的整型变量)。要注意整型表达式的取值范围:
0≤〈整型表达式〉≤元素个数-1通常情况下常用for结构来操作数组,其一般格式是:
for(i=0;i<=数组大小-1;i++){…a[i]…}或
for(i=数组大小-1;i>=0;i--){…a[i]…}以此来顺序或逆序地遍历一维数组中的所有元素。
【例6-1】给数组赋值并以反序输出。#include<stdio.h>main(){inti,a[10]
for(i=0;i<=9;i++)a[i]=1+2*i;for(i=9;i>=0;i--)printf(″%d″,a[i]);return0;}运行输出:
191715131197531程序定义整型数组a有10个元素,通过for循环对数组元素赋以连续的奇数值:1,3,5,…,19,然后以反序输出。在第一个for循环头的第二个表达式中写上数组的最大下标可以预防“丢一错误”,即最好写成i<=9而不要写成i<10或i<9这种形式。6.2.3一维数组变量的初始化上面程序中对数组元素的赋值是通过循环语句实现的,这要占用运行时间。事实上,还可以在定义一个数组变量的同时就给它赋值,这称为数组的初始化。数组初始化有以下两种情况:
(1)对全部数组元素初始化,如:
inta[5]={1,2,3,4,5};是将初始化的值放入花括号中并以逗号分开,按它们出现的顺序分别赋给数组的第0号、第1号、…、第i号元素。这相当于做以下的赋值:
a[0]=1;a[1]=2;a[2]=3;a[3]=4;a[4]=5;可见用初始化的方法可以提高赋值效率,而且这是在程序编译时进行的,不占用系统的运行时间。
(2)只给部分数组元素赋初值。这又可分为两种情况:①如果只给数组的前半部分元素赋初值,可连续写出初值,如:
inta[5]={1,2};其作用是把1赋给a[0],把2赋给a[1],而对后面的元素全部自动赋以初值0,即a[2]=a[3]=a[4]=0;②如果只给数组的后半部分元素或某些不连续的元素赋初值,则花括号中分隔数值的逗号不能缺少,即把要赋的值写入适当的地方,而不予赋值的地方写为0(对数值型数组),如: inta[5]={0,3,0,7,9};则对数组元素a[1]、a[3]、a[4]赋了初值3、7、9,而a[0]、a[2]元素值均为0。数组初始化时的常见错误有以下两种:
·在实际数值之间留有空位,如:
inta[5]={1,,3,,5};是错误的,即在1和3之间、3和5之间必须有具体数值,即使是0也必须写上。
·初始值的个数大于元素的个数,如:
inta[5]={1,2,3,4,5,6};也是错误的,因为数组只有5个元素,而提供了6个初值,这会造成编译错误。为了避免出现这种个数不相符的错误,可以在初始化时不指定数组的大小,而以实际提供的初值个数自动确定数组的大小。如:
inta[]={1,3,5,0,9,11};因提供了6个数值,所以a的大小即为6,最大下标的元素为a[5]。6.2.4一维数组的应用数组是一种非常有用的数据结构,当要处理同一种类型的多个数据时,首先就要想到利用数组,而不是去定义多个同一类型的变量。利用数组求解问题的一般步骤如下:
(1)定义数组。根据所要处理的数据的类型和多少,定义合适的数组,编译系统会根据定义分配适当的内存空间。
(2)输入数组。数组定义之后,尚未有确定的内容,必须根据问题用一种合适的输入方式使数组中的每个元素具有所需要的值。
(3)处理数组。对已经具有数值的数组按照任务的要求进行处理。
(4)输出数组。以上几步对数组的处理都是在内存中进行的,外界看不到,要想查看数组中的内容,必须以适当的形式输出。比如,为清晰整齐起见,每行输出确定个数的数据;对于二维数组,按行进行输出等,这就需要进行适当的控制。在实际问题中,有时(2)、(3)步会结合进行,因为对数组处理的本身就隐含在对数组各元素的确定上。因为数组是由同类型的多个元素组成的,所以对各元素的处理都是相似的,因此处理数组很自然地就要使用循环语句,尤其是for语句。for语句的循环控制变量也就是数组的下标,可以根据数组下标的范围来确定循环控制变量的初值和终值。
【例6-2】将数组倒置,如把12345变为54321。编程思路:设数组a有n个元素,为实现前后倒置,只要把数组前后对应的元素逐个地进行交换即可,如图6-2所示。也就是:
a[0]←→a[n-1]
a[1]←→a[n-2]
图6-2交换示意图如果n为偶数,则要对换n/2对元素;如果n为奇数,则要对换(n-1)/2对元素,即处于正中间的那个元素(下标为(n-1)/2)不需和任何元素对换。但不管n是偶数还是奇数,都要进行n/2次对换。可以定义两个整型变量i和j,分别作为数组前部元素和后部元素的下标,i的初值为0,j的初值为n-1,它们随着操作的进行不断向中间收缩,直至进行完n/2次交换。#defineN10#include<stdio.h>main(){inti,j,t,a[N];printf(″Input10integers:\n″);for(i=0;i<=N-1;i++)scanf(″%d″,&a[i]);printf(″Beforeinverse:\n″);for(i=0;i<=N-1;i++)printf(″%3d″,a[i]);printf(″\n″);for(i=0,j=N-1;i<N/2;i++,j--){t=a[i];a[i]=a[j];a[j]=t;}printf(″Afterinverse:\n″);for(i=0;i<=N-1;i++)printf(″%3d″,a[i]);return0;}运行输出:Input10integers:1234567890↙
Beforeinverse:1234567890↙Afterinverse:0987654321↙这里N=10,进行了N/2=5次对换,最后一次是a[4]和a[5]的对换。
【例6-3】一头母牛一年生育一头小母牛,小母牛从第4年开始又生育小母牛,问20年之内,每年有多少头牛?
编程思路:首先列出年数和牛数的对照表,以便发现其中的规律:从表中可以看出,从第4年起,每一年的头数是前一年和前三年的头数之和,即:tn=tn-1+tn-3(n≥4)可以建立一个数组来记录每年的头数:年数作为下标,头数作为数组值。年数0123456…
牛数12346913…#include<stdio.h>main(){inti,ncow[21];ncow[1]=2,ncow[2]=3,ncow[3]=4;
for(i=4;i<=20;i++)ncow[i]=ncow[i-1]+ncow[i-3];for(i=1;i<=20;i++){printf(″%-10d″,ncow[i]);if(i%5==0)printf(″\n″);}return0;}运行输出:
2 3 4 6 9 13 19 28 41 60 88 129 189 277 407 595 872 1278 1873 2745
【例6-4】用起泡法对数据进行排序。编程思路:起泡法的特点是将相邻的两个元素进行比较,把较小的元素调到前面(升序)或后面(降序)。如果数组有n个元素,那么用起泡法排序最多进行n-1遍比较。每一遍都是第1个元素和第2个元素比,第2个和第3个比,…,第i-1个和第i个比。在比较的过程中,只要发现相邻元素逆序,就把它们交换,这样当第一遍比较之后,最大的元素就被放到了最后(升序);然后进行第二遍的排序,方法如上,只是排序的元素个数减少了1,最后这n-1个元素中的最大者被放在倒数第二位……这样反复进行,直到最后数组中只余下两个元素进行比较处理。设数组有6个元素,则其起泡排序过程可表示如下,其中“”表示比较,下划线表示交换。第一遍:
879142 789142 789142 781942 781492
781429通过这一遍的比较,数组的最大元素9被放到了最后,然后对余下的5个元素进行第二遍排序。第二遍:
78142 78142 71842 71482
71428这一遍把次最大的元素8放到倒数第二位。第三遍:
7142 1742 1472
1427第四遍: 142 142 124第五遍:
12 12可以看出,每比较一遍就去掉一个元素,这样数组中留待排序的元素就越来越少,直至最后只余下两个元素进行比较。对上面的数组共比较了5遍,是比较次数最多的情况,而且每一遍都有交换事件的发生。但对有些数组,n个元素不一定要比较n-1遍,也许可以提早结束。如有数组:214397,进行第一遍比较:
214397 124397 124397 123497 123497 123479可以看出,只进行了一遍就将数组排好序,如果还按原计划对其进行5遍比较操作,显然后来的操作全是无用功。我们可以设法避免这种情况,即在不需要继续比较的时候,让程序发出一种信息,以及时退出不必要的循环。常用的方法是设置一个开关变量,令它的值是0或1。在每遍比较开始时,把它设为1,如果在该遍比较中发生过交换操作,就将其置为0,否则就保持其值为1。当下一遍比较时先判断这个开关量是否为0,若为0,说明可能还有未排序的情况,可继续进行,若为1,说明整个数组已全部排序,不需要继续进行了。根据以上分析,可写出如下程序。#include<stdio.h>#defineM500main(){inti,j,n,t,a[M],fini,count,times;printf(″Inputn(<=%d)\n″,M);
scanf(″%d″,&n);
printf(″Input%dnumberstobesorted:\n″,n);for(i=0;i<=n-1;i++)scnaf(″%d″,&a[i]);fini=count=times=0;for(i=1;i<n&&!fini;i++){fini=1;count++;for(j=0;j<n-i;j++)if(a[j]>a[j+1]){ t=a[j]; a[j]=a[j+1]; a[j+1]=t; times++; fini=0;}}printf(″\npassnumber=%d\n,exchangetimes=%d\n″,count,times);for(i=0;i<n;i++){printf(″%d″,a[i]);if((i+1)%10==0)printf(″\n″);}return0;}变量fini为开关变量,count为比较遍数的计数器,times为交换次数计数器。程序中首先定义一个大数组,在此范围内可以对任意个元素的数组进行排序,这样处理比较灵活。运行输出:Inputn(n<=500)6↙
Input6numberstobesorted:879142↙passnumber=5exchangetimes=11124789再次运行:Inputn(n<=500)10↙
Input10numberstobesorted:9817634541passnumber=9exchangetimes=331134456789上面的排序过程是由后向前进行的,即数组的后半部是排好序的,前半部是尚待排序的。在排序过程中,后半部逐渐增加,前半部逐渐减少,直至变为一个元素。也可以采用相反的排序过程,即由前向后地进行排序,这只需对循环控制变量的初值和终值加以适当的设置即可。比如可以写为:for(i=1;i<n;i++)for(j=n-1,k=0;k<n-i;k++,j--){ if(a[j]<a[j-1]) { t=a[j]
a[j]=a[j-1]
a[j-1]=t; }}在这里,内循环中另外使用了一个变量k来控制循环次数,其实也可以不用新增变量,只要把外循环的控制变量的初值和终值作适当改变即可:for(i=0;i<n-1;i++)for(j=n-1;j>i;j--){…}请读者自行分析其执行过程。
【例6-5】用Shell排序法对数据排序。在起泡排序法中每两个相邻的元素都要进行比较,这样比较的次数就比较多。能否减少比较次数,加快排序的进行呢?Shell排序法可以做到。它的基本思想是允许第一次跳过较大的间隔去和后面的元素进行比较,当接近目的时,再跳过较小的间隔和后面的元素进行比较,直到间隔为1才进行相邻元素的比较。每次跳过多大的间隔为好呢?通常采用一种简单的方案:开始时跳过的间隔是数组长度的一半,以后每一遍再取上一间隔的一半。为了说明Shell排序的原理,我们以10个元素的数组为例作详细的图解说明。10个元素的跳步共有3种:10/2=5,5/2=2,2/2=1,即跳步jump可以取值5、2、1。原始数据:9817634541 jump=5:
9817634541 3817694541 3417698541 3417698541 3414698571 3414198576在这一遍中,第i个元素和第i+jump个元素比较,即第一个元素和第6个元素比较,第2个和第7个比较……,直到第5个和第10个比较。带下划线的数据表示是一对逆序数据,要对它们进行交换,交换后的结果在下一行显示出来。当一遍进行完后,再重复这样的操作,直到在当前跳步(jump=5)下再没有应该交换的元素为止。上面最后一行已符合这样的要求,于是把跳步缩小一半,对上一跳步下的结果继续进行比较。在这一遍中,第i个元素和第i+jump个元素比较,即第一个元素和第6个元素比较,第2个和第7个比较……,直到第5个和第10个比较。带下划线的数据表示是一对逆序数据,要对它们进行交换,交换后的结果在下一行显示出来。当一遍进行完后,再重复这样的操作,直到在当前跳步(jump=5)下再没有应该交换的元素为止。上面最后一行已符合这样的要求,于是把跳步缩小一半,对上一跳步下的结果继续进行比较。 jump=2:
3414198576 1434198576 1434198576 1414398576 1414398576 1414398576 1414358976 1414357986 1414357689在这一遍中是第i个和第i+jump比,比较结果如最后一行所示,再对其进行一遍类似的操作,未发现有逆序现象,于是转入下一跳步操作。
jump=1: 1414357689 1414357689 1144357689 1144357689 1143457689 1143457689 1143457689 1143456789 1143456789最后一行是在本跳步下比较一遍之后的结果,但里面仍有逆序现象,必须在当前跳步下再重复一遍同样的操作,然后继续检查,直至当前跳步下没有逆序为止。最后的结果为:1134456789从上面的排序可以看出,Shell排序需要三重循环:最外层循环控制跳步的大小;最内层循环执行扫描、比较和交换;中间一层的循环控制在当前跳步下重复检查的次数,为此可设一个开关变量alldone来控制,其值为1表示进行内循环,其值为0表示不再进行内循环。设n为数组长度,a为数组名,则可用如下框架描述Shell排序过程:jump=n/2;while(jump>=1){alldone=1;while(alldone==1) {alldone=0; for(i=0;i<n-jump;i++) { j=i+jump; if(a[i]>a[j]) {交换a[i]和a[j]
alldone=1; } }}jump=jump/2;}由此可写出完整的程序,程序中用jump表示跳步间隔,用count表示比较遍数,用times表示交换次数。#include<stdio.h>#defineM500main(){intt,i,j,n,jump,count,times,a[M],alldone;printf(″Inputn(n<=%d):\n″,M);
scanf(″%d″,&n);
printf(″Input%dnumberstobesortedinshell:\n″,n);for(i=0;i<=n-1;i++)scanf(″%d″,&a[i]);count=times=0;jump=n/2;while(jump>=1){alldone=1;while(alldone==1){alldone=0; for(i=0;i<n-jump;i++) {j=i+jump; if(a[i]>a[j]) {t=a[i]; a[i]=a[j]; a[j]=t; alldone=1; times++; } } count++; } jump=jump/2;}for(i=0;i<=n-1;i++){printf(″%3d″,a[i]);if((i+1)%10==0) printf(″\n″);}printf(″passnumber=%d\nexchangetimes=%d\n″,
count,times);return0;}运行输出:Inputn(n<=500)10↙
Input10numberstobesortedinshell:9817634541↙
1134456789passnumber=7exchangetimes=13和起泡法相比,Shell排序法的比较遍数和交换次数都有所降低,说明Shell排序法是个更有效的排序方法。6.2.5数组作为函数的参数在前面的函数中,我们定义和使用的参数类型都是基本类型,其实数组也可以作为参数使用。数组作参数具有一些特殊的性质,利用这样的性质可以对数组进行需要的加工处理。数组作参数时,实参和形参的关系也和普通数据类型一样,必须作到个数、类型、次序相一致;形参数组和实参数组必须分别定义,但其大小不一定一样。在函数中对形参数组处理时必须特别小心,如形参数组大,实参数组小,则对函数中形参过长的部分不能加以引用,若引用将出现错误。如: 实参数组: 形参数组:
4687不可引用反过来,如形参数组定义过小,则实参数组后面多余的元素就会被舍掉而得不到处理。如: 实参数组: 形参数组:4687543被舍掉为了避免这两种情况,使形参数组具有较大的适应性,在定义形参数组时可以不指定大小,而把实参数组的大小再用一个参数表示出来。例如:函数定义为:
floataverage(floata[],int);在主调函数中,若有两个数组:
floats1[5]={34,68,79.6,87.5,68.4} floats2[8]={91.3,62.4,81,76,66,95.5,87.2,77.8};则可以调用函数average如下:ave1=average(s1,5);ave2=average(s2,8);这样average函数就处理了长度为5和8的两个实际数组。数组名作参数有个最重要的特征是传递数组的首地址,就是说把实参数组的首地址传给形参数组,使形参和实参都用同样的内存空间,因而函数中对形参数组的处理也就是对实参数组的处理。这是和其他类型作参数不同的地方。其他类型作参数时,向函数传递的是实参的一个拷贝,函数中是对实参拷贝的处理,与实参变量本身没有关系,这就是所谓的“传值调用”。数组名代表数组的首地址,把实参数组名传给函数,函数中对该数组的处理就像在主调函数中对它的处理一样。在程序设计中往往利用这一特点来改变数组,如对数组进行排序等。
【例6-6】用选择法对数组排序。编程思路:选择法的要点是首先从有n个元素的数组a中选出一个最小的元素,把它和a[0]交换,再从余下的n-1个元素中选出一个最小的元素和a[1]交换……。每进行一遍选择就可确定一个排序元素,把它放到数组的前部。重复这样的过程,直到最后只剩下两个元素进行比较。为了提高排序效率,不必在发现一个比a[0]小的元素如a[i]后就和a[0]交换,也许后面还有比a[i]更小的元素a[i+j]呢!处理的办法是记住当前最小元素的下标,在一遍比较中,该下标可能有变化,在一遍比较结束后再来决定是否和a[0]交换。把该算法用一个函数来实现,即为voidselsort(inta[],intn){inti,j,k,t;for(i=0;i<n-1;i++){k=i;for(j=i+1;j<n;j++) if(a[j]<a[k])k=j;if(k!=i){t=a[k];a[k]=a[i];a[i]=t;}}}这里的k用以标记当前最小元素的下标,初值置为i,即当前数组第一个元素的下标。在内循环结束之后,要检查k的值是否发生了变化,若发生了变化,即k不再等于i,则说明后面的元素a[k]比a[i]小,这时应将它们交换。如果最后k仍然等于i,则说明a[i]就是当前数组的最小元素,不需要交换操作。主调函数调用该函数时,把一个真实的数组名作为实在参数代入,程序如下。#include<stdio.h>voidselsort(int[],int);main(){inti,j,a[10];printf(″Input10integers:\n″);for(i=0;i<=9;i++)scanf(″%d″,&a[i]);selsort(a,10);printf(″sortedarrayare:\n″);for(i=0;i<=9;i++)printf(″%3d″,a[i]);return0;}运行输出:Input10integers:3642518794↙
sortedarrayare:1234456789主函数中的
voidselsort(int[],int);是函数原型声明,当参数是数组时可以不给出数组名,只用“<类型名>[]”就可以表示在这里有一个这种类型的数组。函数调用语句
selsort(a,10);的功能是完成对数组a的排序,不要求函数返回值。
【例6-7】二分法查找。查找是一种用途广泛的操作。查找有很多种方法。如果一个数组未加排序,那么可以使用顺序查找法,从第一个元素起一个一个地考查,直到发现所求元素,或者查完数组也未发现要找的元素。如果数组中有n个元素,则用顺序查找法时平均要查找n/2次。如果数组已经排序(假设为按升序排序),则可以用二分法查找。它的基本思想是:首先把数组中间的那个元素和所求元素进行比较,看是否为所求元素,若是,则查找结束;如果不是,则进一步比较该元素和所求元素的大小关系,若所求元素小,则说明应该到数组的前半部分中找,否则就应该到后半部分中找,这样把查找范围一下子缩小了一半。以后在较小范围内仍按同样的方法进行查找,这样很快就可以得到结果。用这种方法查找有n个元素的数组的最大查找次数为lbn(即以2为底n的对数)。当对有10亿(近似于230)个元素的数组进行查找时,用顺序查找平均需要5亿次,而用二分法查找则最多只需30次,真是天壤之别!二分法查找算法可用一个迭代函数bis来实现。该函数有4个参数:数组名、关键字key、最小下标low和最大下标high。如果关键字和当前查找的数组的中间元素(下标为mid)不匹配,就修改最小下标low或最大下标high,从而在一个更小的范围内查找。如果关键字key小于中间元素,就把最大下标置为mid-1,并继续在low和mid-1之间查找;如果关键字大于中间元素,就把最小下标low置为mid+1,然后继续在mid+1和high之间查找。对当前考查范围内的中间元素在显示时都打上“*”号标记,代表是该元素和关键字进行比较,这种显示方法也单独用一个函数来实现。程序中全局变量m是一个记录查找次数的计数器。程序如下:#include<stdio.h>#defineM15intm=0;intbis(int[],int,int,int);voidprintr(int[],int,int,int);main(){
inti,k,result,a[M];
printf(″Input%dnumbersinorder:\n″,M);
for(i=0;i<=M-1;i++)scanf(″%d″,&a[i]);printf(″Enteranumbertofind:\n″);scanf(″%d″,&k);result=bis(a,k,0,M-1);
if(result!=-1) printf(″%dfoundinarrayelement%d\n″,k,result); else printf(″%dnotfound!\n″,k); printf(″searchtimes=%d\n″,m); return0; }intbis(intb[],intkey,intlow,inthigh){intmid;while(low<=high){ mid=(low+high)/2; m++; printr(b,low,mid,high); if(key==b[mid]) returnmid; elseif(key<b[mid]) high=mid-1; elselow=mid+1;}return-1;}voidprintr(intb[],intlow,intmid,inthigh){inti;for(i=0;i<=M-1;i++)if(i<low‖i>high) printf(″″);elseif(i==mid) printf(″%3d*″,b[i]);elseprintf(″%3d″,b[i]);printf(″\n″);}运行输出:Input15numbersinorder:123456789101112131415↙
Enteranumbertofind:5↙
12345678*91011121314151234*56756*75*5foundinarrayelement4searchtimes=4对于二分法查找算法,我们还可以用递归函数来实现。该算法的递归形成为:intbis(intb[],intkey,intlow,inthigh){intmid;if(low>high)return-1;mid=(low+high)/2;m++;printr(b,low,mid,high);if(key==b[mid])returnmid;elseif(key<b[mid]) returnbis(b,key,low,mid-1);else returnbis(b,key,mid+1,high);}对照两个函数,我们可以发现,在迭代算法中使用的是循环语句,而在递归算法中是不用循环语句的,它是对函数自身的调用,只是要修改函数的参数。一个数组可以看作是由两部分组成的:数组的第一个元素和除第一个元素之外的其他部分。这余下的部分又是个小数组,又可以看作由两部分组成。由此可以得知:数组也是一个递归的数据类型,可以用递归的方法对数组进行处理。
【例6-8】请说出下面程序的功能。#include<stdio.h>intsum(int[],int);main(){inttotal,a[10]={1,2,3,4,5,6,7,8,9,10};total=sum(a,10);printf(″Total=%d\n″,total);return0;}intsum(intb[],intn){if(n==1)return(b[0]);elsereturn(b[n-1]+sum(b,n-1));}
sum函数有一个数组作为形参,调用时主调函数给它赋以实在数组名a。函数sum用递归的方法对数组进行处理。若数组只有一个元素(n=1),则把其惟一的元素b[0]返回,这也是递归的边界条件。如果数组有多个元素(n>1),那么先求出其前n-1个元素的和,再把最后一个元素b[n-1]加上去。由此就可断定,对前n-1个元素的处理实际上是求前n-1个元素的和。递归调用时,参数在递减,数组在变短,所以每次的“最后元素”也是个相对的概念,它只是当前所考虑数组的最后元素。最后一定会变到只有一个元素b[0],作为边界条件终止递归调用。由以上分析可知,该程序的功能是计算a[0]+a[1]+…+a[9]的值,所以运行输出为:
Total=55
【例6-9】将数字1、2、3、4、5、6、7、8、9分成3组,使每一组构成的3位数均是完全平方数,且3组中的数字均不相同。
编程思路:首先产生数字没有重复的完全平方的3位数,并将它们放入一个一维数组中。然后对该数组中的各个元素进行相互比较,从中找出3个符合各位数字均无重复要求的三位数。可将判断一个整数是否完全平方数编一个函数,再将判断两个整数各位数字是否相同也编为一个函数。如已知一个数是完全平方数,还要进一步求出它是哪一个数的平方,则可用另一个函数解决。判断两个三位数中有没有重复数字的算法采用的是分解法,即先从两个三位数中各分离出三个数字,再判断这些数字有无重复。求解一个数(n)是哪个数(j)的平方的算法,是从这个数中不断地减2,直到0为止,则减的次数即为所求的j值。产生没有重复数字的三位数的方法是合成法,直接由不同的三个数字来构成三位数。#include<stdio.h>intsq(intn)//判断一个数是否完全平方数{inti;for(i=1;i<n/2;i++)if(i*i==n) return1;return0;}intneq(intm,intn)//判断两个没有重复数字的三位数是否完全不同,即没有重复数字{inti1,j1,k1,i2,j2,k2;i1=m/100;j1=m%100/10;k1=m%10;i2=n/100;j2=n%100/10;k2=n%10;if(i1!=i2&&i1!=j2&&i1!=k2&&j1!=i2&&j1!=j2&&j1!=k2&&k1!=i2&&k1!=j2&&k1!=k2)return1;return0;}intksqr(intn)//求一个已知的平方数是哪个数的平方{inti,j;for(i=1,j=1;;i+=2)if((n-=i)!=0)j++;elsebreak;returnj;}main(){inti,j,k,r=0,b[15],m;printf(″\nSquarenumberhaving3digitsare:\n\n″);for(i=1;i<=9;i++)//求出本身没有重复数字的且是完全平方的三位数
for(j=1;j<=0;j++)if(j!=i) for(k=1;k<=9;k++) if(k!=i&&k!=j&&sq(i*100+j*10+k)) {b[r++]=i*100+10*j+k; m=r-1; printf(″%5d=(%d)**2\n″,b[m],ksqr(b[m]));}printf(″\nTotalnumber:%d\n″,r);printf(″\=nThis3numbershavenotsamenumber:\n″);for(i=0;i<r;i++)//求出互不相同的三个数,共包含1,
2,3,4,5,6,7,8,9九个数字
for(j=i+1;j<r;j++)for(k=j+1;k<r;k++)if(neq(b[i],b[j])&&neq(b[i],b[k])&&neq(b[j],b[k])) printf(″%d=(%d)**2%d=(%d)**2%d=(%d)**2\n\n″,b[i],ksqr(b[i],b[j],ksqr(b[j],
b[k],ksqr(b[k]));return0;}运行输出:Squarenumberhaveing3digitsare:169=(12)**2196=(14)**2256=(16)**2289=(17)**2324=(18)**2361=(19)**2529=(23)**2576=(24)**2625=(25)**2729=(27)**2784=(28)**2841=(29)**2961=(31)**2Totalnumber:13This3numbershavenotsamenumber:361=(19)**2529=(23)**2784=(28)**2
【例6-10】从30名运动员中选出10名优秀运动员,具体规则是:
(1)运动员按1,2,3,…顺序编号;
(2)由键盘输入选票,每张选票最多可写10个不同的编号,按出现的顺序表示名次;
(3)对应名次可以空缺,但必须用0表示;
(4)若编号超出规定的范围(1~30)或编号出现了重复,则该编号无效,该票上的其他编号仍认为有效;
(5)每张票中的编号按顺序得分为:15,12,9,7,6,5,4,3,2,1;
(6)按运动员所得分数从高到低排序,输出前10名最佳运动员排名表,格式为: 名次编号得分得票数如得分相同,则得票多的在前:如得分和得票都相同,则编号小的在前。编程思路:
(1)一张票上有10个整数,用整型数组表示,通过循环语句实现输入。如其中有重复的数字,则把这些数字全部置为0,表示不予考虑。这种重复数字置0的操作可用双重循环实现。
(2)因票数未定,故用While语句实现循环,先输入一个非负的整数开始循环,最后以输入一个负数退出循环。在循环体中每读入一张票(即10个整数),就对其进行有效性处理(1)。以整数所代表的运动员编号减1作为票数数组的下标,累加该运动员所得票数;而以该整数输入时的顺序作为权值数组的下标,求出相应的权值进行分数的累加。这里用一个数组元素作为另一个数组元素的下标,应考虑到运动员的编号(从1开始)与数组下标(从0开始)的差别。#include<stdio.h>voidsort(inta[],intx[],intk)//按分数排序,让序号跟着分数走。此处是对数组{//a(放分数)排序,数组x(放序号)跟着走inti,j,m,n;for(i=1;i<k;i++){for(j=0;j<k-i;j++) if(a[j]<a[j+1]) {m=a[j];n=x[j]; a[j]=a[j+1];x[j]=x[j+1]; a[j+1]=m;x[j+1]=n; }}}main(){inta[30]={0},aa[30]={0},a1[10],b[10]
={15,12,9,7,6,5,4,3,2,1},x[30],i,
j,k,m,n,c;//数组a表示得票数,aa表示积分数,x表示编号,b表示权值,a1代表一张选票
n=0;for(i=0;i<30;i++) x[i]=i+1;while(1){printf(″Enteraninteger,negativetostop\n″);scanf(″%d″,&c);//控制循环是否中止
if(c<0) break;printf(″Input10integers:0---30\n″);for(i=0;i<10;i++); //每次读入10个数据,算是一张选票
scanf(″%d″,&a1[i]);for(i=0;i<9;i++) //将一张选票中的重复数字全置为0{for(k=i+1;k<10;k++)if(a1[k]==a1[i]){a1[k]=0;n=1;}if(n==1){a1[i]=0;n=0;}}for(i=0;i<10;i++)//让选票中有效的数字(1~30),即a1数组的元素作为票数数组a的下标if(a1[i]>=1&&a1[i]<=30){a[a1[i]-1]++; //所得票数累加
aa[a1[i]-1]+=b[i];//所得分数累加相应的权值
} } sort(aa,x,30);//按分数a降序排序,让序号x跟着分数走for(i=0;i<10;i++) //只对积分数数组aa的前10个数据进行输出
{for(j=i+1;j<10;j++)if(aa[j]==aa[i])//两人分数aa[]相等时
{if(a[j]>a[i])//则票数a[]多的在前
{m=a[j];n=x[j];//序号x[]跟着票数a走
a[j]=a[i];x[j]=x[i];a[i]=m;x[i]=n;}if(a[j]==a[i]) //如票数a[]也相等
if(x[j]<x[i]) //则序号x[]小的在前
{m=x[j]; x[j]=x[i]; x[i]=m; }}}printf(″\n\n名次编号得分得票数\n\n″); //每行输出一个运动员的信息for(i=0;i<10;i++) //输出前10名运动员
{printf(″%2d″,i+1);printf(″%2d″,x[i]+1);printf(″%2d″,aa[i]);printf(″%2d″,a[x[i]]);printf(″\n″);}return0;}运行输出:Enteraninteger,negativetostop999Input10integers:1---307865432890Enteraninteger,negativetostop999Input10integers:1---3015693409722Enteraninteger,negativetostop99Input10integers:1---302231142426281957Enteraninteger,negativetostop999Input10integers:1---306754311233478Enteraninteger,negativetostop-8名次编号得分得票数1 6 33 32 5 30 43 3 29 44 4 25 45 7 18 36 22 16 27 1 15 18 11 14 29 24 6 110 26 5 16.3一维字符数组元素类型为char的数组是字符数组。字符数组有和其他数组相同的共性,同时也有自己的特性,即有独特的运算函数和处理方法,因此我们对字符数组作单独的讨论。6.3.1一维字符数组变量的定义和引用一维字符数组的定义格式和其他类型数组的定义格式一样,如语句
charc[10];定义了有10个字符元素的数组c,这10个元素分别用c[0],c[1],c[2],…,c[9]表示。
【例6-11】从键盘输入一行字符,以换行符结束,分别以正向和反向的次序输出。#include<stdio.h>#defineM80main(){inti,n;chars[M];for(i=0;i<=M-1;i++){s[i]=getchar();if(s[i]==′\n′)break;}
for(n=0;n<i;n++)putchar(s[n]);putchar(′\n′);for(n=i-1;n>=0;n--)putchar(s[n]);return0;}程序在从第一个for循环出来之后,i的值是小于M的一个整数,而且s[i]等于“\n”,不可输出显示,因此在进行下面的正向和反向输出时应当注意到这一点。6.3.2字符数组的输入/输出
1.字符数组的输入字符数组有四种输入方法:赋值输入法、格式控制输入法、连续字符输入法和用gets函数输入法。
(1)赋值输入法。赋值输入法通过对数组变量赋初值的方法使数组元素具有值。赋初值时,既可以逐字符地赋值,也可以以字符串的形式赋值。如:
charc1[10],c2[20],c3[]={′a′,′b′,′c′,′d′},c4[]=″abcd″;
注意:c3和c4是有区别的:c3的长度是4,而c4的长度是5,因为字符串中包含一个看不见的“\0”字符作为结束标志。如果数组定义时指定了长度,则输入的字符应不大于定义的长度。使用赋值输入法时常见的错误是在定义之后又对数组名进行赋值操作。如:c1={′c′,′d′,′e′};c2=″China″;是错误的,因为c1、c2是数组名,代表数组的首地址,它在内存中是个常量,不能改变,因此不能作为赋值语句的左值使用。对其他类型的数组也都是如此。
(2)格式控制输入法。此时使用的格式控制符是“%s”,例如对上面定义的数组,可以用下列语句输入:
scanf(″%s″,c1);因为c1代表数组的首地址,所以在读的时候,在c1前就不必再加取地址运算符“&”了。若从键盘输入:
China↙
则c1中就有“China”的内容。这样输入时也应保证输入的字符个数小于数组的长度。如果输入的串中含有空格,则这种输入法将只把空格以前的部分读入数组,空格以后的部分被舍弃,并在输入部分的最后自动增加一个字符串结束标志′\0′。如:
scanf(″%s″,c2);键盘输入:
ChinaBeijing↙
则c2中只有“China”的内容:利用数组
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年度宠物用品销售及养护服务外包协议4篇
- 2025年度厂房租赁合同配套基础设施完善协议4篇
- 旅游部门半年回顾
- 专用借款协议:2024年版详尽协议版A版
- 2025年度智能家居产品定制生产合同范本4篇
- 二零二四三方国际贸易融资借款协议2篇
- 2025年度拆除项目环保验收合同模板4篇
- 二手房交易代签合同范本2024年版版
- 个性化服务型酒店房间租赁协议版A版
- 二零二五版船艇交易环保与安全协议3篇
- 施工现场水电费协议
- SH/T 3046-2024 石油化工立式圆筒形钢制焊接储罐设计规范(正式版)
- 2024年海口市选调生考试(行政职业能力测验)综合能力测试题及答案1套
- 六年级数学质量分析及改进措施
- 一年级下册数学口算题卡打印
- 真人cs基于信号发射的激光武器设计
- 【阅读提升】部编版语文五年级下册第三单元阅读要素解析 类文阅读课外阅读过关(含答案)
- 四年级上册递等式计算练习200题及答案
- 法院后勤部门述职报告
- 2024年国信证券招聘笔试参考题库附带答案详解
- 道医馆可行性报告
评论
0/150
提交评论