数据结构(精讲)----绪论

本篇起,作者会出一期关于数据结构的详细介绍,有感兴趣的小伙伴们,可以关注作者,方便查看后续内容。

如果有需要。也可以联系作者邮箱:mumyi0516@163.com

第一章 绪论

自1946年第一台计算机问世以来,计算机产业的飞速发展已远远超出人们对它的预料,在某些生产线上,甚至几秒钟就能生产出一台微型计算机,产量猛增,价格低廉,这就使得它的应用范围迅速扩展。如今,计算机已深人到人类社会的各个领域。计算机的应用已不再局限于科学计算,而更多地用于控制、管理及数据处理等非数值计算的处理工作。与此相应,计算机加工处理的对象由纯粹的数值发展到字符、表格和图像等各种具有一定结构的数据,这就给程序设计带来一些新的问题。为了编写出一个"好"的程序,必须分析待处理的对象的特性以及各处理对象之间存在的关系。这就是"数据结构"这门学科形成和发展的背景。

1.1 什么是数据结构

一般来说,用计算机解决一个具体问题时,大致需要经过下列几个步骤:首先要从具体问题抽象出一个适当的数学模型,然后设计一个解此数学模型的算法,最后编出程序进行测试、调整直至得到最终解答。寻求数学模型的实质是分析问题,从中提取操作的对象,并找出这些操作对象之间含有的关系,然后用数学的语言加以描述。例如,求解梁架结构中应力的数学模型为线性方程组;预报人口增长情况的数学模型为微分方程。然而,更多的非数值计算问题无法用数学方程加以描述。下面请看3个例子。

例1-1 图书馆的书目检索系统自动化问题。当你想借阅一本参考书但不知道书库中是否有的时候;或者,当你想找某一方面的参考书而不知图书馆内有哪些这方面的书时,你都需要到图书馆去查阅图书目录卡片。在图书馆内有各种名目的卡片:有按书名编排的,有按作者编排的,还有按分类编排的,等等。若利用计算机实现自动检索,则计算机处理的对象便是这些目录卡片上的书目信息。列在一张卡片上的一本书的书目信息可由登录号、书名、作者名、分类号、出版单位和出版时间等若干项组成,每一本书都有惟一的一个登录号,但不同的书目之间可能有相同的书名,或者有相同的作者名,或者有相同的分类号。由此,在书目自动检索系统中可以建立一张按登录号顺序排列的书目文件和3张分别按书名、作者名和分类号顺序排列的索引表,如图1.1所示。由这4张表构成的文件便是书目自动检索的数学模型,计算机的主要操作便是按照某个特定要求(如给定书名)对书目文件进行查询。诸如此类的还有查号系统自动化、仓库账目管理等。在这类文档管理的数学模型中,计算机处理的对象之间通常存在着的是一种最简单的线性关系,这类数学模型可称为线性的数据结构。

例1-2计算机和人对弈问题。计算机之所以能和人对弈是因为有人将对的策略事先已存入计算机。由于对弈的过程是在一定规则下随机进行的,所以,为使计算机能灵活对弈就必须对对弈过程中所有可能发生的情况以及相应的对策都考虑周全,并且,一个

"好"的棋手在对弈时不仅要看棋盘当时的状态,还应能预测棋局发展的趋势,甚至最后结局。因此,在对弈问题中,计算机操作的对象是对弈过程中可能出现的棋盘状态--称为格局。例如图 1.2(a)所示为井字棋Ф的一个格局,而格局之间的关系是由比赛规则决定的。通常,这个关系不是线性的,因为从一个棋盘格局可以派生出几个格局,例如从图1.2(a)所示的格局可以派生出5个格局,如图1.2(b)所示,而从每一个新的格局又可派生出4个可能出现的格局。因此,若将从对开始到结束的过程中所有可能出现的格局都画在一张图上,则可得到一棵倒长的"树"。"树根"是对弈开始之前的棋盘格局,而所有的"叶子"就是可能出现的结局,对弈的过程就是从树根沿树权到某个叶子的过程。"树"可以是某些非数值计算问题的数学模型,它也是一种数据结构。

