根据考试大纲,本章要求考生掌握以下知识点:
1.算法的基本概念、算法复杂度的概念和意义(时间复杂度与空间复杂度)。
2.数据结构的定义、数据的逻辑结构与存储结构、数据结构的图形表示、线性结构与非线性结构的概念。
3.线性表的定义、线性表的顺序存储结构及其插入与删除运算。
4.栈和队列的定义、栈和队列的顺序存储结构及其基本运算。
5.线性单链表、双向链表与循环链表的结构及其基本运算。
6.树的基本概念、二叉树的定义及其存储结构、二叉树的遍历(前序、中序和后序)。
7.顺序查找与二分法查找算法、基本排序算法(交换类排序、选择类排序、插入类排序)。
1.1 考点精讲
计算机已经被广泛用于数据处理。所谓数据处理,是指对数据集合中的各元素以各种方式进行操作,包括插入、删除、查找、更改等,也包括对数据元素进行分析。在进行数据处理时,实际需要处理的数据元素一般很多,而这些大量的数据元素都需要存放在计算机中,因此,大量的数据元素在计算机中如何组织,以便提高数据处理的效率,并且节省计算机的存储空间,这是进行数据处理的关键问题。
算法是一个十分古老的研究课题,然而计算机的出现为这个课题注入了青春和活力,使算法的设计和分析成为计算机学科中最为活跃的研究热点之一。针对实际问题,例如要编制一个高效率的处理程序,那就需要解决如何合理地组织数据,建立合适的数据结构,设计好的算法来提高程序执行的效率这样的问题。
1.1.1 算法
算法(algorithm)是对解题方案的准确而完整的描述。但它不等于程序,也不等于计算方法。通常,程序的编制不可能优于算法的设计。算法是指令的有限序列,也就是说,能够对一定规范的输入,在有限时间内获得所要求的输出。当然,程序也可以作为算法的一种描述,但程序通常还需考虑很多与方法和分析无关的细节问题,这是因为在编写程序时要受到计算机系统运行环境的限制。
1.算法的基本特征
一个算法,一般应具有以下几个基本特征:
1)可行性:算法的可行性,是指算法中指定的操作都可以通过基本运算执行有限的次数后实现。针对实际问题设计算法时必须考虑它的可行性,因为人们总是希望能够达到满意的结果。
2)确定性:算法的确定性,是指算法中的每一个步骤都必须是有明确定义的,不允许有模棱两可的解释,也不允许有多义性。
3)有穷性:算法的有穷性,是指一个算法必须在有限的时间内做完,即算法必须能在执行有限个步骤之后终止。另外,算法的有穷性还应包括合理的执行时间,如果一个算法需要执行很长时间甚至上千年才能终止,就失去了实用价值。
4)拥有足够的情报:一个算法是否有效,还取决于为算法所提供的情报是否足够,一般来说,当算法拥有足够的情报时,此算法才是有效的,否则,可能无效。
2.算法的基本要素
. 1)算法中对数据的运算和操作:每个算法实际上是按解题要求从环境能进行的所有操作中选择合适的操作所组成的一组指令序列。
计算机可以执行的基本操作是以指令的形式描述的。一个计算机系统能执行的所有指令的集合,称为该计算机系统的指令系统。计算机程序就是按解题要求从计算机指令系统中选择合适的指令所组成的指令序列。在一般的计算机系统中,基本的运算和操作有以下4类:
算术运算:主要包括加、减、乘、除等运算。
逻辑运算:主要包括“与”、“或”、“非”等运算。
关系运算:主要包括“大于”、“小于”、“等于”、“不等于”等运算。
数据传输:主要包括赋值、输入、输出等操作。
2)算法的控制结构:一个算法的功能不仅取决于所选用的操作,而且还与各操作之间的执行顺序有关。算法中各操作之间的执行顺序称为算法的控制结构。
算法的控制结构给出了算法的基本框架,它不仅决定了算法中各操作的执行顺序,而且也直接反映了算法的设计是否符合结构化原则。描述算法的工具通常有传统流程图、N-S结构化流程图、算法描述语言等。一个算法一般都可以用顺序、选择、循环3种基本控制结构组合而成。
3.算法设计的基本方法
计算机解题的过程实际上是在实施某种算法,这种算法称为计算机算法。计算机算法不同于人工处理的方法,下面是工程上常用的几种算法设计方法,在实际应用时,各种方法之间往往存在着一定的联系。
(1)列举法
列举法的基本思想是,针对待解决的问题,列举所有可能的情况,并用问题中给定的条件来检验哪些是必需的,哪些是不需要的。因此,列举法常用于解决“是否存在”或“有多少种可能”等类型的问题。例如,我国古代的趣味数学题:“百钱买百鸡”、“鸡兔同笼”等求解不定方程的问题,均可采用列举法解决。
其特点是原理比较简单,但当列举的可能情况较多时,执行列举算法的工作量将会很大。因此,在用列举法设计算法时,只要对实际问题进行详细的分析,将与问题有关的知识条理化、完备化、系统化,从中找出规律;或对所有可能的情况进行分类,引出一些有用的信息,就可以大大减少列举量。
列举算法虽然是一种比较笨拙而原始的方法,其运算量比较大,但在有些实际问题中(如寻找路径、查找、搜索等问题),局部使用列举法却是很有效的。因此,列举算法是计算机算法中的一个基础算法。
(2)归纳法
归纳法的基本思想是,通过列举少量的特殊情况,经过分析,最后归纳出一般的关系。显然,归纳法比列举法更能反映问题的本质,并且可以解决列举量为无限的问题。从本质上讲,归纳就是通过观察一些简单而特殊的情况,最后总结出一般性的结论。
归纳是一种抽象,即从特殊现象中找出一般规律。但由于在归纳法中不可能对所有的情况进行列举,因此,该方法得到的结论只是一种猜测,还需要进行证明。
(3)递推法
递推,即是从已知的初始条件出发,逐次推出所要求的各个中间环节和最后结果。其中初始条件或问题本身已经给定,或是通过对问题的分析与化简而确定。递推的本质也是一种归纳,工程上许多递推关系式实际上是通过对实际问题的分析与归纳得到的。因此,递推关系式通常是归纳的结果。
递推算法在数值计算中是极为常见的。例如,裴波那契数列是采用递推的方法解决问题的。但是,对于数值型的递推算法必须注意数值计算的稳定性问题。
(4)递归
在解决一些复杂问题或问题的规模比较大时,为了降低问题的复杂程序,通常将问题逐层分解,最后归结为一些最简单的问题。这种将问题逐层分解的过程,并没有对问题进行求解,而只是在解决了最后那些最简单的问题后,再沿着原来分解的逆过程逐步进行综合,这就是递归的基本思想。
递归分为直接递归和间接递归两种方法。如果一个算法直接调用自己,称为直接递归调用;如果一个算法A调用另一个算法B,而算法B又调用算法A,则此种递归称为间接递归调用。递归过程能将一个复杂的问题归纳为若干较简单的问题,然后将这些较简单的问题再归结为更简单的问题,这个过程可以一直做下去,直到归结出最简单的问题为止。由此可以看出,递归的基础也是归纳。在实际工程中,许多问题就是用递归来定义的,数学中的许多函数也是用递归来定义的。递归在可计算性理论和算法设计中占很重要的地位。
(5)减半递推技术
实际问题的复杂程序往往与问题的规模有着密切的联系。因此,利用分治法解决这类实际问题是有效的。所谓分治法,就是对问题分而治之。 工程上常用的分治法是减半递推技术。
所谓“减半”,即将问题的规模减半,而问题的性质不变;所谓“递推”,是指重复“减半”的过程。例如,一元二次方程的求解,求解过程见下例。
例如,设方程f(x)=0在区间[a,b]上有实根,且f(a)与f(b)异号。利用二分法求该方程在区间[a,b]上的一个实根。
用二分法求方程实根的减半递推过程如下:
首先取给定区间的中点c=(a+b)/2。
然后判断f(c)是否为0。若f(c)=0,则说明c即为所求的根,求解过程结束;如果f(c)≠0,则根据以下原则将原区间减半:
若f(a)f(c)<0,则取原区间的前半部分;
若f(b)f(c)<0,则取原区间的后半部分。
最后判断减半后的区间长度是否已经很小:
若︱a-b︱< ,则过程结束,取(a+b)/2为根的近似值;
若︱a-b︱≥ ,同重复上述的减半过程。
(6)回溯法
前面讨论的递推和递归算法本质上是对实际问题进行归纳的结果,而减半递推技术也是归纳法的一个分支。在工程上,有些实际的问题很难归纳出一组简单的递推公式或直观的求解步骤,也不能无限地列举。对于这类问题,只能采用“试探”的方法,通过对问题的分析,找出解决问题的线索,然后沿着这个线索进行试探,如果试探成功,就得到问题的解,如果不成功,再逐步回退,换其他路线进行试探。这种方法即称为回溯法。
回溯法在处理复杂数据结构方面有着广泛的应用,如人工智能中的机器人下棋。
4.算法复杂度
算法的复杂度主要分为时间复杂度和空间复杂度。
(1)算法的时间复杂度
算法的时间复杂度是指执行算法所需要的计算工作量。
为了能够比较客观地反映出一个算法的效率,一个算法的工作量不仅与所使用的计算机、程序设计语言以及程序编制者无关,而且还应与算法实现过程中的许多细节无关。为此,可以用算法在执行过程中所需基本运算的执行次数来度量算法的工作量,而算法所执行的基本运算次数是问题规模的函数,即
算法的工作量=f(n)
其中n是问题规模。例如,两个n阶矩阵相乘所需要的基本运算次数为n3,即计算量为n3,也就是时间复杂度为n3。
另外,在同一问题规模下,若算法执行所需的基本运算次数取决于某一特定的输入数据,则可以用平均性态分析和最坏情况分析两种方法来分析算法的工作量。顾名思义,平均性态分析即输入所有可能的平均值,相应的时间复杂度为算法的平均时间复杂度;最坏情况分析则是以最坏的情况估算算法执行时间的一个上界。一般情况下,后者更为常用。
(2)算法的空间复杂度
一个算法的空间复杂度,一般是指执行这个算法所需要的内存空间。一个算法所占用的存储空间包括算法程序所占的空间、输入的初始数据所占的存储空间以及算法执行中所需要的额外空间。其中额外空间包括算法程序执行过程中的工作单元以及某种数据结构所需要的附加存储空间(例如,在链式结构中,除了需要存储数据本身之外,还需要存储链接信息)。如果额外空间量相对于问题规模来说是常数,则称该算法是原地工作的。
在许多实际问题中,为了减少算法的内存空间,一般采用压缩存储技术,以便尽量减少不必要的额外空间。
1.1.2 数据结构
数据结构(data structure)是指反映数据元素之间关系的数据元素集合的表示,其作为计算机的一门学科,主要研究和讨论以下3个方面的问题:
1)数据集合中各数据元素之间所固有的逻辑关系,即数据的逻辑结构。
2)各数据元素在计算机中的存储关系,即数据的存储结构。
3)对各种数据结构进行的运算。
讨论以上问题的主要目的是提高数据处理的效率。提高数据处理的效率主要包括两个方面:一是提高数据速度,二是尽量节省在数据处理过程中所占用的计算机存储空间。
1.什么是数据结构
在数据处理领域中,建立数学模型有时并不十分重要,事实上,许多实际问题是无法表示成数学模型的。人们最感兴趣的是知道数据集合中各数据元素之间存在什么关系,应如何组织它们,即如何表示所需要处理的数据元素。
数据的组织方式不同,通常对它进行处理时的效率也不同。例如,在对两个存放了相同元素的表进行查找时,如果一个表中的所有数据元素是没有规律的,而另外一个表中的元素是经过排序的,则对前者进行查找时,只能从第一个元素开始,逐个将表中的元素与被查数进行比较,直到表中的某个元素与被查数相等(即查找成功)或者表中所有元素与被查数都进行了比较且都不相等(即查找失败)为止。而对于后者,由于有序表中的元素是从小到大进行排列的,在查找时可以利用这个特点,使比较次数大大减少。可见数据元素在表中的排列顺序对查找效率是有很大影响的。
要理解数据结构,还需要理解以下基本概念:
数据(data)是对客观事物的符号表示,是信息的载体,它能够被计算机识别、存储和加工处理。在计算机学科中,数据就是计算机加工处理的对象,它可以是数值数据,也可以是非数值数据。
数据元素(data element)是数据的基本单位,在计算机程序中通常作为一个整体进行考虑和处理。在不同的条件下,数据元素又可称为元素、结点、顶点、记录等。例如,描述一年四季的季节名:春、夏、秋、冬可以作为季节的数据元素。
数据对象(data object)是具有相同性质的数据元素的集合。在某个具体问题中,数据元素都具有相同的性质(元素值不一定相等),属于同一数据对象,数据元素是数据元素类的一个实例。
数据结构(data structure)是指相互之间存在一种或多种特定关系的数据元素的集合。
一般情况下,在具有相同特征的数据元素集合中,各个数据元素之间存在某种关系(即连续),这种关系反映了该集合中的数据元素所固有的一种结构。在数据处理领域,通常把数据元素之间这种固有的关系简单地用前后件关系(或直接前驱与直接后继关系)来描述。例如,在考虑一年四个季节的顺序关系时,“春”是“夏”的前件(即直接前驱,下同),而“夏”是“春”的后件(即直接后继,下同)。同样,“夏”是“秋”的前件,“秋”是“夏”的后件;“秋”是“冬”的前件,“冬”是“秋”的后件。在考虑家庭成员间的辈分关系时,“父亲”是“儿子”和“女儿”的前件,而“儿子”与“女儿”都是“父亲”的后件。
前后件关系是数据元素之间的一个基本关系,但前后件关系所表示的实际意义随具体对象的不同而不同。一般来说,数据元素之间的任何关系都可以用前后件关系来描述。
2.数据的逻辑结构
前面提到,数据结构是指反映数据元素之间关系的数据元素集合的表示。通俗地说,数据结构是指带有结构的数据元素的集合。结构实际上是指数据元素之间的前后件关系。
一个数据结构应包含以下两方面信息:
1)表示数据元素的信息。
2)表示各数据元素之间的前后件关系。
所谓数据的逻辑结构,是指反映数据元素之间逻辑关系的数据结构。由前面的叙述可以知道,数据的逻辑结构有两个要素:一是用D表示数据元素的集合,二是用R表示数据元素之间的前后件关系。即一个数据结构可以表示为B=(D,R),其中B表示数据结构。这就是一个二元关系的表示方式。为了反映D中各数据元素之间的前后件关系,一般用二元组来表示。例如,假设a与b是D中的两个数据,则二元组(a,b)表示a是b的前件,b是a的后件。这样,在D中的每两个元素之间的关系都可以用这种二元组来表示。
例如,一年四季的数据结构可以表示成
B={D,R}
D={春,夏,秋,冬}
R={(春,夏),(夏,秋),(秋,冬)}
例如,家庭成员数据结构可以表示成
B =(D,R)
D ={父亲,儿子,女儿}
R ={(父亲,儿子),(父亲,女儿)}
例如,n维向量X=(x1,x2,…,xn)也是一种数据结构,即X ={D,R},
其中数据元素集合为
D={ x1,x2,…,xn }
关系为
R={(x1,x2),(x2,x3),…,(xn-1,xn)}
3.数据的存储结构
在进行数据处理时,计算机所处理的数据总是存放在计算机的存储空间中,一个数据结构中的各元素在计算机存储空间中的位置关系与逻辑关系有可能是不同的。
数据的逻辑结构在计算机存储空间中的存放形式称为数据的存储结构(也称为数据的物理结构)。
由于数据元素在计算机存储空间中的位置关系可能与逻辑关系不同,因此,为了表示存放在计算机存储空间中的各数据元素之间的逻辑关系(即前后件关系),在数据的存储结构中,不仅要存放各数据元素的信息,还需要存放各数据元素之间的前后件关系的信息。
一般来说,一种数据的逻辑结构根据需要可以表示成多种存储结构,常用的存储结构有顺序、链接、索引等,采用不同的存储结构,其数据处理的效率是不同的。因此,在进行数据处理时,选择合适的存储结构是很重要的。
4.数据结构的图形表示
数据结构除了用二元关系表示外,还可以直观地用图形表示。
在数据结构的图形表示中,对于数据集合D中的每一个数据元素用中间标有元素值的方框表示,一般称为数据结点,并简称为结点;为了进一步表示各数据元素之间的前后件关系,对于关系R中的每一个二元组,用一条有向线段从前件结点指向后件结点。
例如,一年四季的数据结构可以用如图1-1所示的图形来表示。
图1-1 一年四季数据结构的图形表示
又例如,反映家庭成员间辈分关系的数据结构可以用如图1-2所示的图形表示。
图1-2 家庭成员间辈分关系数据结构的图形表示
显然,用图形方式表示一个数据结构是很方便的,并且也比较直观。
在数据结构中,没有前件的结点称为根结点;没有后件的结点称为终端结点(也称为叶子结点)。例如,在图1-1所示的数据结构中,元素“春”所在的结点(简称为结点“春”,下同)为根结点,结点“冬”为终端结点;在图1-2所示的数据结构中,结点“父亲”为根结点,结点“儿子”与结点“女儿”均为终端结点。数据结构中除了根结点与终端结点外的其他结点一般称为内部结点。
通常,一个数据结构中的结点可能是动态变化的。根据需要或在处理过程中,可以在一个数据结构中增加一个新结点(称为插入运算),也可以删除数据结构中的某个结点(称为删除运算)。插入与删除是对数据结构的两种基本运算。除此之外,对数据结构的运算还有查找、分类、合并、分解、复制和修改等。 在对数据结构的处理过程中,不仅数据结构中结点(即数据元素)的个数在动态变化,而且,各数据元素之间的关系也有可能在动态变化。例如,一个无序表可以通过排序处理而变成有序表;一个数据结构中的根结点被删除后,它的某一个后件可能变成了根结点;在一个数据结构中的终端结点后插入一个新的结点后,则原来的那个终端结点就不再是终端结点而成为内部结点了。有关数据的基本运算将在后面讲到具体数据结构时再介绍。
5.线性结构与非线性结构
如果一个数据结构中一个数据元素都没有,则称该数据结构为空的数据结构。一个空的数据结构中插入一个新的元素后,该数据结构就变为非空数据结构;在只有一个数据元素的数据结构中,将该元素删除后,该数据结构就变为空的数据结构。
根据数据结构中各数据元素之间前后件关系的复杂程度,一般将数据结构分为两大类:线性结构与非线性结构。
如果一个非空的数据结构满足如下两个条件:
1)有且只有一个根结点。
2)每一个结点最多有一个前件,也最多有一个后件。
则称该数据结构为线性结构。线性结构又称为线性表。
由此可以看出,在线性结构中,各数据元素之间的前后件关系是很简单的。例如,图1-1中一年四季这个数据结构就属于线性结构。
特别需要说明的是,在一个线性结构中插入或删除任何一个结点后还应是线性结构。根据这一点,如果一个数据结构满足上述两个条件,但在此数据结构中插入或删除任何一个就不满足这两个条件时,则该数据结构不能称为线性结构。例如,图1-3的数据结构显然是满足上述两个条件的,但它不属于线性结构,因为在这个数据结构中删除结点A后,就不满足上述的条件1)了。
图1-3 不是线性结构的数据结构特例
如果一个数据结构不是线性结构,则称为非线性结构。例如,图1-2中的家庭成员间辈分关系的数据结构不是线性结构,而是非线性结构。显然,在非线性结构中,各数据元素之间的前后件关系要比线性结构复杂,因此,对非线性结构的存储与处理比线性结构要复杂得多。
线性结构与非线性结构都可以是空的数据结构。一个空的数据结构究竟是线性结构,还是非线性结构,要根据具体情况来确定。如果对该数据结构的运算是按线性结构的规则来处理的,则属于线性结构,否则属于非线性结构。
1.1.3 线性表
线性表的特点是数据元素之间的关系是线性的,数据元素可以看成是排列在一条线或一个环上。线性表(linear list)是最简单、最常用的一种数据结构。
1.线性表的基本概念
线性表由一组数据元素构成。数据元素的含义很广泛,在不同的情况下,它可以有不同的含义。例如,一个n维向量(x1,x2,…,xn)是一个长度为n的线性表,其中的每一个分量就是一个数据元素。又例如,英文小写字母表(a,b,c,…,z)是一个长度为26的线性表,其中的每一个小写字母就是一个数据元素。再如,一年中的4个季节(春、夏、秋、冬)是一个长度为4的线性表,其中的每一个季节名就是一个数据元素。
数据元素可以是简单项(如上述例子中的数字、字母、季节名等)。在稍微复杂的线性表中,一个数据元素还可以由若干数据项组成。例如,某班的学生情况登记表是一个复杂的线性表,表中每一个学生的情况就组成了线性表中的每一个元素,每一个数据元素包括学号、姓名、性别、年龄、班级5个数据项,如表1-1所示。在这种复杂的线性表中,由若干数据项组成的数据元素称为记录,而由多个记录构成的线性表又称为文件。因此,上述学生情况登记表就是一个文件,其中每一个学生的情况就是一个记录。
表1-1 学生情况登记表
学 号 姓 名 性 别 年 龄 班 级
2010011810205 于春梅 女 18 2010级电信016班
2010011810260 何仕鹏 男 18 2010级电信017班
2010011810284 王爽 女 18 2010级通信011班
2010011810360 王亚武 男 18 2010级通信012班
… … … … …
综上所述,线性表是一个线性结构,它是一个含有n(n≥0)个结点的有限序列。对于其中的结点,有且仅有一个根结点,没有前件但有一个后件;有且仅有一个终端结点,没有后件但有一个前件。其他的结点都有且仅有一个前件和一个后件。一般地,一个线性表可以表示成一个线性序列:a1,a2,…,an,其中a1是根结点,an是终端结点。
线性结构的基本特征如下:
1)有且仅有一个根结点,无前件。
2)有且仅有一个终端结点,无后件。
3)除终端结点外,其他所有结点均有且仅有一个后件。
4)除根结点外,其他所有结点均有且仅有一个前件。
由n(n≥0)个数据元素(结点)a1,a2,…,an组成的有限序列称为线性表,其中数据元素的个数n定义为表的长度。当n=0时称为空表,常常将非空的线性表(n>0)记作:(a1,a2,…,an)。
2.线性表的顺序存储
在计算机中存放线性表,一种最简单的方法是顺序存储,也称为顺序分配。
线性表的顺序存储指的是用一组地址连续的存储单元依次存储线性表的数据元素。
线性表的顺序存储结构具备如下两个基本特征:
1)线性表中的所有元素所占的存储空间是连续的。
2)线性表中各数据元素在存储空间中是按逻辑顺序依次存放的。
由此可以看出,在线性表的顺序存储结构中,其前后件两个元素在存储空间中是紧邻的,且前件元素一定存储在后件元素的前面。如果线性表中各数据元素所占的存储空间(字节数)相等,则要在线性表中查找某一个元素是很方便的。
假设线性表中的第一个数据元素的存储地址(指第一字节的地址,即首地址)为ADR(a1),每个元素需要占用k个存储单元,则线性表中第i个元素ai在计算机存储空间中的存储地址为
ADR(ai)=ADR(a1)+(i-1)×k
即在顺序存储结构中,线性表中每一个数据元素在计算机存储空间中的存储地址由该元素在线性表中的位置序号唯一确定。一般来说,长度为n的线性表(a1,a2,…,an),设每个数据元素在内存中占4字节,起始元素的存储地址为1010,则该线性表在计算机中的顺序存储结构如图1-4所示。
图1-4 线性表的顺序存储结构示意图
在程序设计语言中,通常定义一个一维数组来表示线性表的顺序存储空间。在用一维数组存放线性表时,该一维数组的长度通常要定义得比线性表的实际长度大一些,以便对线性表进行各种运算,特别是插入运算。在一般情况下,如果线性表的长度在处理过程中是动态变化的,则在开辟线性表的存储空间时,要考虑到线性表在动态变化过程中可能达到的最大长度。
在线性表的顺序存储结构下,可以对线性表做以下运算:
1)在线性表的指定位置处加入一个新的元素(即线性表的插入)。
2)在线性表中删除指定的元素(即线性表的删除)。
3)在线性表中查找某个(或某些)特定的元素(即线性表的查找)。
4)对线性表中的元素进行排序(即线性表的排序)。
5)将一个线性表分解成多个线性表(即线性表的分解)。
6)将多个线性表合并成一个线性表(即线性表的合并)。
7)复制一个线性表(即线性表的复制)。
8)逆转一个线性表(即线性表的逆转)等。
线性表的基本运算是插入运算和删除运算,下面两小节主要讨论这两种运算。
3.线性表的插入运算
线性表的插入运算是指在表的第i(1≤i≤n+l)个位置上,插入一个新结点x,使长度为n的线性表:
(a1, …, ai-1, ai, …, an)
变成长度为n+1的线性表:(a1, …, ai-1, x, a, …, an)
首先举一个例子来说明如何在顺序存储结构的线性表中插入一个新元素。
例如,图1-5a是一个长度为8的线性表,顺序存储在长度为10的存储空间中。现在要求在第2个元素(即18)之前插入一个新元素35。其插入过程如下:
首先从最后一个元素开始直到第2个元素,将其中的每一个元素均依次往后移动一个位置,然后将新元素35插入第2个位置。
插入一个新元素后,线性表的长度变成了9,如图1-5b所示。
如果要再在线性表的第9个元素之前插入一个新元素22,则采用类似的方法:将第9个元素往后移动一个位置,然后将新元素插入第9个位置。插入后,线性表的长度变成了10,如图1-5c所示。
现在,为线性表开辟的存储空间已经满了,不能再插入新的元素了。如果再要插入,则会造成称为“上溢”的错误。
a) 长度为8的线性表 b) 插入元素87后的线性表 c) 插入元素22后的线性表
图1-5 线性表在顺序存储结构下的插入
一般来说,设长度为n的线性表为
(a1, …, ai-1,ai, …, an)
现要在线性表的第i个元素ai之前插入一个新元素b,插入后得到长度为n+1的线性表为
(a1′,a2′,… , aj′, aj+1′, …,an′,an+1′)
则插入前后的两线性表中的元素满足如下关系:
在一般情况下,要在第i(1≤i≤n)个元素之前插入一个新元素时,首先要从最后一个(即第n个)元素开始,直到第i个元素,共n-i+1个元素依次向后移动一个位置,移动结束后,第i个位置就被空出,然后将新元素插入第i项。插入结束后,线性表的长度就增加了1。
下面分析插入算法的时间复杂度。顺序表的插入运算,其时间主要花费在了数据的移动上,在第i个位置插入b,从ai到an都要向后移动一个位置,共需要移动n-i+1个元素,而i的取值范围为1≤i≤n,即有n+1个位置可以插入。假设在第i个位置上做插入操作的概率为pi,则平均移动数据元素的次数为:
设pi=1/(n+1),即为等概率情况,则
这说明,在顺序表上进行插入操作大约需要移动表中一半的数据元素,显然该算法的时间复杂度为O(n)。
4.线性表的删除运算
线性表的删除运算是指将表的第i(1≤i≤n)个元素从线性表中去掉,删除后使原来长度为n的线性表:
(a1,…,ai-1,ai,…,an)
变成长度为n-1的线性表:(a1,…,ai-1,ai+1,…,an)
首先举一个例子来说明如何在顺序存储结构的线性表中删除一个元素。
例如,图1-6a为一个长度为8的线性表顺序存储在长度为10的存储空间中。现在要求删除线性表中的第1个元素(即删除元素26)。其删除过程如下:
从第2个元素开始直到最后一个元素,将其中的每一个元素均依次往前移动一个位置。此时,线性表的长度变成了7,如图1-6b所示。
如果要再删除线性表的第6个元素,则采用类似的方法:将第7个元素往前移动一个位置。此时,线性表的长度变成了6,如图1-6c所示。
a) 长度为8的线性表 b) 删除元素26后的线性表 c) 删除元素35后的线性表
图1-6 线性表在顺序存储结构下的删除
一般来说,设长度为n的线性表为
(a1,…,ai-1,ai,…,an)
现要删除表中的第i个元素,删除后得到长度为n-1的线性表为
(a1′,a2′,… ,aj′,…,an-1′)
则删除前后的两线性表中的元素满足如下关系:
在一般情况下,在删除第i(1≤i≤n)个元素时,则要从第i+1个元素开始,直到第n个元素之间共n-i个元素依次向前移动一个位置。删除结束后,线性表的长度就减小了1。
下面分析删除算法的时间复杂度。与插入运算相同,其时间主要花费在了数据的移动上,当删除第i个位置时,从ai+1到an都要向前移动一个位置,共需要移动n-i个元素,而i的取值范围为1≤i≤n,即有n个位置可以删除。假设在第i个位置上做删除操作的概率为pi,则平均移动数据元素的次数为:
在等概率情况下,pi=1/n,则:
这说明,在顺序表上进行删除操作时大约需要移动表中一半的元素,显然该算法的时间复杂度也是O(n)。
1.1.4 栈和队列
栈和队列是两种特殊的线性结构。从数据结构的角度看,它们是线性表。从操作的角度看,它们是操作受限的线性表。在日常生活中经常会遇到栈或队列的实例,另外栈和队列还广泛应用于各种软件系统中。