[C语言基础] 第2章 算法的概念
2.1 什么是算法
广义地说,为解决一个问题而采取的方法和步骤,就称为“算法”。
计算机算法可分为两大类别:数值运算算法和非数值运算算法。
数值运算的目的是求数值解,例如求方程的根、求一个函数的定积分等,都属于数值运算范围。
非数值运算涉及的面十分广泛,最常见的是用于事务管理领域,例如对一批职工按姓名排序、图书检索、人事管理和行车调度管理等。
目前,计算机在非数值运算方面的应用远远超过了在数值运算方面的应用。
2.1.1 算法的特性
一个有效算法应该具有以下特点:
-
有穷性。一个算法应包含有限的操作步骤,而不能是无限的。“有穷性”往往指“在合理的范围之内”。
-
确定性。算法中的每一个步骤都应当是确定的,而不应当是含糊的﹑模棱两可的。也就是说,算法的含义应当是唯一的,而不应当产生“歧义性”。
-
有零个或多个输入。所谓输入是指在执行算法时需要从外界取得必要的信息。
-
有一个或多个输出。算法的目的是为了求解,“解”就是输出。没有输出的算法是没有意义的。
-
有效性。算法中的每一个步骤都应当能有效地执行,并得到确定的结果。
2.1.2 如何衡量一个算法的好坏
如何衡量一个算法的好坏:
算法效率的度量:时间复杂度和空间复杂度
-
正确性
算法的正确性是评价一个算法优劣的最重要的标准。正确性也是一个最基本的要求
-
可读性
算法的可读性是指一个算法可供人们阅读的容易程度。好的算法应该遵循标识符命名规则,简洁,易懂,注释语句恰当、适量,方便自己和他人阅读,便于后期调试和修改。
-
健壮性
-
健壮性是指一个算法对不合理数据输入的反应能力和处理能力,也称为容错性。一个好的算法应该具有较高的容错性,能够在各种情况下都能正确地执行任务,输出清晰的信息
-
时间复杂度
这是衡量算法执行速度的主要指标,表示随着输入数据规模的增大,算法执行时间的增长趋势。
一般来说,时间复杂度越小,算法的执行速度越快
-
空间复杂度
这是衡量算法内存消耗的指标,表示随着输入数据规模的增大,算法所需内存空间的增长趋势。
一般来说,空间复杂度越小,算法所需的内存空间越少
2.2 怎样表示一个算法
为了表示一个算法,可以用不同的方法。
常用的方法有:自然语言﹑传统流程图,结构化流程图和伪代码等。
2.2.1 用自然语言表示算法
自然语言就是人们日常使用的语言,可以是汉语、英语或其他语言。
用自然语言表示通俗易懂,但文字冗长,容易出现歧义。
自然语言表示的含义往往不大严格,要根据上下文才能判断其正确含义。
因此,除了那些很简单的问题以外,一般不用自然语言表示算法。
2.2.2 用流程图表示算法
流程图是用一些图框来表示各种操作。用图形表示算法,直观形象,易于理解。
美国国家标准化协会(American National Standard Institute,ANSI)规定了一些常用的流程图符号,已被世界各国程序工作者普遍采用。
连接点(小圆圈)是用于将画在不同地方的流程线连接起来。
它表示这两个点是连接在一起的,实际上它们是同一个点,只是画不下才分开来画。用连接点可以避免流程线交叉或过长,使流程图清晰。
注释框不是流程图中必要的部分,不反映流程和操作,只是为了对流程图中某些框的操作作必要的补充说明,以帮助阅读流程图的人更好地理解流程图的作用。
例如,用流程图来表示判定2000-2500年中每一年是否为闰年的算法:
Y代表yes(是),N代表no(否)
一个流程图包括以下几部分:
-
表示相应操作的框
-
带箭头的流程线
-
框内外必要的文字说明
流程线必须要画箭头,因为它是反映流程的先后的,如不画出箭头就难以判定各框的执行次序了。
缺点:这种流程图占用篇幅较多,尤其当算法比较复杂时,画流程图既费时又不方便。
2.2.3 三种基本结构
传统流程图的弊端:
传统的流程图用流程线指出各框的执行顺序,对流程线的使用没有严格限制。因此,使用者可以不受限制地使流程随意地转来转去,使流程图变得毫无规律,阅读时要花很大精力去追踪流程,使人难以理解算法的逻辑。
乱麻一样的算法称为BS型算法。
为了提高算法的质量,使算法的设计和阅读方便,必须限制箭头的滥用,即不允许无规律地使流程随意转向,只能顺序地进行下去。但是,算法上难免会包含一些分支和循环,而不可能全部由一个个顺序框组成。于是规定了几种基本结构。
三种基本结构:
1966年,Bohra和 Jacopini提出了以下3种基本结构,用这3种基本结构作为表示一个良好算法的基本单元。
-
顺序结构
如图2.14所示。
虚线框内是一个顺序结构。其中A和B两个框是顺序执行的。即:在执行完A框所指定的操作后,必然接着执行B框所指定的操作。顺序结构是最简单的一种基本结构。
-
选择结构
选择结构又称选取结构或分支结构,如图2.15所示。
虚线框内是一个选择结构。此结构中必包含一个判断框。根据给定的条件 p是否成立而选择执行A框或B框。 注意:无论 p条件是否成立,只能执行A框或B框之一,不可能既执行A框又执行B框。无论走哪一条路径,在执行完A或B之后,都经过 b点,然后脱离本选择结构。
A或B两个框中可以有一个是空的,即不执行任何操作,如图2.16所示。
-
循环结构
-
又称重复结构,即反复执行某一部分的操作。有两类循环结构。
-
①当型(while型)循环结构
当型循环结构如图(a)所示。
它的作用是:当给定的条件p1成立时,执行A框操作,执行完A后,再判断条件p1是否成立,如果仍然成立,再执行A框,如此反复执行A框,直到某一次p1条件不成立为止,此时不执行A框,而从b点脱离循环结构。
-
②直到型(until型)循环结构
直到型循环结构如图(b)所示。
它的作用是:先执行A框,然后判断给定的p2条件是否成立,如果p2条件不成立,则再执行A,然后再对p2条件作判断,如果p2条件仍然不成立,又执行A……如此反复执行A,直到给定的p2条件成立为止,此时不再执行A,从b点脱离本循环结构。
使用(until型)循环结构,无论p2条件如何,A框至少都会执行一次。
-
以上3种基本结构,有以下共同特点:
-
只有一个入口
-
只有一个出口
一个判断框有两个出口,而一个选择结构只有一个出口。不要将判断框的出口和选择结构的出口混淆。
-
结构内的每一部分都有机会被执行到
也就是说,对每一个框来说,都应当有一条从入口到出口的路径通过它。
-
结构内不存在“死循环”(无终止的循环)
基本结构并不局限于上述三种,只要具有上述4个特点都可以作为基本结构。例如下方的结构:
2.2.4 用N-S流程图表示算法
1973年,美国学者I.Nassi和 B.Shneiderman提出了一种新的流程图形式。
在这种流程图中,完全去掉了带箭头的流程线。全部算法写在一个矩形框内,在该框内还可以包含其他从属于它的框。
或者说,由一些基本的框组成一个大的框。这种流程图又称N-S结构化流程图(N和S是两位美国学者的英文姓氏的首字母)。
这种流程图适于结构化程序设计,因而很受欢迎。
N-S流程图(盒图)用以下的流程图符号:
-
顺序结构
顺序结构用图2.24形式表示。A和B两个框组成一个顺序结构。
-
选择结构
选择结构用图2.25表示。当p条件成立时执行A操作,p不成立则执行B操作。
-
循环结构
当型循环结构用图2.26形式表示,当p1条件成立时反复执行A操作,直到p1条件不成立为止。 直到型循环结构用图2.27形式表示。
在初学时,为清楚起见,可如图2.26和图2.27那样,写明“当pl成立”或“直到p2成立”。
待熟练之后,可以不写“当”和“直到”字样,只写“pl”和“p2”。从图的形状即可知道是当型还是直到型。
例如,用N-S流程图表示判定2000-2500年中每一年是否为闰年的算法
N-S图表示算法的优点:它比文字描述直观、形象、易于理解;比传统流程图紧凑易画,尤其是它废除了流程线。
整个算法结构是由各个基本结构按顺序组成的,N-S流程图中的上下顺序就是执行时的顺序,也就是图中位置在上面的先执行,位置在下面的后执行。写算法和看算法只须从上到下进行就可以了,十分方便。
用N-S图表示的算法都是结构化的算法(它不可能出现流程无规律的跳转,而只能自上而下地顺序执行)。
归纳起来可知:一个结构化的算法是由一些基本结构顺序组成的,在基本结构之间不存在向前或向后的跳转,流程的转移只存在于一个基本结构范围之内;一个非结构化的算法可以用一个等价的结构化算法代替,其功能不变。如果一个算法不能分解为若干个基本结构,则它必然不是一个结构化的算法。
2.2.5 用伪代码表示算法
用传统的流程图和N-S图表示算法直观易懂,但画起来比较费事,在设计一个算法时,可能要反复修改,而修改流程图是比较麻烦的。
因此,流程图适于表示一个算法,但在设计算法过程中使用不是很理想(尤其是当算法比较复杂、需要反复修改时)。
为了设计算法时方便,常用一种称为伪代码(pseudo code)的工具。
伪代码是用介于自然语言和计算机语言之间的文字和符号来描述算法。它如同一篇文章一样,自上而下地写下来。每一行(或几行)表示一个基本操作。它不用图形符号,因此书写方便,格式紧凑,修改方便,容易看懂,也便于向计算机语言算法(即程序)过渡。
用伪代码写算法并无固定的、严格的语法规则,可以用英文,也可以中英文混用。只要把意思表达清楚,便于书写和阅读即可,书写的格式要写成清晰易读的形式。
2.2.6 用计算机语言表示算法
分析问题后,可以根据每一种计算机语言的特点来选择采用哪种语言进行编写程序。
用计算机语言表示算法,必须严格遵循所用语言的语法规则。
保持良好习惯,必要的代码打上注释,方便阅读,变量名和函数名的命名要有规则有含义,方便阅读。
2.3 结构化程序设计方法
结构化程序就是用计算机语言表示的结构化算法,用3种基本结构组成的程序必然是结构化的程序。这种程序便于编写,阅读、修改和维护,这就减少了程序出错的机会,提高了程序的可靠性,保证了程序的质量。
结构化程序设计强调程序设计风格和程序结构的规范化,提倡清晰的结构。
结构化程序设计方法用来解决人脑思维能力的局限性和被处理问题的复杂性之间的矛盾。
结构化程序设计方法的基本思路是:把一个复杂问题的求解过程分阶段进行,每个阶段处理的问题都控制在人们容易理解和处理的范围内。具体说,采取以下方法来保证得到结构化的程序:
-
自顶向下
-
逐步细化
-
模块化设计
-
结构化编码
在接受一个任务后应怎样着手进行呢?有两种不同的方法:
-
自顶向下,逐步细化:就是先构思大体,在细化部分(推荐这种,方便构思程序)
-
自下而上,逐步积累:从细节入手,逐步向整体推进的认知过程。通过对局部的观察、了解和积累,逐渐建构出对整体的认知和判断。
程序中的子模块在C语言中通常用函数来实现。
程序中的子模块一般不超过50行,即把它打印输出时不超过一页,这样的规模便于组织,也便于阅读。
划分子模块时应注意模块的独立性,即使用一个模块完成一项功能,耦合性越少越好。