例 1-3 多叉路口交通灯的管理问题。通常,在十字交叉路口只需设红、绿两色的交通灯便可保持正常的交通秩序,而在多叉路口需设几种颜色的交通灯才能既使车辆相互之间不碰撞,又能达到车辆的最大流通。假设有一个如图1.3(a)所示的五叉路口,其中C和E为单行道。在路口有13条可行的通路,其中有的可以同时通行,如A→B和E→C,而有的不能同时通行,如E->B和A→D。那么,在路口应如何设置交通灯进行车辆的管理呢?

通常,这类交通、道路问题的数学模型是一种称为"图"的数据结构。例如在此例的问题中,可以图中一个顶点表示一条通路,而通路之间互相矛盾的关系以两个顶点之间的连线表示。如在图1.3(b)中,每个圆圈表示图1.3(a)所示五叉路口上的一条通路,两个圆圈之间的连线表示这两个圆圈表示的两条通路不能同时通行。设置交通灯的问题等价为对图的顶点的染色问题,要求对图上的每个顶点染一种颜色,并且要求有线相连的两个顶点不能具有相同颜色,而总的颜色种类应尽可能地少。图1.3(b)所示为一种染色结果,圆圈中的数字表示交通灯的不同颜色,例如3号色灯亮时只有D→A和D→B两条路可通行。

综上3个例子可见,描述这类非数值计算问题的数学模型不再是数学方程,而是诸如表、树和图之类的数据结构。因此,简单说来,数据结构是一门研究非数值计算的程序设计问题中计算机的操作对象以及它们之间的关系和操作等的学科。

"数据结构"作为一门独立的课程在国外是从1968年才开始设立的。在这之前,它的某些内容曾在其他课程,如表处理语言中有所阐述。1968年在美国一些大学的计算机系的教学计划中,虽然把"数据结构"规定为一门课程,但对课程的范围仍没有作明确规定。当时,数据结构几乎和图论,特别是和表、树的理论为同义语。随后,数据结构这个概念被扩充到包括网络、集合代数论、格、关系等方面,从而变成了现在称之为《离散结构》的内容。然而,由于数据必须在计算机中进行处理,因此,不仅考虑数据本身的数学性质,而且还必须考虑数据的存储结构,这就进一步扩大了数据结构的内容。近年来,随着数据库系统的不断发展,在"数据结构"课程中又增加了文件管理(特别是大型文件的组织等)的内容。

1968年美国唐·欧·克努特教授开创了"数据结构"的最初体系,他所著的《计算机程序设计技巧》第一卷《基本算法》是第一本较系统地阐述数据的逻辑结构和存储结构及其操作的著作。从20世纪60年代末到70年代初,出现了大型程序,软件也相对独立,结构程序设计成为程序设计方法学的主要内容,人们就越来越重视"数据结构",认为程序设计的实质是对确定的问题选择一种好的结构,加上设计一种好的算法。从20世纪70年代中期到80年代初,各种版本的数据结构著作就相继出现。

"数据结构"在计算机科学中是一门综合性的专业基础课。"数据结构"的研究不仅涉及到计算机硬件(特别是编码理论、存储装置和存取方法等)的研究范围,而且和计算机软件的研究有着更密切的关系,无论是编译程序还是操作系统,都涉及到数据元素在存储器中的分配问题。在研究信息检索时也必须考虑如何组织数据,以便查找和存取数据元素更为方便。因此,可以认为"数据结构"是介于数学、计算机硬件和计算机软件三者之间的一门核心课程(如图 1.4所示)。在计算机科学中,'数据结构"不仅是一般程序设计(特别是非数值计算的程序设计)的基础,而且是设计和实现编译程序、操作系统、数据库系统及其他系统程序和大型应用程序的重要基础。

