1.1 从问题到程序
用计算机实现问题求解,实质上就是在计算机中建立一个解决问题的模型。可以有不同的抽象形式 —— 容易被人理解但不太严格的需求模型、比较抽象但很精确的数学模型、容易被计算机理解或执行的实现模型
程序 是使用程序设计语言精确描述的实现模型,它是问题求解的一个可以在计算机上运行的模型。程序中描述的数据用来表示问题中涉及的对象,程序中描述的过程表示了对于数据处理的算法,通过接受实际问题的输入,经过程序的运行,便可以得到实际问题的一个解。
正确求解程序的建立,通常分为几个阶段:
- 分析阶段。这阶段的任务,首先是弄清用户的需求是什么,设计者根据它进行深入分析,使用规范说明语言(或数学语言等工具)给出系统的需求模型(或数学模型)
- 设计阶段。这阶段的任务是建立求解系统的实现模型,重点是算法的设计和数据结构的设计。对于大型的复杂的系统,这一阶段往往还包含抽象数据类型或者模块的设计。一般而言,设计过程需要从粗到细,经过多次精化才能确定。
- 编码阶段。主要任务包括使用适当的程序设计语言(C语言、C++ 语言或是Java语言等)把设计阶段的成果,编写成可执行的程序。
- 调试和维护。 任务包括使用足够的例子调试编写的程序,发现和排除程序代码中的错误。在计算机上执行程序,获得问题的解,也包括在系统投入运行后,解决在使用过程中发现的隐含错误和根据使用中提出的要求进行必要的维护和完善。
1.2 抽象数据类型
1.2.1 什么是抽象数据类型
类型
类型是一组值(或者对象)的集合。
数据类型
数据类型通常是指在计算机(语言)中可以使用的一个类型,它不但包括这个类型的值的集合,还包括定义在这个类型上的一组操作
抽象数据类型
抽象数据类型可以定义为具有一定行为(操作)的抽象(数学)类型。它不关系类型中值得具体表示方式和数据类型中定义的各种操作的具体实现方法,是所有可能的值得具体表示和各种操作的具体实现的抽象
1.2.2 意义与作用
抽象数据类型的实质是抽象出了数据类型的使用要求,而把它的具体表示方式和运算的实现细节都隐藏起来。从问题求解(或使用者)的观点来看,抽象数据类型仅仅规定了数据类型应该具有的行为(操作)。一旦抽象数据类型被正确实现,就好像程序设计语言中所提供的数据类型那样,可以被自由使用,问题求解的工作就可以在更加抽象的层次上,直接使用抽象数据类型提供的各种操作
抽象数据类型支持数据类型的实现与使用分离的原则,允许独立地考虑数据类型的外部接口和内部的实现。
1.3 数据结构
1.3.1 什么是数据结构
计算机中表示(存储)的、具有一定逻辑关系和行为特征的一组数据。其中每个数据元素称为这个结构的一个结点
根据面向对象的观点,可以把数据结构理解成为抽象数据类型的物理实现。一个是如何具体表示抽象数据类型中的数学模式,另一个是如何给出抽象数据类型中需要操作的具体实现
数据结构三要素:
- 逻辑结构。定义了数学模型中的基本元素(结点)和元素之间的相互关系
- 存储结构。定义了数学模型的具体表示方式,包括结点的表示和关系的表示。
- 操作。 给出抽象数据类型关系的各种行为在存储结构上的具体实现算法。
1.3.2 数据结构的分类
按逻辑结构分类
逻辑结构可以用二元组 B=<K,R>来表示,其中K是结点的有穷集合,R是K上的一个关系。所谓关系可以理解为“二元组的集合”。K上的二元组是K中元素的有序对,记为<k, k'>。K上的一个关系就是K上的一些二元组组成的集合,K上不同的二元组集合构成不同的关系。若k, k'∈K,<k, k'>∈R,则称k为k'的前驱,k'为k的后继。没有前驱的结点称为开始结点,没有后继的结点称为终端结点。根据R的特点可以将数据结构分为以下三类:
线性结构: K中每个结点最多只有一个前驱和一个后继的结构。
树形结构:K中每个结点最多只有一个前驱,但可有多个后继的结构。
复杂结构:K中结点的前驱、后继结点个数都不做限制的结构。
此外,还有一种特殊的但十分广泛应用的结构:集合。可以看成R为空的情况,但结点之间没有任何关系。
各种逻辑结构之间具有以下包含关系:
集合结构∈线性结构∈树形结构∈复杂结构。
按存储结构分类
存储结构有四种基本的表示方法:
顺序表示:用一个连续的空间,顺序存放数据结构中的各个节点。
连接表示:结点的存放位置是任意的,结点之间的关系通过与结点关联的指针(或引用)方式显示表达出来。
散列表示:又称关键码-地址转换法。即选择适当的散列(杂凑)函数,根据关键码的值将结点映射到给定的存储空间(散列表)中。
索引表示:索引与散列一样,都给出一种从关系码到存储地址的映射方法。散列法的映射是通过函数定义。而索引法是通过建立辅助的索引结构解决。
1.3.3 结点与结构
结点是数据结构中的基本单位。在某些结构中,又称为元素、记录或表目等。结点分为两大类:初等类型和组合类型。
初等类型
常用的初等类型有四种:
字符类型(char):取值为计算机可以表示的字符集的元素
整数类型(int):取值为计算机可以表示范围的整数
浮点数类型(float):取值为计算可以表示范围内的实数
布尔类型(boolean):取值为真(true)和假(false)
组合类型
组合类型是由初等类型用某种方式构造而成的。不同的构造方式形成不同的类型
1.3.4 外存数据的组织
在计算机中,通常把外存上的数据以某种方式组织成文件,通过专门的软件(文件系统)实现对它们的管理,把这些数据调入内存处理。
文件是逻辑记录的集合。逻辑记录是应用程序需要进行内外存交换的逻辑单位,每个记录可以包含若干数据项,其中能够唯一标识该记录的数据项称为关键码。
与内存不同,对外存的数据必须按页块存取。页块又称物理记录,是内存与外存进行交换的物理单位。
1.4 算法
1.4.1 什么是算法
算法的概念
算法(Algorithm)是由有穷规则构成的为解决某一类问题的运算序列(方法或过程)。算法可以有若干输入,这些输入是在算法开始时给出的初始值或条件。算法通常又有若干个输出,他们是桶输入有某种关系的计算结果。
算法的性质如下:
有穷性:
算法正确性
如果一个算法以一组满足初始条件的输入开始,那么该算法的执行一定终止,并且在终止时得到满足要求的(输出)结果
1.4.2 算法的设计
常用的算法设计的方法:
贪心法
基本思想是当追求的目标是一个问题的最优解时,设法把整个问题的求解工作分成若干步骤来完成。在其中的每一阶段都选择从局部看是最优的方案,以期望通过各阶段的局部最优选择达到整体的最优
分治法
基本思想是把一个规模较大的问题分成两个或多个较小的与原问题相似的子问题,首先对子问题进行求解,然后把各个子问题的解合并起来,得出整个问题的解,即对问题分而治之
回溯法
基本思想是采用一步一步向前试探的方法,当某一步有多种选择时,可以先任意选择一种,只要这种选择暂时可行就继续向前,一旦发现到达某步后无法再继续前景,说明前面已经做的选择可能有问题,就可以后退,回到上一步重新选择
动态规划法
与分治法相似,都是吧一个大问题分解为若干较小的子问题,通过求解子问题而得到源问题的解。不同点是:分治法每次分解的子问题数目较小,子问题之间界限清楚,处理的过程通常是自顶向下进行。动态规划法分子的子问题可能比较多,而且子问题相互包含,为了重用已经计算的结果,要把计算的中间结果保存起来,动态规划法通常是自底向上进行。
分支界限法
与回溯法相似,分支界限法(Branch and Bound) 也是一种在全部问题解的空间中进行系统搜索的方法。不同的是,回溯法使用了深度优先策略,而分支界限法可以采用广度优先策略,与此同时,分支界限法在搜索过程中,还利用最优解属性的上下界来控制搜索的分支,剪去不必再花时间搜索的部分,从而提高搜索的效率
1.4.3 算法的精化
实现一个算法,就是要把设计者头脑中的算法思想转化成计算机中可以执行的程序。对于一个比较复杂的算法,其实现过程往往需要经过多次细化才能完成。这个过程叫做算法的精化。
1.4.4 算法的分析
时间代价和空间代价
空间代价,指的是解决该问题的算法所占用的空间
时间代价,指的是算法所耗费的时间
代价的表示与计算
大O表示法
说某个算法的时间代价(空间代价)为O(f(n)),如果存在正的常数c和n0,但问题的规模 n >= n0后,该算法的时间(空间)代价 T(n) <= c * f(n)。这时也称该算法的时间(空间)代价的增长率为f(n)。这种说法意味着:当n充分大时,该算法的复杂性不大于f(n)的一个常数倍。
计算规则
根据大O表示法的函数,很容易推导出常用的计算规则,主要有一下几中:
- 加法规则
- 乘法规则