![数据结构简明教程(第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=1739161091-8VecoowuunewNTh6zKUVLl2fojWa7Ss1-0-dc5e0aedfa18fe21aa60a6c0467c0755)
其中,数据域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=1739161091-ygOdkQ2N1dcf9rwIVmTGXhr79401CHM5-0-eb49bb4f87c45cbaf86228107931d495)
图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=1739161091-hw8fGLIOBR3rKIXjqaYus20DY8anC6US-0-fcbbf7253b71d06e50a2dfa3d75d15ea)
本算法的时间复杂度为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=1739161091-jtnG9ltrPLbD0ZxWnQRki2883xLa8Pwc-0-f7153270b755be1f862456248ece27e6)
本算法的时间复杂度为O(1)。
5)按值查找运算算法
在顺序表L中找第一个值为x的元素,找到后返回其逻辑序号,否则返回0(由于线性表的逻辑序号从1开始,这里用0表示没有找到值为x的元素)。对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P45_46543.jpg?sign=1739161091-5dLfv8w93J8ZMeScXo0bVQWVrHcytMMV-0-212c342f015b86e7d95574f62f1df04b)
本算法的时间复杂度为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=1739161091-nYW9VHLPyNs4p4V3SrGrIJeV3he9ZWDv-0-f1ae79d94a2ffdfeeaa83186e6495ce0)
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P46_28645.jpg?sign=1739161091-yDZXNbwa2cY5367VqvnX5GdfgbWNJH1F-0-a0f44a30c3775502443ba47900e18050)
图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=1739161091-XKi1Dy7SyRuFkVGduLBrRAlGfQPKU8Re-0-a3bef6e97a4af5cac4782d3d3f4f67a9)
图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=1739161091-4xM1hTCY6V58BrX50db966wOqidfkC9F-0-c9725344d8aeb02790fc50e05b63f483)
而插入算法的主要时间花费在元素移动上,所以算法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=1739161091-hW4AvUF8laTD5nJXZwDev6rYzwN56GJw-0-64b4aa530ece5be733137587d907704b)
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P47_28713.jpg?sign=1739161091-KzLgFiQ4DJucZIjwVJXMBMoCRUr3yfa7-0-a40e81ae38b74b27f696ec7ef2d6ba7d)
图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=1739161091-bJLZsO5h3P0LmpVygqVxL2MzzA9nWrip-0-668c29a88a71d0bfb22605c4acf7cbea)
图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=1739161091-ENLSkzrpBXysaoBUYdnsltEJzBlEx3e7-0-0249dbcb5172001859ba1522e86ea22d)
而删除算法的主要时间花费在元素移动上,所以删除算法的平均时间复杂度为O(n)。
从以上分析看到,当线性表采用顺序表存储时,插入、删除算法的性能不太理想,主要是需要移动大量的元素。
8)输出元素值运算算法
从头到尾扫描(或者称为遍历)顺序表L,输出各元素值。对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P47_46556.jpg?sign=1739161091-Qy8PDuRY7aTlG4uaCgRpWpjLYLjDVHid-0-4f4ad21eceec0b1cc353e686a0539452)
本算法的时间复杂度为O(n),其中,n为L中的元素个数。
提示:将顺序表类型声明及其基本运算函数存放在SqList.cpp文件中。
当顺序表的基本运算设计好后,给出以下主函数调用这些基本运算函数,读者可以对照程序执行结果进行分析,进一步体会顺序表各种操作的实现过程。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P48_28775.jpg?sign=1739161091-XgozTke1agTdRyq0JbHjZjlF302t3ZPV-0-e959a4db6d94f71e48497899e84875aa)
以上程序的执行结果如图2.9所示。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P48_28776.jpg?sign=1739161091-sCEraDFLQ5rbmHFoTbkZG7K8M86BWZw8-0-87b2a5a1bacf79a3e3d5625883931dd0)
图2.9 程序执行结果
2.整体创建顺序表的算法
可以通过调用基本运算算法来创建顺序表,其过程是先初始化一个顺序表,然后向其中一个一个地插入元素。这里介绍的是快速创建整体顺序表的算法,也称为整体建表。假设给定一个含有n个元素的数组,由它来创建顺序表,对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P48_46561.jpg?sign=1739161091-r7MH132Yp7HvjqzNYKKh8cpKk1IR5izt-0-a6e070ea248e60f496a97282f687fec2)
算法的时间复杂度为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=1739161091-CYCyTaa9TeZF9TLh5IpHR0HfSSFj9155-0-9e65d07afd535009c04b5c2edee52d74)
上述算法的时间复杂度为O(n)。
【例2.4】设计一个算法,从线性表中删除自第i个元素开始的k个元素,其中,线性表用顺序表L存储。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P49_28813.jpg?sign=1739161091-mSkxIlnvhs0WIe25w6n1tP9UcOv0ngqJ-0-6ad52f27fcb8d40f4f358716cab943a6)
图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=1739161091-xxLieMTlsESbNgYvxRFT8PBnMeWfAWE9-0-e6f09ca6a8118f94a8995ee0f692fdf8)
本算法的时间复杂度为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=1739161091-SRWSc1TFP5eM1rcALGhRdOpHuBsol06T-0-b5180dd367870e09a63028fbc8f2b366)
本算法正好扫描了顺序表中每个元素一次,所以时间复杂度为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=1739161091-ErFqTBAahZMdCYrzkaaQB6CFSKemKivX-0-e7ed2d93fe85ebd7eecadc6242a3d081)
上述算法的时间复杂度为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=1739161091-Tvuvzq2Uy8Cr6Vz8MT3S7EVAhBXz3E5k-0-4b917eec608e4f32192bd2cc7e08ad22)
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=1739161091-cyYZfkzBKOX681ROTSMulUPItuJEyM2L-0-a5cbb27d6c312f058e339f6a1d8b2742)
图2.11 二路归并过程
对应的算法如下。
![](https://epubservercos.yuewen.com/71470A/13467201303429506/epubprivate/OEBPS/Images/Figure-P51_46575.jpg?sign=1739161091-X7DltuxCuZjqw2Qs1ge0egw7ClJGbMVZ-0-67656d9216268767a918cbb2e9266f56)
本算法的空间复杂度为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=1739161091-HMhDD172dOHFUaPxFSGAs5rexAlY2cFw-0-16da7a6402af58075bf4a86ac8c62540)
本算法的空间复杂度为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=1739161091-GjsvJPq62fiLE7CUX4WNb5H5E9ymLOvo-0-8a4ad9c60a5ce2e560e3bd485a323f46)
上述算法需要考虑三种情况: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=1739161091-XWGj1oCcHWtLmyHfjwbz4koaLUYwXJzq-0-7461049b0bb9d44fe266a17f62adb79a)
说明:本题仅求第k小的元素,没有必要将A、B中所有元素二路归并到临时表C中,再在C中求出e=C.data[k—1],这样做算法的空间复杂度为O(n+m),从空间角度看,不如Topk1和Topk2算法高效。