这样,min中存放的就是3个数中的最小值。
求解一个给定的、可计算或可解的问题,不同的人可以编写出不同的算法来解决同一个问题。
例如计算1999+2999+3999+…+9999,也许有的人会选择一个一个加起来,当然也有人会选择(2000–1)+(3000–1)+…+(10000–1)的算法。
理论上,不论使用哪种算法,只要逻辑正确并能够得出正确的结论就可以,但是,为了节约时间、运算资源等,当然提倡简单易行的算法。
制定一个算法,一般要经过设计、确认、分析、编码、测试、调试、计时等阶段。
对算法的研究主要包括5个方面的内容。
(1)设计算法。
算法设计工作的完全自动化是不现实的,算法的设计最终还是要人们自己来完成,应学习和了解已经被实践证明可行的一些基本的算法设计方法,这些基本的设计方法不仅适用于计算机科学,而且适用于电气工程、运筹学等任何算法相关的其他领域。
(2)表示算法。
算法的类型不同、解决的问题不同、解决问题的步骤不同,表示算法的方法也自然有很多种形式,例如自然语言、图形、算法语言等。
这些表示方式各有特色,也分别有适用的环境和特点。
(3)认证算法。
算法认证其实就是确认这种算法是否能够正确的工作,是否达到解决问题的目的,即确认该算法具有可行性。
正确的算法用计算机算法语言描述,构成计算机程序,计算机程序在计算机上运行,得到算法运算的结果。
(4)分析算法。
对算法进行分析,确认这个算法解决问题所需要的计算时间和存储空间,并对其进行定量分析。
对一个算法的分析可以很好地预测一种算法适合的运行环境,从而判断出其适合解决的问题。
(5)验证算法。
用计算机语言将算法描述出来,进行运行、测试、调试,客观地判断算法的实际应用性、合理性。
1.2.2算法的特性
一个算法应当具有以下5方面性质。
(1)确定性。
与我们日常的行为不同,算法绝对不能有含糊其辞的步骤,像“请把那天的书带来!
”,这种无法明确哪一天、哪一本书、带到哪里的语句是不能够出现在算法中的,否则,算法的运行将变得无所适从。
算法的每一步都应当是意义明确、毫不模糊的。
(2)可行性。
算法的基本目的是解决问题,所以要求算法至少是可以运行并能够得到确定的结果的,不能存在违反基本逻辑的步骤。
(3)输入。
一个算法有0个或多个输入,在算法运算开始之前给出算法所需数据的初值,这些输入取自特定的对象集合。
(4)输出。
作为算法运算的结果,一个算法产生一个或多个输出,输出是同输入有某种特定关系的量。
(5)有穷性。
一个算法应包含有限个操作步骤,而不能是无限的。
一个算法总是在执行了有穷步的运算后终止,即该算法是可达的。
满足前4个特性的一组规则不能称为算法,只能称为计算过程。
操作系统是计算过程的一个例子,操作系统用来管理计算机资源、控制作业的运行,没有作业运行时,计算过程并不停止,而是处于等待状态。
1.2.3算法的表示
由于算法的步骤繁简不同、解决的问题不同,算法的表示方法也有许多种,一般可以归纳为以下几种。
1.自然语言表示
自然语言,简单来说就是人们日常生活中应用的语言。
相对于计算机语言来说,自然语言更容易被接受,也更容易学习和表达,但是自然语言往往冗长烦琐,而且容易产生歧义。
例如:
“他看到我很高兴。
”便不清楚是他高兴,还是我高兴。
尤其是在描述分支、循环算法时,用自然语言十分不方便。
所以,除了一些十分简单的算法外,我们一般不采用自然语言来表示算法。
2.图形表示
用图形表示算法即用一些有特殊意义的几何图形来表示算法的各个步骤和功能。
使用图形表示算法是一种很好的方法,因为千言万语不如一张图明了易懂,图形的表示方法比较直观、清晰,易于掌握,有利于检查程序错误,在表达上也克服了产生歧义的可能。
一般我们使用得比较多的有传统流程图、N-S流程图、PAD流程图等。
本书只介绍传统流程图,其他流程图请参看其他程序设计书籍。
传统的流程图一般由图1.1所示的几种基本图形组成。
开始或结束框处理框输入/输出框判断框连接点流程线
图1.1流程图的基本图形
【例1.2】输入两个整数给变量x和y,交换x和y的值后再输出x和y的值。
分析:
完成本题需要3个步骤,首先输入两个整数给x和y;之后交换x和y的值;最后输出x和y。
根据以上分析,画出程序的流程图(见图1.2),并根据流程图写出程序:
main()
{intw,x,y;
printf("请输入两个整数:
");
scanf("%d%d",&x,&y);
w=x;
x=y;
y=w;
printf("交换后:
x=%dy=%d\n",x,y);
}
说明:
①scanf()函数为输入函数,可以用来输入数据;输出数据可以使用printf()函数。
②引入第3个变量w,先把变量x的值赋给w,再把变量y的值赋给x,最后把变量w的值赋给y,最终达到交换变量x和y的值的目的。
引入w的作用是交换变量x和y的值。
交换x和y的值不能简单地用x=y;和y=x;这两个语句,如果没有把x的值保存到其他变量就执行x=y;语句,把y的值赋给x,将使x和y具有相同的值,丢失x原来的值,也就无法实现两个值的交换。
假如输入3和9,运行程序时,屏幕可能显示如下信息:
请输入两个整数:
39
交换后:
x=9y=3
【例1.3】输入a、b、c共3个数,把最小的值输出,流程图如图1.3所示。
图1.2例1.2流程图图1.3例1.3流程图
先将a与b进行比较,若a
根据以上几个例子可以看出,使用传统的流程图主要由带箭头的线、文字说明和不同形状的框构成。
采用传统的流程图可以清晰直观地反映出整个算法的步骤和每一步的先后顺序。
因此,在相当长的一段时间内,传统流程图成为很流行的一种算法描述方式,但是当算法相当复杂,篇幅很长时,使用传统的流程图就会显得费时又费力。
随着结构化程序设计思想的推行与发展,渐渐的衍生出N-S结构化流程图。
3.伪代码
伪代码(pseudocode)是一种算法描述语言。
使用伪代码的目的是为了使被描述的算法可以容易地以任何一种编程语言(Pascal、C、Java等)实现。
因此,伪代码必须结构清晰、代码简单、可读性好并且类似自然语言。
用各种算法描述方法所描述的同一算法,只要该算法实现的功能不变,就允许在算法的描述和实现方法上有所不同。
1.2.4算法的复杂度
找到求解一个问题的算法后,接着就是该算法的实现,至于是否可以找到实现的方法,取决于算法的可计算性和计算的复杂度,该问题是否存在求解算法,能否提供算法所需要的时间资源和空间资源。
算法的复杂度是对算法运算所需时间和空间的一种度量。
在评价算法性能时,复杂度是一个重要的依据。
算法的复杂度的程度与运行该算法所需要的计算机资源的多少有关,所需要的资源越多,表明该算法的复杂度越高;所需要的资源越少,表明该算法的复杂度越低。
在计算机的资源中,最重要的是运算所需的时间资源和存储程序、数据所需的空间资源,所以算法的复杂度有时间复杂度和空间复杂度之分。
对于任意给定的问题,设计出复杂度尽可能低的算法是在设计算法时考虑的一个重要目标。
另外,当给定的问题已有多种算法时,选择其中复杂度最低者是在选用算法时应遵循的一个重要准则。
因此,算法的复杂度分析对算法的设计或选用有着重要的指导意义和实用价值。
算法的时间复杂度是指算法需要消耗的时间资源。
一般情况下,问题的规模越大,时间复杂度越大,算法的执行率越低。
算法的空间复杂度是指算法需要消耗的空间资源。
其计算和表示方法与时间复杂度类似,一般都用复杂度的渐近性来表示。
同时间复杂度相比,空间复杂度的分析要简单得多。
1.2.5结构化程序设计方法
程序设计初期,由于计算机硬件条件的限制,运算速度与存储空间都迫使程序员追求高效率,编写程序成为一种技巧与艺术而程序的可理解性、可扩充性等因素被放到第二位。
这一时期的高级语言如FORTRAN、COBOL、ALGOL、BASIC等,由于追求程序的高效率而不太注重所编写程序的结构,其存在的一个典型问题就是程序中的控制随意跳转,即不加限制地使用goto语句。
goto语句允许程序从一个地方直接跳转到另一个地方去。
这样做的好处是程序设计十分方便灵活,减少了人工复杂度,但其缺点也是十分突出的,一大堆跳转语句使得程序的流程十分复杂紊乱,难以看懂也难以验证程序的正确性,如果有错,排起错来更是十分困难。
这种转来转去的流程图所表达的混乱与复杂,正是软件危机中程序人员处境的一个生动写照,而结构化程序设计,就是要把这团乱麻理清。
经过研究,人们发现任何复杂的算法都可以由顺序结构、选择(分支)结构和循环结构这3种基本结构组成。
因此,人们构造一个算法的时候,也仅以这3种基本结构作为“建筑单元”,遵守3种基本结构的规范。
基本结构之间可以并列、可以相互包含但不允许交叉,不允许从一个结构直接转到另一个结构的内部去。
正因为整个算法都是由3种基本结构组成的,就像用模块构建的一样,所以结构清晰、易于正确性验证、易于纠错,这种方法就是结构化方法。
遵循这种方法的程序设计就是结构化程序设计,C语言就是一种结构化语言。
1.顺序结构
顺序结构表示程序中的各操作是按照它们出现的先后顺序执行的,其流程如图1.4所示。
整个顺序结构只有一个入口点和一个出口点。
这种结构的特点是:
程序从入口点开始,按顺序执行所有操作,直到出口点处,所以称为顺序结构。
程序的总流程都是顺序结构的。
2.选择结构
选择结构表示程序的处理步骤出现了分支,它需要根据某一特定的条件选择其中的一个分支执行。
选择结构有单选择、双选择和多选择3种形式。
双选择是典型的选择结构形式,其流程如图1.5所示,在这两个分支中只能选择一条且必须选择一条执行,但不论选择了哪一条分支执行,最后流程都一定到达结构的出口点处。
图1.4顺序结构图1.5选择结构
多选择结构是指程序流程中遇到多个分支,程序执行方向将根据条件确定。
要根据判断条件选择多个分支的其中之一执行。
不论选择了哪一条分支,最后流程都要到达同一个出口点处。
如果所有分支的条件都不满足,则直接到达出口点处。
3.循环结构
循环结构表示程序反复执行某个或某些操作,直到某条件为假(或为真)时才可终止循环。
在循环结构中最主要的是:
什么情况下执行循环?
哪些操作需要循环执行?
循环结构的基本形式有两种:
当型循环和直到型循环,其流程如图1.6(a)和图1.6(b)所示。
图中A的操作称为循环体,是指从循环入口点到循环出口点之间的处理步骤,这就是需要循环执行的部分,而什么情况下执行循环则要根据条件判断。
当型结构:
表示先判断条件,当满足给定的条件时执行循环体,并且在循环终端处流程自动返回到循环入口点;如果条件不满足,则退出循环体直接到达流程出口点处。
因为是“当条件满足时执行循环”,即先判断后执行,所以称为当型循环,其流程如图1.6(a)所示。
直到型循环:
表示从结构入口点处直接执行循环体,在循环终端处判断条件,如果条件不满足,返回入口点处继续执行循环体,直到条件为真时再退出循环到达流程出口点处,即先执行后判断。
因为是“直到条件为真时为止”,所以称为直到型循环,其流程如图1.6(b)所示。
循环结构也只有一个入口点和一个出口点,循环终止是指流程执行到了循环的出口点。
图中所表示的A处理可以是一个或多个操作,也可以是一个完整的结构或一个过程。
(a)当型循环(b)直到型循环
图1.6循环结构
通过3种基本控制结构可以看到,结构化程序中的任意基本结构都具有唯一入口点和唯一出口点,并且程序不会出现死循环。
在程序的静态形式与动态执行流程之间具有良好的对应关系。
1.2.6算法举例
【例1.4】计算n!
。
分析:
n!
即1×2×3×4×…×(n–1)×n,因此可以一步一步的采用基本的方法进行计算,即:
S1:
1*2求得结果等于2;
S2:
再用S1求得的结果2乘以3,等于6;
……
Sn–1:
用Sn–1求得的结果乘以n,最终求得n!
(S为步骤Step的缩写)。
这样的计算方式固然也能够求出正确的答案,但是计算的过程过于烦琐。
所以可以设两个变量,一个表示乘数,一个表示被乘数。
算法表示如下:
设a为乘数,b为被乘数;
S1:
输入n;
S2:
令a=1;b=2;
S3:
计算a*b,并把两数的乘积放在a中;
S4:
使b的值加1;如果bn,则返回到S3,否则结束运行,输出a即为所求。
【例1.5】求2008—3200年中的哪些年是闰年。
分析:
如果某年是闰年,它要么能被4整除但不能被100整除,如:
2008年、2020年;要么能被400整除,如2000年、2400年。
根据如上分析,我们可以设计算法如下:
设判断的年份为N;
S1:
设N=2008;
S2:
如果N能被4整除,转入S3;
如果N不能被4整除,就输出N不是闰年转入S5;
S3:
如果N不能被100整除,就输出N是闰年转入S5;
如果N能被100整除,就转入S4;
S4:
如果N不能被400整除,就输出N不是闰年转入S5;
如果N能被400整除,就输入N是闰年转入S5;
S5:
令N=N+1;
S6:
当N3200时,转入S2继续执行,否则停止算法。
闰年的计算方法虽然只是简单的循环和判断,但这些简单的循环和判断却是组成结构化程序的基本要素。
1.3上机编程准备
1.3.1TurboC集成开发环境
进入TurboC2.5集成开发环境后,屏幕上显示的内容如图1.7所示。
图1.7TurboC2.5集成开发环境
图中第2行为TurboC2.5主菜单,中间窗口为编辑区,接下来是信息窗口,最底下一行为参考行。
这4个窗口构成了TurboC2.5的主屏幕,以后的编程、编译、调试以及运行都将在这个主屏幕中进行。
下面详细介绍主菜单的内容。
主菜单在TurboC2.5主屏幕的第2行,显示下列内容:
File、Edit、Run、Compile、Project、Options、Debug、Break/watch。
除Edit项外,其他各项均有子菜单,只要用Alt键加上某项中第1个字母(即大写字母)就可进入该项的子菜单中。
1.File(文件)菜单
按Alt+F键可进入File菜单,该菜单包括以下内容。
(1)Load(加载):
装入一个文件,可用类似DOS的通配符(如*.c)来进行列表选择,也可装入其他扩展名的文件,只要给出文件名(或只给路径)即可。
(2)Pick(选择):
将最近装入编辑窗口的8个文件列成一个表让用户选择,选择后将该程序装入编辑区,并将光标置在上次修改过的地方,其热键为Alt+F3。
(3)New(新文件):
说明文件是新的,默认文件名为NONAME.C,存盘时可改名。
(4)Save(存盘):
将编辑区中的文件存盘,当文件名是NONAME.C时,将询问是否更改文件名,其热键为F2。
(5)Writeto(存盘):
可由用户给出文件名将编辑区中的文件存盘。
(6)Directory(目录):
显示目录及目录中的文件,并可由用户选择。
(7)Changedir(改变目录):
显示当前目录,用户可以改变显示的目录。
(8)OSshell(暂时退出):
暂时退出TurboC2.5到DOS提示符下。
若想回到TurboC中,只要在DOS状态下输入exit即可。
(9)Quit(退出):
退出TurboC。
2.Edit(编辑)菜单
按Alt+E组合键可进入Edit菜单,若再按回车键,则光标出现在编辑窗口,此时用户可以进行文本编辑。
编辑方法基本与WordStar相同,可按F1键以获得有关编辑方法的帮助信息。
Edit命令简介如表1.1所示。
表1.1Edit命令简介表
PageUp
向前翻页
Ctrl+Q
查找TurboC2.5双界符的前匹配符
PageDn
向后翻页
Ctrl+KP
块文件打印
Home
End
将光标移到所在行的开始
将光标移到所在行的结尾
Ctrl+F1
如果光标所在处为TurboC2.5库函数,则获得有关该函数的帮助信息
Ctrl+Y
删除光标所在的一行
Ctrl+KC
块拷贝
Ctrl+T
删除光标所在处的一个词
Ctrl+KY
块删除
Ctrl+KB
设置块开始
Ctrl+KR
读文件
Ctrl+KK
设置块结尾
Ctrl+KW
存文件
Ctrl+Q
查找TurboC2.5双界符的后匹配符
3.Run(运行)菜单
按Alt+R键可进入Run菜单,该菜单包括以下各项。
(1)Run(运行程序):
运行由Project/Projectname项指定的文件或当前编辑区的文件。
如果对上次编译后的源代码未做过修改,则直接运行到下一个断点(没有断点则运行到结束),否则要先进行编译、连接后才能运行,其热键为Ctrl+F9。
(2)Programreset(程序重启):
中止当前的调试,释放分给程序的空间,其热键为Ctrl+F2。
(3)Gotocursor(运行到光标处):
调试程序时使用,选择该项可使程序运行到光标所在行。
光标所在行必须为一条可执行语句,否则提示错误,其热键为F4。
(4)Traceinto(跟踪进入):
在执行一条调用其他用户定义的子函数时,若用Traceinto项,则执行长条将跟踪到该子函数内部去执行,其热键为F7。
(5)Stepover(单步执行):
执行当前函数的下一条语句,即使用户使用函数调用,执行长条也不会跟踪进函数内部,其热键为F8。
(6)Userscreen(用户屏幕):
显示程序运行时在屏幕上显示的结果,其热键为Alt+F5。
4.Compile(编译)菜单
按Alt+C键可进入Compile菜单,该菜单包括以下内容。
(1)Compiletoobj(编译生成目标码):
将一个C源文件编译生成.obj目标文件,同时显示生成的文件名,其热键为Alt+F9。
(2)Makeexefile(生成可执行文件):
此命令生成一个.exe的文件,并显示生成的.exe文件名,其中.exe文件名是下面几项之一。
由Project/Projectname说明的项目文件名。
若没有项目文件名,则是由PrimaryCfile说明的源文件名。
若以上两项都没有文件名,则为当前窗口的文件名。
(3)Linkexefile(连接生成可执行文件):
把当前.obj文件及库文件连接在一起生成.exe文件。
(4)Buildall(建立所有文件):
重新