值得注意的是,"数据结构"的发展并未终结,一方面,面向各专门领域中特殊问题的数据结构得到研究和发展,如多维图形数据结构等;另一方面,从抽象数据类型的观点来讨论数据结构,已成为一种新的趋势,越来越被人们所重视。

1.2 基本概念和术语

在本节中,我们将对一些概念和术语赋以确定的含义,以便与读者取得"共同的语言"。这些概念和术语将在以后的章节中多次出现。

数据 (data)是对客观事物的符号表示,在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称。它是计算机程序加工的"原料"。例如,一个利用数值分析方法解代数方程的程序,其处理对象是整数和实数:一个编译程序或文字处理程序的处理对象是字符串。因此,对计算机科学而言,数据的含义极为广泛,如图像、声音等都可以通过编码而归之于数据的范畴。

数据元素(data element)是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。例如,例1-2中的"树"中的一个棋盘格局,例1-3中的"图"中的一个圆圈都被称为一个数据元素。有时,一个数据元素可由若干个数据项(dataitem)组成,例如,例1-1中一本书的书目信息为一个数据元素,而书目信息中的每一项(如书名、作者名等)为一个数据项。数据项是数据的不可分割的最小单位。

数据对象(dataobject)是性质相同的数据元素的集合,是数据的一个子集。例如,整数数据对象是集合N={0,士1,士2,...),字母字符数据对象是集合C={'A','B',...,'Z'}。

数据结构(data structure)是相互之间存在一种或多种特定关系的数据元素的集合。这是本书对数据结构的一种简单解释。从1.1节中3个例子可以看到,在任何问题中,数据元素都不是孤立存在的,而是在它们之间存在着某种关系,这种数据元素相互之间的关系称为结构(structure)。根据数据元素之间关系的不同特性,通常有下列4类基本结构:(1)集合结构中的数据元素之间除了"同属于一个集合"的关系外,别无其他关系@;(2)线性结构结构中的数据元素之间存在一个对一个的关系;(3)树形结构结构中的数据元素之间存在一个对多个的关系;(4)图状结构或网状结构结构中的数据元素之间存在多个对多个的关系。图1.5为上述4类基本结构的关系图。由于"集合"是数据元素之间关系极为松散的一种结构,因此也可用其他结构来表示它。

数据结构的形式定义为:数据结构是一个二元组Data_Structure=(D,S)其中:D是数据元素的有限集,S是D上关系的有限集。下面举两个简单例子说明之。

例1-4 在计算机科学中,复数可取如下定义:复数是一种数据结构

Complex=(C,R)

