《2.2.1顺序表的定义|精讲篇》
上一节学习了线性表的逻辑结构,线性表需要实现哪些基本运算/操作?在本节中,我们将学习顺序表的定义、顺序表的特性,以及如何用代码来实现顺序表。下个小节我们会介绍基于顺序存储(这种存储结构)如何用代码具体的实现之前定义的一系列基本操作。
从逻辑上看,线性表的各个元素构成一个有顺序的序列。
数据元素之间存在先后顺序关系,这种逻辑结构是从我们人类的视角来理解所看到的一些特性。
计算机如何表示数据元素间的逻辑关系?
我将分别介绍两种线性表的实现方式:
采用顺序存储结构来实现线性表
采用链式存储结构来实现线性表。
顺序表的定义:
顺序表——用顺序存储的方式实现线性表。
顺序存储——把逻辑上相邻的数据元素存储在物理上也相邻的存储单元中(绪论中提过),如图。
这些数据元素之间的前后关系,通过物理内存上的连接关系来体现。
后面作者会在2025.5.25 00:00前整理出笔记和思维导图大家放心,主页还有其他文章欢迎参考 收藏文章 关注博主 高效学习
线性表当中的各个数据元素(上一小节强调过)的数据类型相同,即每个数据元素所占的内存空间一样大。
所以如果顺序表的第一个数据元素。
它的存放地址是这个地址的话,那么由于顺序表当中各个数据元素,它们在。物理内存上是连续存放的,并且每个数据元素。
它们所占的空间大小都是相等的,因此它的第二个数据元素。所存放的位置就应该是这个顺序表的起始地址,加上数据元素的大小,而第三个数据元素存放的位置就应该是它的起始地址。
加上二乘以数据元素的大小。以此类推。那我们怎么才能知道一个数据元素的大小到底是多少呢?C语言当中提供了一个很方便使用的关键字,叫size off size off打个小括号。
然后里边传入。你的顺序表当中存放的这个数据元素的数据类型,比如如果你的顺序表当中存放的是一个一个的整数,那么只要你用size off里边。填入int就可以得到一个int型的整数。
在这个系统当中,它占多大的内存空间?那在C语言当中,很多情况下,一个int型的变量。
它是占四个字节。当然,你的顺序表当中还可以存放呃,其他更复杂的数据,比如说可以存放结构类型的数据。
像这个地方,我们定义了一个叫custom的结构,里边存了两个整数,分别是num和people,那么两个整数。
每个整数占四个字节,所以customer这种数据类型。它占的内存空间大小就应该是八个字节,当然你并不需要关心它的这个八个字节是怎么来的,你只需要使用C语言给你提供的。
这个很方便的size off关键字。啊,就可以了。所以如果知道了顺序表的起始存放位置,也就是它的第一个数据元素的存放地址。
那么后面这些数据元素的存放地址是不是就可以很方便的用size off?这个关键字马上就可以得到,马上就可以算出来。好的,那接下来看顺序表的第一种实现方式叫做静态分配啊。
所谓静态分配就是指使用这种大家最熟悉的数组的这种定义方式。来实现一个顺序表,当然这是一个静态的数组,也就是说这个数组的长度大小,一旦确定了之后。
它就不可以改变这是静态数组的特点。我们的顺序表用这样的数据类型来表示,里边定义了一个静态数组,长度为max size啊,这是我们宏定义的一个常量。
另外,还定义了一个叫lens的变量,用于表示当前这个顺序表,它的实际长度到底是多少,那么access的值决定了顺序表。
它最多可以存放几个数据元素。而lens的值表示的是当前这个顺序表当中已经存入了多少个元素,如果从内存的视角来看的话,当你声明了一个data数组的时候。那么。
其实就是在内存当中开辟了一整片的连续空间,这一整片的连续空间总共可以存放十个数据元素。我们这儿的代码当中,数据元素的类型用呃I lin tap来表示il im其实就是ilmant就是元素的缩写。数据元素的类型可以是int型。
可以是你自己定义的某一些更复杂的struct类型,这个具体要看你要用你的顺序表来存什么?那这个地方我们用element type来表示,只是为了让它更具有通用性,大家如果自己写代码实现的话。
那么你的数据元素类型呃,它具体是什么类型?你把这个element type。给替换掉就可以了。那我们把顺序表起名为sq list。
这儿的SQ指的是sequence,所以这儿的SQ其实也是一个缩写。好的,那么接下来来看一段具体的代码在这儿,我们定义了一个顺序表。
这个顺序表是用于存放整数的,也就是说数据元素的数据类型是int。那在这儿我们定义了一个叫做data的数组,静态的数组用于存放这些数据元素,最多只能存十个。
那定义了这样的一个数据结构之后,我们在main行数里首先声明,一个sq list也就是声明,一个顺序表。那么。
在执行这句代码的时候,其实计算机就会在内存当中给这个顺序表分配,它所需要的空间。首先是存放data数组的一整片的连续空间,那这片空间的大小就应该是十乘以每一个数据元素的呃大小。
那像这个地方,由于我们的数据元素是int型,所以每个数据元素的大小就应该是四个字节,也就是这个是四个字节,这一片是四个字节。
好以此类推。那除了data之外,也需要分配一个呃存放lens这个变量的空间,这个也是四个字节,因为它也是int型的。
好,那接下来在这个地方,我们实现了一个in it list这样的一个函数,对这个顺序表进行初始化,其实这个函数就是我们上。
上一小节当中提到的基本运算的第一个好,那既然main函数调用了in it list,所以接下来就会开始执行这个函数里边的代码。首先是一个for循环。这个for循环做的事情是把data这个数组当中所有的这些数据元素的啊值都置为零。
也就是给各个数据元素设置一个默认的初始值,当然这个设置默认初始值的步骤其实是可以省略的,这个我们一会儿再来解释好,那除此之外还需要把lens的值置为零。因为刚开始顺序表当中没有存入任何一个数据元素。
所以此时顺序表的当前长度应该是零。那这就是对顺序表的一个初始化工作好,那接下来要探讨的问题是,如果不给这个data数组设置一个默认的初始值的话。会发生什么情况?
我们把这个部分的代码给去掉,也就是说在对一个顺序表进行初始化的时候,只是设置了它的lens变量的值。那我们在main函数里添加了一个for循环,把data这个数组全部给打印出来。
那打印的结果是这样的。可以看到data这个数组前面这些数据元素啊,它都是零,这很正常对吧?但是最后这两个数据元素看一下是两个很奇怪的值。
如果大家在自己的电脑上实现这段代码的话,那么大家的电脑上打印出来的呃data这个数组当中各个元素的值跟我的还会不一样?那产生这种奇怪现象的原因是内存当中会有遗留的脏数据,也就说当我们在声明这个顺序表的时候,虽然系统在背后给我们分配了这么一大片的内存空间。
但是这一片内存空间之前存的是什么数据?其实我们并不知道,所以如果我们不给这些呃数据元素设置默认值的话,那么呃会因为之前一个。遗留下来的脏数据。
而导致我们的这个数组当中出现一些奇怪的数据。不过,刚才我们说过,给这个数据元素设置默认值这一步,其实是可省略的。
原因在于我们在这个main行数里打印顺序表当中的内容,这个操作其实是违规的,我们就不应该按照这样的方式来访问顺序表。因为顺序表当中不是定义了一个变量叫lens嘛。lens表示的是它当前的长度。
所以当我们在访问顺序表当中的各个数据元素的时候。不应该从第一个元素访问到最后一个元素,而应该是访问到顺序表当中。当前实际已经存储的啊,最后的一个元素。
那由于刚开始lens的值是零,所以如果用这种稍微正规一些的写法的话,那么这个for循环当中的语句是不会被执行的。所以为什么说我们可以省略呃,给各个数据元素设置默认值这一步。
那是因为如果按正常的访问方式的话,那么我们其实并不应该访问。啊大于顺序表实际长度的那些数据元素,当然了,其实更好的做法应该是使用基本操作来访问各个数据元素。
大家可以回顾一下,上个小节我们提到过呃,我们应该实现一个基本操作,叫get all这个基本操作实现的事情是把l这个线性表当中的第二个元素给取出来。所以其实使用基本操作来访问是最好的一种方式。
那这个地方想让大家重点体会的是这个脏数据是怎么回事?那既然内存当中会有脏数据,所以当我们声明这个变量的时候的初始值把它设为零,这一步是不是就肯定不能省略?因为你无法预知在这小片的呃内存区域内之前存放的到底是什么数据。
那有的同学可能会说哎C语言不是会自动给int型的变量设置一个默认初始值为零吗?那其实这个默认初始值设置为多少?这是编译器做的事情,如果换一个C语言的编译器,也许它就不会帮你做这种初始化的工作。
所以当我们在声明一个顺序表的时候,刚开始把它的length值设为零,这一步是必须做的好的,那么通过刚才的代码相信大家对顺序表的静态分配,这种实现方式。
已经有了更深入的理解,那这种实现方式的精髓在这个地方就是要定义一个静态的数组来存放你的数据元素,那接下来要思考的问题是,如果你声明的你刚开始声明的这个数组的长度。不够了。
它存满了怎么办?那遇到这种情况的话,给大家一个建议就是可以直接放弃治疗,因为这种静态数组的长度,只要你刚开始声明了。
那之后它的容量就不可以再改变。也就是说给这个顺序表分配的存储空间是不可变的,是静态的,那有的同学可能会说,那既然这样的话。
你刚开始就申请一大片连续的。存储空间把这个数组的长度设大一点不就行了吗?那如果采用这种方式的话,存在的问题就是很浪费。你想假如你的这些数组。
你设置了一万的长度,但是最后你只用了十个,那那这样岂不是很浪费内存资源吗?所以这种方式是不太机智的。那从这个地方。
大家就应该能够体会到静态分配这种实现方式,它存在一定的局限性,主要就是这个顺序表的大小容量,它是不可调的。无法更改。
那如果要让顺序表的大小可变的话,那我们可以采用动态分配的,这种实现方式。如果采用动态分配来实现顺序表的话,那么我们需要定义一个指针。
这个指针是指向了顺序表当中的第一个数据元素。另外,由于动态分配方式当中顺序表的容量大小是可以变的,所以我们需要在这儿增加一个变量,叫max size。
表示顺序表的最大容量是多少?那除了最大容量之外,当然也需要用lens这个变量来记录顺序表的当前长度。也就是说,此时顺序表当中实际上已经存放了多少个数据元素。
C语言当中提供了my lock和free这两个函数来分别实现动态的申请,一片内存空间和释放一片内存空间。这两个函数是十分重要的好,那具体来看一下MA lock函数的原理MA lock这个函数,它所实现的事情是会申请一整片的啊。
连续的内存空间。那这一整片的内存空间,它肯定有一个起始的内存地址,所以MA lock函数执行结束之后,它会return会返回一个指向这一整片存储空间。
开始地址的这个指针,那由于这一片存储空间是用于存放我们一个一个的数据元素的,所以在这个地方我们需要把MA lock函数返回的这个指针,把它强制转换成。你所定义的这个数据元素的数据类型所对应的指针。
比如如果你的顺序表是用于存放整数的,也就是说数据元素是int类型,那么当你在使用melloc函数的时候呃,就需要把这个il im type把它换成int。那MA lock函数返回的这个内存的起始地址的这个指针。
我们需要把它赋给顺序表当中的data,这个指针变量。也就是说data这个指针是指向了这一整片存储空间的起始地址,那第二个需要注意的点,既然MA loc函数是申请一整片的连续存储空间。
那么你到底要申请多大的空间呢?这就这个是由m数的这个参数所指明的,看一下左边这个size of element type。之前我们讲过这个部分的式子,得到的结果就是你的一个数据元素。
它所占存储空间的大小,如果你的数据元素是int类型,那么它所占的大小就应该是四个字节。然后第二个部分,它要乘以in it size in it size。
指的是这个顺序表,它刚开始初始的长度,那在这儿我们把它呃定义为一个常量是十。所以这一整个式子计算得到的结果就应该是存放十个,你的int型变量。
所需要的存储空间大小,那这就是me loc函数,那如果学过C加加的同学啊,可以用new和delete这两个关键字来实现类似于。和free的功能当然new和delay涉及到面向对象相关的一些知识点。
而我们为了照顾到更多的跨考的同学,所以我们在之后的学习当中会更多的使用和free这两个函数。好的,那接下来还是用一个具体的代码来看一下这个顺序表的动态分配,在背后发生了一些什么事情。
我们在这儿定义了一个顺序表,这个顺序表的数据元素类型是int类型。那data这个指针指向了顺序表当中的第一个数据元素,然后我们实现了一个函数in it list,用于初始化一个动态分配方式实现的顺序表。
然后再实现一个函数用于动态的增加,这个顺序表的长度,然后我们在main函数里调用这些相关的操作,一会儿我们来分析一下背后的过程,那需要注意的是。
in it list,这里边使用到了malloc函数,然后增加动态数组的长度,或者说增加顺序表长度,这个函数里边又使用到了malloc和free这两个函数。
malloc和free包含在了这个头文件当中,所以如果大家自己写代码,需要使用到malloc和free这两个函数的话,需要include这个头文件。好。
那接下来来分析一下这段代码运行的过程,首先在main函数里声明一个顺序表执行完这句代码之后,其实计算机会在内存当中开辟这样的一小片空间。这片存储空间存放了呃,这个顺序表当中的这几个变量max size表示的是顺序表的最大容量。
然后lens表示的是当前这个顺序表当中有几个数据元素,而data它是一个指针类型的变量。好,那接下来会开始执行我们定义的这个基本操作,也就是初始化顺序表在这个函数的第一句会调用malloc函数。
malloc函数会申请一整篇连续的存储空间。这片存储空间的大小应该是能够存得下十个int类型数据的啊,这样的一个大小接下来malloc函数会返回一个指针,我们把这个指针的类型把它。转换成和这儿相统一的呃指针类型。
然后把my lock返回的这些指针的值,把它赋给data。那之前我们说过my lock返回的是这一整片连续存储空间的起始地址,所以在执行完这句代码之后data这个指针应该是指向了这个位置。再次强调。
需要把me lock返回的指针把它转换成我们这定义的同类型的指针好,那除了data之外,我们还需要把呃顺序表的当前长度length把它设为零。然后把顺序表的最大容量把它设置为这个初始值。和这个地方保持一致。
好,那接下来我们省略了一些代码,可以往这个顺序表当中插入数据,把它都给填满,那此时lens的值就应该是。
10 max size的值也应该是十再往后,如果还想存入一些数据的话,这个顺序表的大小是不是就不够了?所以在这个地方,我们实现了一个函数。
动态的增加,这个数组的长度或者说增加这个顺序表的长度,那这有个参数line。这个参数表示的是我需要拓展啊,多少的长度。
那我们这传入五也就说想要让这个顺序表可以再多存五个数据元素。好,那第一句我们定义了一个指针p把顺序表的data指针的值赋给这个p,也就是说这个p指针和data是指向了同一个位置。接下来要调用mo loc函数mo loc函数所做的事情是申请一整片的内存空间。
这片空间的大小应该能够存得下当前的所有的这些数据元素。同时还可以再多存五个啊,新的数据元素,当然这还需要乘以每一个数据元素的大小size of element type。也就是需要使用size off这个关键字。
这样的话就意味着这开辟了一片新的空间,这片空间可以存15个元素。以前只能存十个,现在可以多存五个好,那由于它申请的内存空间是另一片内存空间。
而这片内存空间此时啊,并没有往里边存任何数据。接下来,我们让data这个指针指向新的这一片空间,然后再用一个for循环。
把以前这一片内存空间的这些数据把它给挪过来。然后由于顺序表的最大容量增加了这么多,所以我们需要把max size的值把它加五,也就是变成了15。最后要做的一件事情是调用free函数。
free函数会把p这个指针所指向的这一整片的啊,存储空间给释放掉。把它归还给系统,那由于p这个变量,它是一个局部于这个函数的变量。
所以当这个函数执行结束之后。存储p这个变量的这些内存空间会被系统自动的回收,所以这就用my lock实现了一个呃动态数组的扩展,或者说顺序表的扩展。那由于我们需要把数据复制到这个新的区域因此虽然动态分配这种方式可以让顺序表的啊。
大小能够灵活的改变,但是其实时间开销还是很大的。另外C语言基础好的同学可能知道一个叫做re loc的函数,这个函数其实也可以实现,这边我们所提到的这一系列的过程和功能。
但是re loc这个函数,其实它调用的过程当中会有一些意想不到的坑,所以我建议大家最好还是自己使用。和free这一对函数,并且用这两个函数更能理解我们这个动态。
分配它背后所发生的这些过程。好的,那么我们介绍了顺序表的两种实现方式,第一种是静态分配,第二种是动态分配。
那不管是用哪种方式实现顺序表都具有这样的一些特性。第一,叫随机访问,也就说可以在常数级的时间复杂度内就可以找到第二个元素。原因就在于顺序表当中各个数据元素的存放位置是连续存放的。
因此只需要知道第一个数据元素的存放地址,那么后面这些数据元素的存放地址就可以马上算出来。所以可以在常数集的时间内找到第二个元素,那对应我们的代码,其实就是我们用数组。
然后给一个数组下标。就可以直接找到第二个元素,当然其实系统在背后还做了计算地址等等,这一系列的操作。那顺序表的第二个特点是存储密度高。
每个存储节点只存储数据元素本身,但如果我们采用链式存储的话,除了存储数据元素本身之外,还需要耗费一定的存储空间来存放指针,这样的信息。
所以这是存储密度高的意思。第三个特点是拓展容量不方便静态分配,这种方式直接就是不可以拓展容量。而动态分配这种方式,虽然可以拓展容量。
但是由于我们需要把数据给复制到新的区域,所以其实时间复杂度也比较高。第四个特点是插入删除操作不方便,需要移动大量的元素。那这个特性。
我们会在下个小节当中结合具体的代码,让大家有更直观的体会好的,那这个小节我们介绍了顺序表的定义。所谓顺序表,其实就是用顺序存储的方式实现的线性表。
顺序存储的存储结构就决定了啊,逻辑上相邻的数据元素在物理上也相邻。那我们介绍顺序表的两种实现方式,分别是静态分配和动态分配。静态分配的代码很好写的。
就是定义一个大家很熟悉的数组,而动态分配里边,我们需要使用到m lock和free这两个函数。mo loc函数可以申请一整片的呃内存空间,如果当前顺序表的容量不够的话。
那么我们可以用mo loc再申请一片更大的存储空间。然后把数据元素复制到这个新的区域,并且用free函数释放掉原来的这个内存区域,把它归还给系统。这个函数在考研当中是十分重要的。
大家一定要自己动手写一写,熟悉它的用法,最后我们介绍顺序表的几个特点,最重要的特点是这个。随机访问的特点可以在o1的时间复杂度内找到第二个元素。
这个美好的特性就是由于啊,数据元素在内存当中连续存放而导致的。下一个小节当中,我们会介绍呃,怎么实现顺序表的插入和删除这两个基本操作。
到时候大家会更直观的体会到什么叫插入和删除数据不方便。好的,那以上就是这个小节的全部内容。