![数据结构简明教程(第2版)微课版](https://wfqqreader-1252317822.image.myqcloud.com/cover/351/25111351/b_25111351.jpg)
2.2 顺序表
线性表的存储结构主要分为顺序存储结构和链式存储结构两类,前者简称为顺序表。本节主要介绍顺序表及其线性表基本运算在顺序表上实现的算法。
2.2.1 顺序表的定义
顺序表是线性表采用顺序存储结构在计算机内存中的存储方式,它由多个连续的存储单元构成,每个存储单元存放线性表的一个元素,逻辑上相邻的数据元素在内存中也是相邻的,不需要额外的内存空间来存放元素之间的逻辑关系。顺序表称为线性表的直接映射。
假定线性表的数据元素的类型为ElemType(在实际应用中,此类型应根据实际问题中的数据元素的特性具体定义,如为int、char类型等),在C/C++语言中,声明顺序表类型如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P44_28567.jpg?sign=1738811408-J0Qx9L6vpxAb3NIHwQi5vAoChQazYEKC-0-792b1311240ea91714cdb0302710ffdf)
其中,数据域data是一个一维数组,线性表的第1,2,…,n个元素分别存放在此数组的第0,1,…,n—1个单元中;数据域length表示线性表当前的长度,顺序表的示意图如图2.4所示。常数MaxSize称为顺序表的容量,其值通常根据具体问题的需要取为线性表实际可能达到的最大长度。length~MaxSize—1下标为顺序表当前的空闲区(或称备用区)。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P44_28569.jpg?sign=1738811408-ffii4csZ0yCgIrjQIMkxovZgyX0uD7sZ-0-3617a466fb9d49f8d96c563e235faa83)
图2.4 顺序表示意图
注意:在算法设计时,应遵守相应的语法规定。例如,L被声明为SqList类型的变量,即为一个顺序表,其表长应写为L.length。另外,线性表的元素ai(1≤i≤n)存放在data数组的data[i—1]中,也就是说,逻辑序号为i的元素在顺序表中对应的物理下标(或物理序号)为i—1。
由于顺序表采用数组存放元素,而数组具有随机存取特性,所以顺序表具有随机存取特性。
2.2.2 线性表基本运算在顺序表上的实现
所谓实现运算就是用C/C++语言给出完整的求解步骤即算法,因此运算实现必须以存储结构的类型定义为前提。上面已经给出了顺序表的类型定义,在此基础上可以进一步讨论线性表的基本运算在顺序表上的实现。
下面讨论顺序表中线性表基本运算算法的实现过程。
1.顺序表的基本运算算法
1)初始化线性表运算算法
将顺序表L的length域置为0,对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P44_46536.jpg?sign=1738811408-A7bEA7YygpUR9qCWVAjbYXNHOTHUP1K4-0-ca0300dc21e1bcba4d21758db104427d)
本算法的时间复杂度为O(1)。
2)销毁线性表运算算法
这里顺序表L作为自动变量,其内存空间是由系统自动分配的,在不再需要时会由系统自动释放其空间,所以对应的函数不含任何语句。对应的算法如下。
void DestroyList(SqList L)
{ }
3)求线性表长度运算算法
返回顺序表L的length域值,对应的算法如下。
int GetLength(SqList L)
{
return L.length;
}
本算法的时间复杂度为O(1)。
4)求线性表中第i个元素运算算法
对于顺序表L,算法在逻辑序号i无效时返回特殊值0(假),有效时返回1(真),并用引用型形参e返回第i个元素的值。对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P45_46542.jpg?sign=1738811408-v30mcilyXvc3zL0sN4GgVolhltIYLfws-0-0afa4b69ec86abd32531c3c484842408)
本算法的时间复杂度为O(1)。
5)按值查找运算算法
在顺序表L中找第一个值为x的元素,找到后返回其逻辑序号,否则返回0(由于线性表的逻辑序号从1开始,这里用0表示没有找到值为x的元素)。对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P45_46543.jpg?sign=1738811408-Wh2xt9LofOIxSGEo25Xhlb9ukuqOG6yG-0-80f03c7763e423bba031b707fdf264e8)
本算法的时间复杂度为O(n),其中,n为L中的元素个数。
6)插入元素运算算法
将新元素x插入到顺序表L中逻辑序号为i的位置(如果插入成功,元素x成为线性表的第i个元素)。当i无效时返回0(表示插入失败),i有效时将L.data[i—1..L.length—1]均后移一个位置,再在L.data[i—1]处插入x,顺序表长度增1,并返回1(表示插入成功),如图2.5所示(图中n表示顺序表L的长度)。对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P45_28613.jpg?sign=1738811408-WpcGrdsMSoBTu6JFQDZ1MRMKq7Y0QtUT-0-0a27df9b14fe46ebbc38d796a6cb2d14)
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P46_28645.jpg?sign=1738811408-ew2poqzTLQDob0CgzIgPUIQRxNXea55J-0-8890e9fb75c5f82ccd01ad3d8c9c2e2c)
图2.5 顺序表中第i个位置插入元素x的过程
对于插入算法InsElem()来说,在顺序表L中插入新元素共有n+1种情况,如图2.6所示。元素x插入到不同的位置,顺序表中元素移动的次数是不同的。当i=n+1时(插入尾元素),移动次数为0,呈现最好的情况;当i=1时(插入第一个元素),移动次数为n,呈现最坏的情况。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P46_28648.jpg?sign=1738811408-kAsioUUv4QtfMw0MqmwdjbfT42lgdyJj-0-f3fca11c1570b99d6c227c034967d289)
图2.6 在顺序表中插入新元素共有n+1种情况
对于一般情况,在位置i插入新元素x,需要将ai~an的元素均后移一次,移动次数为n—i+1。假设pi是在第i个位置上插i入一个元素的概率,则在长度为n的线性表L中插入一个元素时,所需移动元素的平均次数为:
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P46_46547.jpg?sign=1738811408-8d6HYjphx6TUi4ItJErfsIuIrcmrZfso-0-7e26ff8c63f87b459606b3647590cafc)
而插入算法的主要时间花费在元素移动上,所以算法InsElem()的平均时间复杂度为O(n)。
7)删除元素运算算法
删除顺序表L中逻辑序号为i的元素。在i无效时返回0(表示删除失败),i有效时将L. data[i..L.length—1]均前移一个位置,顺序表长度减1,并返回1(表示删除成功),如图2.7所示(图中n表示顺序表L的长度)。对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P46_28688.jpg?sign=1738811408-0LHxaGzwGdq7FXy1NeHYOLdQAcJdfT2t-0-7b9da4a2d6dd1d80ddbe59822983e10f)
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P47_28713.jpg?sign=1738811408-fjEcN1JlLomqk6OwkkzB0XQj18jHv8p6-0-d838573f7e8c38e7c8daf9fa85fda25e)
图2.7 顺序表中删除第i个元素的过程
对于删除算法DelElem()来说,在顺序表L中删除已存在的元素共有n种情况,如图2.8所示。删除元素的位置不同,顺序表中元素移动的次数是不同的。当i=n时(删除尾元素),移动次数为0;当i=1时(删除第一个元素),移动次数为n—1。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P47_28717.jpg?sign=1738811408-mmepC7AMYIGbLrfTYd56phVc2OEPCcWL-0-6c22d1f7f5b23ad25943f9e55fea1771)
图2.8 在顺序表中删除元素共有n种情况
对于一般情况,删除位置i的元素ai,需要将ai+1~an的元素均前移一次,移动次数为n—(i+1)+1=n—i。假设pi是删除第i个位置上元素的概率,则在长度为n的线性表中删除一个元素时所需移动元素的平均次数为:
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P47_46557.jpg?sign=1738811408-c8AJ5Bg0zsQmTrcYBoic8qF5MEGTT8MQ-0-d1137bd29c4490bd0dd6fb498881dff1)
而删除算法的主要时间花费在元素移动上,所以删除算法的平均时间复杂度为O(n)。
从以上分析看到,当线性表采用顺序表存储时,插入、删除算法的性能不太理想,主要是需要移动大量的元素。
8)输出元素值运算算法
从头到尾扫描(或者称为遍历)顺序表L,输出各元素值。对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P47_46556.jpg?sign=1738811408-S5A7BhR54opDgj9jJnaBC0uA8dqqDLou-0-ae72d8ed739948d1c6c2b49fd5e82502)
本算法的时间复杂度为O(n),其中,n为L中的元素个数。
提示:将顺序表类型声明及其基本运算函数存放在SqList.cpp文件中。
当顺序表的基本运算设计好后,给出以下主函数调用这些基本运算函数,读者可以对照程序执行结果进行分析,进一步体会顺序表各种操作的实现过程。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P48_28775.jpg?sign=1738811408-ZHTD7rtGY3VQdkh7NlaykBW8lq8n5X08-0-7ddcbe9576139cfe412ab0d9833d047c)
以上程序的执行结果如图2.9所示。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P48_28776.jpg?sign=1738811408-1AdEzzH2q1F5Q2sCB1TW8csi4qyMMWsI-0-a63af9e4aed8f61b8a5b4eec030cda33)
图2.9 程序执行结果
2.整体创建顺序表的算法
可以通过调用基本运算算法来创建顺序表,其过程是先初始化一个顺序表,然后向其中一个一个地插入元素。这里介绍的是快速创建整体顺序表的算法,也称为整体建表。假设给定一个含有n个元素的数组,由它来创建顺序表,对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P48_46561.jpg?sign=1738811408-TCLP947TpIESmcLuw1pkWWNQhls1irZj-0-a8ecc71ff1d918ce02f9d9b483710214)
算法的时间复杂度为O(n)。
说明:尽管该算法只是简单地将a[0..n—1]的元素复制到L的data数组中,但可以体会到整体创建顺序表L的过程。实际上,可以通过修改插入元素的条件使得仅仅将满足条件的元素插入到L中。
2.2.3 顺序表的算法设计示例
1.基于顺序表基本操作的算法设计
这类算法设计中包括顺序表元素的查找、插入和删除等。
【例2.3】假设有一个顺序表L,其中元素为整数且所有元素值均不相同。设计一个算法将最大值元素与最小值元素交换。
解:用maxi和mini记录顺序表L中最大值元素和最小值元素的下标,初始时maxi= mini=0,i从1开始扫描L.data的所有元素:当L.data[i]>L.data[maxi]时置maxi=i;否则若L.data[i]<L.data[mini]时置mini=i。扫描完毕时,L.data[maxi]为最大值元素,L. data[mini]为最小值元素,将它们交换。对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P49_46567.jpg?sign=1738811408-JOV4XZmBBZ02T1RQCPtNhw8sUDxWIB6Q-0-3a4dd9f37f1571169c9536bb093e0100)
上述算法的时间复杂度为O(n)。
【例2.4】设计一个算法,从线性表中删除自第i个元素开始的k个元素,其中,线性表用顺序表L存储。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P49_28813.jpg?sign=1738811408-jvkuTKUUKRZYGN8vALZcEqKA9SdTGi8d-0-d14db69eef64a8ddd7d5c5be05de1fe4)
图2.10 在顺序表中删除若干个元素
解:本题将线性表中ai~ai+k—1元素(对应L. data[i—1..i+k—2]的元素)删除,即将ai+k~an(对应L.data[i+k—1..n—1])的所有元素依次前移k个位置,如图2.10所示(图中n表示顺序表L的长度)。对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P49_46568.jpg?sign=1738811408-9GTvRZWLlm8vWbN8fVA4zc8YaBeA90P9-0-93e415ee439e68fe0c863608d241339f)
本算法的时间复杂度为O(n),其中,n为顺序表L中的元素个数。
【例2.5】假设有一个顺序表L,其中元素为整数且所有元素值均不相同。设计一个尽可能高效的算法将所有奇数移到所有偶数的前面。
解:采用前后元素交换的算法设计思路:置i=0,j=n—1,在顺序表L中从前向后找到偶数L.data[i],从后向前找到奇数L.data[j],将两者交换;循环这个过程直到i等于j为止。对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P50_46570.jpg?sign=1738811408-x9tm1o9H4T5LDrL5PL6th6X02me6lbF0-0-eb5ed62d4c6ab3522f90cd8f2a0b6237)
本算法正好扫描了顺序表中每个元素一次,所以时间复杂度为O(n),算法中只定义了固定几个临时变量,所以算法的空间复杂度为O(1)。
2.基于整体建表的算法设计
这类算法设计中需要根据条件产生新的结果顺序表。
【例2.6】已知一个整数线性表采用顺序表L存储。设计一个尽可能高效的算法删除其中所有值为x的元素(假设L中值为x的元素可能有多个)。
解:由于删除所有x元素后得到的结果顺序表可以与原L共享存储空间,求解问题转化为新建结果顺序表。采用整体创建顺序表的算法思路,将插入元素的条件设置为“不等于x”,即仅将不等于x的元素插入到L中。用k记录结果顺序表中元素个数(初始值为0),扫描L,将L中所有不为x的元素重新插入到L中,每插入一个元素k增加1,最后置L的长度为k。对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P50_46571.jpg?sign=1738811408-HBtp5ceUlstwWQyWQ6Zzh9e0mMsaZgfO-0-a11acbc10214ebf431b1099173b62297)
上述算法的时间复杂度为O(n),空间复杂度为O(1),属于高效的算法。如果另外创建一个临时顺序表L1,将L中所有不为x的元素插入到L1中,再将L1复制回L,对应算法的空间复杂度为O(n)。如果在扫描L时对每个等于x的元素都采用移动方式实现删除操作,对应算法的时间复杂度为O(n2)。这两个算法都不是高效的算法。
【例2.7】已知一个整数线性表采用顺序表L存储。设计一个尽可能高效的算法删除其中所有值为负整数的元素(假设L中值为负整数的元素可能有多个)。
解:采用例2.6的思路,仅将插入元素的条件设置为“元素值≥0”即可。对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P51_46574.jpg?sign=1738811408-Vj0PTdczMeugRAjIxw8gKfKEjsbrExMR-0-d7823c759bfe983b70d95e07e3df316c)
3.有序顺序表的二路归并算法
有序表是指按元素值递增或者递减排列的线性表,有序顺序表是有序表的顺序存储结构。对于有序表,可以利用其元素的有序性提高相关算法的效率,二路归并就是有序表的一种经典算法。
【例2.8】有两个按元素值递增有序的顺序表A和B,设计一个算法将顺序表A和B的全部元素归并到一个按元素递增有序的顺序表C中。并分析算法的空间复杂度和时间复杂度。
解:算法设计思路是:用i扫描顺序表A,用j扫描顺序表B。当A和B都未扫描完时,比较两者的当前元素,总是将较小者复制到C中。最后将尚未扫描完的顺序表的余下元素均复制到顺序表C中。这一过程称为二路归并,如图2.11所示。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P51_28890.jpg?sign=1738811408-qYDgCxu5bcicHYX5eDDX0ZAHZwLGqZGw-0-5044003dc59b90aa43729dd29516b47d)
图2.11 二路归并过程
对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P51_46575.jpg?sign=1738811408-9mlxsgra3JwpOP2GOyssKmolvsVV6Cvq-0-00d59bee557479eeec153957963db795)
本算法的空间复杂度为O(1),时间复杂度为O(n+m),其中,n和m分别为顺序表A和B的长度。
说明:上述算法是新建有序顺序表C,它是采用整体建表实现的。插入到C中的元素是按二路归并即比较后得到的较小的元素。
【例2.9】有两个递增有序顺序表A和B,设计一个算法由顺序表A和B的所有公共元素产生一个顺序表C。并分析该算法的空间复杂度和时间复杂度。
解:本算法仍采用二路归并的思路,用i、j分别扫描有序顺序表A、B,跳过不相等的元素,将两者相等的元素(即公共元素)放置到顺序表C中。对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P52_46579.jpg?sign=1738811408-lnYvoY95TbhFFYmCVLgXYPHixdlg2RAR-0-793a69520a11178f28b07069a5188a29)
本算法的空间复杂度为O(1),时间复杂度为O(m+n),其中,m和n分别为顺序表A和B的长度。和例2.2算法相比,它们的功能相同,但例2.2算法的时间复杂度为O(m×n),所以本例算法更优,这是因为本例中顺序表的元素是有序的。
【例2.10】有两个递增有序顺序表A和B,分别含有n和m个整数元素(最大的元素不超过32 767),假设这n+m个元素均不相同。设计一个尽可能高效的算法求这n+m个元素中第k小的元素。如果参数k错误,算法返回0;否则算法返回1,并且用参数e表示求出的第k小的元素。
解:本算法仍采用二路归并的思路。若k <1或者k >A.length+B.length,表示参数k错误,返回0。否则,用i、j分别扫描有序顺序表A、B,当两个顺序表均没有扫描完时,比较它们的当前元素,每比较一次k减1,当k=0时,较小的元素就是最终结果e,找到这样的e后返回1。如果没有找到e,若顺序表A没有扫描完,e就是A.data[i+k—1],若顺序表B没有扫描完,e就是B.data[j+k—1]。对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P53_46584.jpg?sign=1738811408-CyfwHIlI7cQRGKfUzDGOrcTnn2ERAChV-0-2c5a77fa22861f61925fd4a1582513dc)
上述算法需要考虑三种情况:A、B均没有扫描完,A没有扫描完和B没有扫描完。由于A、B均没有扫描完时总是比较找最小元素,并且最大元素值为INF(32767),可以这样简化判断:x表示当前A的元素,当A扫描完时取x为INF,y表示当前B的元素,当B扫描完时取y为INF,从而简化为总是进行x、y的元素比较。对应的简化算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P53_46585.jpg?sign=1738811408-n79ayRSlZXs6sr1x5oSot5KESNFYQK42-0-c5248f22cf7ec526af43ef7ada14547a)
说明:本题仅求第k小的元素,没有必要将A、B中所有元素二路归并到临时表C中,再在C中求出e=C.data[k—1],这样做算法的空间复杂度为O(n+m),从空间角度看,不如Topk1和Topk2算法高效。