其中:C是含两个实数的集合(c1,c2);R=P},而P是定义在集合C上的一种关系{〈c1,c2〉},其中有序偶(c1,c2>表示c1是复数的实部,c2是复数的虚部。

例1-5 假设我们需要编制一个事务管理的程序,管理学校科学研究课题小组的各项事务,则首先要为程序的操作对象--课题小组设计一个数据结构。假设每个小组由1位教师、1~3名研究生及1~6名本科生组成,小组成员之间的关系是:教师指导研究生,而由每位研究生指导一至两名本科生。则可以如下定义数据结构:

Group=(P,R)

上述数据结构的定义仅是对操作对象的一种数学描述,换句话说,是从操作对象抽象出来的数学模型。结构定义中的"关系"描述的是数据元素之间的逻辑关系,因此又称为数据的逻辑结构。然而,讨论数据结构的目的是为了在计算机中实现对它的操作,因此还需研究如何在计算机中表示它。

数据结构在计算机中的表示(又称映像)称为数据的物理结构,又称存储结构。它包括数据元素的表示和关系的表示。在计算机中表示信息的最小单位是二进制数的一位,叫做位(bit)。在计算机中,我们可以用一个由若干位组合起来形成的一个位串表示一个数据元素(如用一个字长的位串表示一个整数,用8位二进制数表示一个字符等),通常称这个位串为元素(element)或结点(node)。当数据元素由若干数据项组成时,位串中对应于各个数据项的子位串称为数据域(datafield)。因此,元素或结点可看成是数据元素在计算机中的映像。

数据元素之间的关系在计算机中有两种不同的表示方法:顺序映像和非顺序映像,并由此得到两种不同的存储结构:顺序存储结构和链式存储结构。顺序映像的特点是借助元素在存储器中的相对位置来表示数据元素之间的逻辑关系。例如,假设用两个字长的位串表示一个实数,则可以用地址相邻的4个字长的位串表示一个复数,如图1.6(a)为表示复数 z1=3.0-2.3i和z2=-0.7+4.8i的顺序存储结构;非顺序映像的特点是借助指示元素存储地址的指针(pointer)表示数据元素之间的逻辑关系,如图1.6(b)为表示复数z1的链式存储结构,其中实部和虚部之间的关系用值为"0415"的指针来表示(0415 是虚部的存储地址)@。数据的逻辑结构和物理结构是密切相关的两个方面,以后读者会看到,任何一个算法的设计取决于选定的数据(逻辑)结构,而算法的实现依赖于采用的存储结构。

如何描述存储结构呢。虽然存储结构涉及数据元素及其关系在存储器中的物理位置,但由于本书是在高级程序语言的层次上讨论数据结构的操作,因此不能如上那样直接以内存地址来描述存储结构,但我们可以借用高级程序语言中提供的"数据类型"来描述它,例如可以用所有高级程序语言中都有的"一维数组"类型来描述顺序存储结构,以C语言提供的"指针"来描述链式存储结构。假如我们把C语言看成是一个执行C指令和数据类型的虚拟处理器,那么本书中讨论的存储结构是数据结构在C虚拟处理器中的表示,不妨称它为虚拟存储结构

数据类型 (datatype)是和数据结构密切相关的一个概念,它最早出现在高级程序语言中,用以刻画(程序)操作对象的特性。在用高级程序语言编写的程序中,每个变量、常量或表达式都有一个它所属的确定的数据类型。类型明显或隐含地规定了在程序执行期间变量或表达式所有可能取值的范围,以及在这些值上允许进行的操作。因此数据类型是一个值的集合和定义在这个值集上的一组操作的总称。例如,C语言中的整型变量,其值集为某个区间上的整数(区间大小依赖于不同的机器),定义在其上的操作为加、减、乘、除和取模等算术运算。

按"值"的不同特性,高级程序语言中的数据类型可分为两类:一类是非结构的原子类型。原子类型的值是不可分解的,例如C语言中的基本类型(整型、实型、字符型和枚举类型)、指针类型和空类型。另一类是结构类型。结构类型的值是由若干成分按某种结构组成的,因此是可以分解的,并且它的成分可以是非结构的,也可以是结构的。例如数组的值由若千分量组成,每个分量可以是整数,也可以是数组等。在某种意义上,数据结构可以看成是"一组具有相同结构的值",则结构类型可以看成由一种数据结构和定义在其上的一组操作组成。

实际上,在计算机中,数据类型的概念并非局限于高级语言中,每个处理器Ф(包括计算机硬件系统、操作系统、高级语言、数据库等)都提供了一组原子类型或结构类型。例如,一个计算机硬件系统通常含有"位"、"字节"、"字"等原子类型,它们的操作通过计算机设计的一套指令系统直接由电路系统完成,而高级程序语言提供的数据类型,其操作需通过编译器或解释器转化成低层,即汇编语言或机器语言的数据类型来实现。引人"数据类型"的目的,从硬件的角度看,是作为解释计算机内存中信息含义的一种手段,而对使用数据类型的用户来说,实现了信息的隐蔽,即将一切用户不必了解的细节都封装在类型中。例如,用户在使用"整数"类型时,既不需要了解"整数"在计算机内部是如何表示的,也不需要知道其操作是如何实现的。如"两整数求和",程序设计者注重的仅仅是其"数学上求和"的抽象特性,而不是其硬件的"位"操作如何进行。

抽象数据类型(Abstract Data Type,简称 ADT)是指一个数学模型以及定义在该模型上的一组操作。抽象数据类型的定义仅取决于它的一组逻辑特性,而与其在计算机内部如何表示和实现无关,即不论其内部结构如何变化,只要它的数学特性不变,都不影响其外部的使用。

抽象数据类型和数据类型实质上是一个概念。例如,各个计算机都拥有的"整数"类型是一个抽象数据类型,尽管它们在不同处理器上实现的方法可以不同,但由于其定义的数学特性相同,在用户看来都是相同的。因此,"抽象"的意义在于数据类型的数学抽象特性。

另一方面,抽象数据类型的范畴更广,它不再局限于前述各处理器中已定义并实现的数据类型(也可称这类数据类型为固有数据类型),还包括用户在设计软件系统时自己定义的数据类型。为了提高软件的复用率,在近代程序设计方法学中指出,一个软件系统的框架应建立在数据之上,而不是建立在操作之上(后者是传统的软件设计方法所为)。即在构成软件系统的每个相对独立的模块上,定义一组数据和施于这些数据上的一组操作,并在模块内部给出这些数据的表示及其操作的细节,而在模块外部使用的只是抽象的数据和抽象的操作。显然,所定义的数据类型的抽象层次越高,含有该抽象数据类型的软件模块的复用程度也就越高。

一个含抽象数据类型的软件模块通常应包含定义、表示和实现3个部分。如前所述,抽象数据类型的定义由一个值域和定义在该值域上的一组操作组成。若按其值的不同特性,可细分为下列3种类型:

原子类型 (atomic data type)属原子类型的变量的值是不可分解的。这类抽象数据类型较少,因为一般情况下,已有的固有数据类型足以满足需求。但有时也有必要定义新的原子数据类型,例如数位为100的整数。

固定聚合类型 (fixed-aggregate data type)属该类型的变量,其值由确定数目的成分按某种结构组成。例如,复数是由两个实数依确定的次序关系构成。

可变聚合类型 (variable-aggregate data type)和固定聚合类型相比较,构成可变聚合类型"值"的成分的数目不确定。例如,可定义一个"有序整数序列"的抽象数据类型,其中序列的长度是可变的。

显然,后两种类型可统称为结构类型。

和数据结构的形式定义相对应,抽象数据类型可用以下三元组表示

(D,S,P)

其中,D是数据对象,S是D上的关系集,P是对D的基本操作集。本书采用以下格式定义抽象数据类型:

其中,数据对象和数据关系的定义用伪码描述,基本操作的定义格式为

基本操作有两种参数:赋值参数只为操作提供输入值;引用参数以&打头,除可提供输人值外,还将返回操作结果。"初始条件"描述了操作执行之前数据结构和参数应满足的条件,若不满足,则操作失败,并返回相应出错信息。"操作结果"说明了操作正常完成之后,数据结构的变化状况和应返回的结果。若初始条件为空,则省略之。

例1-6抽象数据类型三元组的定义:

多形数据类型(polymorphic data type)是指其值的成分不确定的数据类型。例如,例1-6中定义的抽象数据类型Triplet,其元素e1、e2和e3可以是整数或字符或字符串,甚至更复杂地由多种成分构成(只要能进行关系运算即可)。然而,不论其元素具有何种特性,元素之间的关系相同,基本操作也相同。从抽象数据类型的角度看,具有相同的数学抽象特性,故称之为多形数据类型。显然,需借助面向对象的程序设计语言如C++等实现之。本书中讨论的各种数据类型大多是多形数据类型,限于本书采用类C语言作为描述工具,故只讨论含有确定成分的数据元素的情况。如例1-6中的ElemSet是某个确定的、将由用户自行定义的、含某个关系运算的数据对象。

1.3 抽象数据类型的表示与实现

抽象数据类型可通过固有数据类型来表示和实现,即利用处理器中已存在的数据类型来说明新的结构,用已经实现的操作来组合新的操作。由于本书在高级程序设计语言的虚拟层次上讨论抽象数据类型的表示和实现,并且讨论的数据结构及其算法主要是面向读者,故采用介于伪码和C语言之间的类C语言作为描述工具,有时也用伪码描述-些只含抽象操作的抽象算法。这使得数据结构与算法的描述和讨论简明清晰,不拘泥于C语言的细节,又能容易转换成C或者C++程序。

本书采用的类C语言精选了C语言的一个核心子集,同时做了若干扩充修改,增强了语言的描述功能。以下对其作简要说明。

(1)预定义常量和类型:

(2)数据结构的表示(存储结构)用类型定义(typedef)描述。数据元素类型约定为ElemType,由用户在使用该数据类型时自行定义。

(3)基本操作的算法都用以下形式的函数描述:

除了函数的参数需要说明类型外,算法中使用的辅助变量可以不作变量说明,必要时对其作用给予注释。一般而言,a、b、c、d、e等用作数据元素名,i、j、k、1、m、n等用作整型变量名,P、9、r等用作指针变量名。当函数返回值为函数结果状态代码时,函数定义为Status类型。为了便于算法描述,除了值调用方式外,增添了C++语言的引用调用的参数传递方式。在形参表中,以&打头的参数即为引用参数。

(4)赋值语句有

(5)选择语句有

(6)循环语句有

(7)结束语句有

(8)输入和输出语句有

(9)注释

(10)基本函数有

(11)逻辑运算约定

例1-7抽象数据类型 Triplet 的表示和实现

1.4 算法和算法分析

1.4.1 算法

算法 (algorithm)是对特定问题求解步骤的一种描述,它是指令的有限序列,其中每一条指令表示一个或多个操作;此外,一个算法还具有下列5个重要特性:

(1)有穷性: 一个算法必须总是(对任何合法的输人值)在执行有穷步之后结束,且每一步都可在有穷时间Ф内完成。

(2)确定性: 算法中每一条指令必须有确切的含义,读者理解时不会产生二义性。并且,在任何条件下,算法只有惟一的一条执行路径,即对于相同的输入只能得出相同的输出。

(3)可行性: 一个算法是能行的,即算法中描述的操作都是可以通过已经实现的基本运算执行有限次来实现的。

(4)**输入:**一个算法有零个或多个的输人,这些输入取自于某个特定的对象的集合。

(5)**输出:**一个算法有一个或多个的输出,这些输出是同输人有着某些特定关系的量。

1.4.2 算法设计的要求

通常设计一个"好"的算法应考虑达到以下目标。

(1)正确性: (correctness)算法应当满足具体问题的需求。通常一个大型问题的需求,要以特定的规格说明方式给出,而一个实习问题或练习题,往往就不那么严格,目前多数是用自然语言描述需求,它至少应当包括对于输人、输出和加工处理等的明确的无歧义性的描述。设计或选择的算法应当能正确地反映这种需求:否则,算法的正确与否的衡量准则就不存在了。

"正确"一词的含义在通常的用法中有很大差别,大体可分为以下4个层次:a.程序不含语法错误;b.程序对于几组输入数据能够得出满足规格说明要求的结果;c.程序对于精心选择的典型、苛刻而带有刁难性的几组输入数据能够得出满足规格说明要求的结果;d.程序对于一切合法的输人数据都能产生满足规格说明要求的结果。显然,达到第d层意义下的正确是极为困难的,所有不同输人数据的数量大得惊人,逐一验证的方法是不现实的。对于大型软件需要进行专业测试,而一般情况下,通常以第c层意义的正确性作为衡量一个程序是否合格的标准。

(2)可读性:(readability)算法主要是为了人的阅读与交流,其次才是机器执行。可读性好有助于人对算法的理解;晦涩难懂的程序易于隐藏较多错误,难以调试和修改。

(3)健壮性: (robustness)当输人数据非法时,算法也能适当地做出反应或进行处理,而不会产生莫明其妙的输出结果。例如,一个求凸多边形面积的算法,是采用求各三角形面积之和的策略来解决问题的。当输人的坐标集合表示的是一个凹多边形时,不应继续计算,而应报告输入出错。并且,处理出错的方法应是返回一个表示错误或错误性质的值,而不是打印错误信息或异常,并中止程序的执行,以便在更高的抽象层次上进行处理。

(4)**效率与低存储量需求:**通俗地说,效率指的是算法执行的时间。对于同一个问题如果有多个算法可以解决,执行时间短的算法效率高。存储量需求指算法执行过程中所需要的最大存储空间。效率与低存储量需求这两者都与问题的规模有关。求100个人的平均分与求1000个人的平均分所花的执行时间或运行空间显然有一定的差别。

1.4.3 算法效率和度量

算法执行时间需通过依据该算法编制的程序在计算机上运行时所消耗的时间来度量。而度量一个程序的执行时间通常有两种方法,

(1)事后统计的方法:因为很多计算机内部都有计时功能,有的甚至可精确到毫秒级,不同算法的程序可通过一组或若干组相同的统计数据以分辨优劣。但这种方法有两个缺陷:一是必须先运行依据算法编制的程序;二是所得时间的统计量依赖于计算机的硬件、软件等环境因素,有时容易掩盖算法本身的优劣。因此人们常常采用另一种事前分析估算的方法

(2)事前分析估算的方法:一个用高级程序语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:

  1. 依据的算法选用何种策略;

  2. 问题的规模,例如求100以内还是 1000 以内的素数;

  3. 书写程序的语言,对于同一个算法,实现语言的级别越高,执行效率就越低;

  4. 编译程序所产生的机器代码的质量;

  5. 机器执行指令的速度

显然,同一个算法用不同的语言实现,或者用不同的编译程序进行编译,或者在不同的计算机上运行时,效率均不相同。这表明使用绝对的时间单位衡量算法的效率是不合适的撇开这些与计算机硬件、软件有关的因素,可以认为一个特定算法"运行工作量"的大小,只依赖于问题的规模(通常用整数量n表示),或者说,它是问题规模的函数。

一个算法是由控制结构(顺序、分支和循环3种)和原操作(指固有数据类型的操作)构成的,则算法时间取决于两者的综合效果。为了便于比较同一问题的不同算法,通常的做法是,从算法中选取一种对于所研究的问题(或算法类型)来说是基本操作的原操作,以该基本操作重复执行的次数作为算法的时间量度。

例如,在如下所示的两个NXN矩阵相乘的算法中,乘法"运算是"矩阵相乘问题"的基本操作。整个算法的执行时间与该基本操作(乘法)重复执行的次数"成正比,记作

一般情况下,算法中基本操作重复执行的次数是问题规模"的某个函数f(n),算法的时间量度记作

它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称做算法的渐近时间复杂度 (asymptotic time complexity),简称时间复杂度

显然,被称做问题的基本操作的原操作应是其重复执行次数和算法的执行时间成正比的原操作,多数情况下它是最深层循环内的语句中的原操作,它的执行次数和包含它的语句的频度相同。语句的频度(frequencycount)指的是该语句重复执行的次数,例如,在下列3个程序段中:

含基本操作"x增1"的语句的频度分别为1、n和n",则这3个程序段的时间复杂度分别为 O(1)、O(n)和O(n),分别称为常量阶、线性阶和平方阶。算法还可能呈现的时间复杂度有对数阶 O(log n)、指数阶O(2")等。不同数量级时间复杂度的性状如图1.7所示从图中可见,我们应该尽可能选用多项式阶O(n)的算法,而不希望用指数阶的算法。一般情况下,对一个问题(或一类算法)只需选择一种基本操作来讨论算法的时间复杂度即可,有时也需要同时考虑几种基本操作,甚至可以对不同的操作赋予不同权值,以反映执行不同操作所需的相对时间,这种做法便于综合比较解决同一问题的两种完全不同的算法。

由于算法的时间复杂度考虑的只是对于问题规模n的增长率,则在难以精确计算基本操作执行次数(或语句频度)的情况下,只需求出它关于"的增长率或阶即可。例如,在下列程序段中:

语句++x的执行次数关于n的增长率为",它是语句频度表达式(n-1)(n-2)/2中增长最快的项。

有的情况下,算法中基本操作重复执行的次数还随问题的输入数据集不同而不同例如在下列起泡排序的算法中:

"交换序列中相邻两个整数"为基本操作。当a中初始序列为自小至大有序,基本操作的执行次数为0;当初始序列为自大至小有序时,基本操作的执行次数为(n一1)/2。对这类算法的分析,一种解决的办法是计算它的平均值,即考虑它对所有可能的输入数据集的期望值,此时相应的时间复杂度为算法的平均时间复杂度。如假设a中初始输入数据可能出现n!种的排列情况的概率相等,则起泡排序的平均时间复杂度T(n)=O(n'),然而,在很多情况下,各种输人数据集出现的概率难以确定,算法的平均时间复杂度也就难以确定。因此,另一种更可行也更常用的办法是讨论算法在最坏情况下的时间复杂度,即分析最坏情况以估算算法执行时间的一个上界。例如,上述起泡排序的最坏情况为a中初始序列为自大至小有序,则起泡排序算法在最坏情况下的时间复杂度为T(n)=O(㎡')。在本书以后各章中讨论的时间复杂度,除特别指明外,均指最坏情况下的时间复杂度。

实践中我们可以把事前估算和事后统计两种办法结合起来使用。以两个矩阵相乘为例,若上机运行两个10x10的矩阵相乘,执行时间为12ms,则由算法的时间复杂度T(n)=〇(n)可估算两个 31x31的矩阵相乘所需时间大致为(31/10)3。12ms≈358ms。

1.4.4 算法的存储空间需求

类似于算法的时间复杂度,本书中以空间复杂度(spacecomplexity)作为算法所需存储空间的量度,记作

其中n为问题的规模(或大小)。一个上机执行的程序除了需要存储空间来寄存本身所用指令、常数、变量和输入数据外,也需要一些对数据进行操作的工作单元和存储一些为实现计算所需信息的辅助空间。若输入数据所占空间只取决于问题本身,和算法无关,则只需要分析除输人和程序之外的额外空间,否则应同时考虑输入本身所需空间(和输入数据的表示形式有关)。若额外空间相对于输人数据量来说是常数,则称此算法为原地工作,第10章讨论的有些排序算法就属于这类。又如果所占空间量依赖于特定的输人,则除特别指明外,均按最坏情况来分析。

相关推荐
稳兽龙6 分钟前
P7865 「EVOI-RD1」无人机航拍( ( [主题训练B1]线段树 ) 第四题)[ 采用高级二维差分数组 ]
c++·算法·二维差分数组·线段树区间修改
7yewh1 小时前
嵌入式知识点总结 C/C++ 专题提升(一)-关键字
c语言·开发语言·c++·嵌入式硬件·物联网·算法
ALISHENGYA1 小时前
用Python实现SVM搭建金融反诈模型(含调试运行)
算法·机器学习·支持向量机·svm
keep intensify2 小时前
c语言分支和循环
c语言·开发语言·算法
程序趣谈3 小时前
算法随笔_13: 有效三角形的个数
算法
code monkey.4 小时前
【探寻C++之旅】第二章:类和对象(上)
c++·算法
骑狗看夕阳4 小时前
Tarjan算法笔记
笔记·算法
大桔骑士v4 小时前
【算法学习笔记】35:扩展欧几里得算法求解线性同余方程
算法·acm·数论·扩展欧几里得算法·线性同余方程
大桔骑士v4 小时前
【算法学习笔记】34:扩展欧几里得算法
算法·acm·数论·最大公约数·扩展欧几里得算法
pursuit_csdn5 小时前
LeetCode 2661. First Completely Painted Row or Column
算法·leetcode