Python全栈(二)数据结构和算法之2.顺序表

引入:

几个问题:
1.列表的下标为什么从0开始;
2.为什么列表append比insert快;
3.列表append之后,id值为什么不变,即地址不变。

一、内存&类型&连续存储

内存:
单位:1字节=8位
有一个int值1,32位电脑1个整型int占4个字节,1转换成8位为0000 0001,对应有一个内存地址。
有多个整型时,如7、21、39,在内存中分别存放,地址不是连续的,如0x01、ox09、0x17,
如果放在一起,如7为0x01,则21、39分别为0x05、0x09是连续存储的,
取39时,如果知道7的地址0x01,则39的位置为0x01+2*4=0x09。
类型决定了数据在计算机中占多大的存储单位,如一个int占4个字节。
如图
内存和存储

二、顺序表的基本形式

1.基本顺序表

li = [200,389,78,12]
存储li时,会在内存申请5个连续的地址,存放这5个数,分别对应着5个连续的地址,此即基本顺序表
基本顺序表:
元素连续存储,每个元素所占的存储单元大小固定相同,下标是逻辑地址。
Loc(ei) = Loc(e0) + c*i
其中,Loc(ei)是待确定元素地址,Loc(e0)是起始地址,i表示第i个元素,c表示存储单元大小。
li指向首个元素的地址。
基本顺序表存储的数据类型是一样的。

2.元素外置顺序表

列表存储的数据类型不一样时,如
li = [12,'ab',1.11,1000]
先保存200,有一个内存地址,再保存字符串,但是不是连续的,再保存浮点数,和后边的整型,都不是连续保存的,再将它们的内存地址连续保存在内存的其他区域,存储的地址也会占用4位,内存地址的地址是连续的,此即元素外置顺序表
元素外置顺序表:
将实际数据元素另行存储,而顺序表中各单元位置保存对应元素的地址信息(即链接)。由于每个链接所需的存储量相同,通过上述公式,可以计算出元素链接的存储位置,而后顺着链接找到实际存储的数据元素。
这时c不再是数据元素的大小,而是存储一个链接地址所需的存储量,这个量通常很小。
外置顺序表也被称为对实际数据的索引,这是最简单的索引结构。
li指向首个元素地址的对应地址。
顺序表两种基本形式图示:
基本顺序表和元素外置顺序表
两种基本形式的简单存储方式示意:
两种基本形式

解答1:

列表的下标为什么从0开始
下表最确切的定义是偏移(offfset),a[k]表示便宜k个存储单元大小的位置。
a[k]_address = base_address + k * type_size
如果下表从1开始,则a[k]的内存地址会变为:
a[k]_address = base_address + (k-1)*type_size
对比可知,从1开始编号,每次都多进行了一次减法运算,CPU多进行了一次减法指令。数组作为非常基础的数据结构,通过下标随机访问数组元素又是其非常基础的编程操作,效率的优化就要尽可能做到极致。为了减少一次减法运算,数组从0开始编号,而不是从1开始编号。

三、顺序表的两种基本实现方式

1.顺序表的结构:

表头信息+数据区
即顺序表的完整信息包括两部分:
※表中的数据集合
※为实现正确操作而需记录的信息:
包括元素存储区的容量和当前表已有的元素个数。
顺序表的两种基本实现形式

2.一体式结构:

存储表信息单元与元素存储区以连续的方式安排在一块存储区里,两部分数据的整体构成一个完整的顺序表对象。
结构整体性强,易于管理,但顺序表创建后,元素存储区就固定了。
表头也会有内存地址,且与元素地址是相连的。

3.分离式结构:

表对象里只保存与整个表有关的信息(即容量和元素个数),实际数据元素存放在另一个独立的元素存储区里,通过链接与基本表对象关联。
表头除了存表头信息和数据区,还保存了首个元素的地址,用以指向数据区,数据区和表头信息的地址不是连续的。
添加元素时,数据区会重新生成,但是表头地址不会变,只是改变存储的数量和表头所存的地址,列表指向表头不改变,所以改变列表不会改变内存地址。
也说明列表等数据结构使用的是分离式结构。
如图:
一体式和分离式结构

4.元素存储区替换:

一体式结构由于顺序表信息区与数据区连续存储在一起,所以若想更换数据区,则只能整体搬迁,即整个顺序表对象(指存储顺序表的结构信息的区域)改变了;
分离式结构若想更换数据区,只需将表信息区中的数据区链接地址更新即可,而该顺序表对象不变。

5.元素存储区扩充:

采用一体式结构的顺序表,进行扩充时,如不存在空白元素区时,不能直接在最后一个元素后添加,只能重新开一个内存区域,用来存储原来的元素和新增的元素,列表所指向的地址也随着数据区的改变而改变。
采用分离式结构的顺序表,若将数据区更换为存储空间更大的区域,则可以在不改变表对象的前提下对其数据存储区进行了扩充,所有使用这个表的地方都不必修改。只要程序的运行环境(计算机系统)还有空闲存储,这种表结构就不会因为满了而导致操作无法进行。人们把采用这种技术实现的顺序表称为动态顺序表,因为其容量可以在使用中动态变化。
两种实现方式扩充原理的对比
扩充方式对比
两种扩充策略:
线性增长
每次扩充增加固定数目的存储位置,如每次扩充增加10个元素位置。
特点:节省空间,但是扩充操作频繁,操作次数多。
成倍增长
每次扩充容量加倍,如每次扩充增加一倍存储空间。
特点:减少了扩充操作的执行次数,但可能会浪费空间资源。以空间换时间,推荐的方式。

四、顺序表的操作

1.增加元素:

(1)表尾端加入元素append
时间复杂度:O(1)
(2)非保序的元素插入(不常见)insert
时间复杂度:O(1)
(3)保序的元素插入insert
时间复杂度:O(n)
保序:即插入元素,原先的顺序不变。
如图,
增加元素

解答2:

为什么列表append比insert快
append时间复杂度为O(1),insert(一般为保序)复杂度为O(n),当然append更快

2.删除元素:

(1)表尾删除元素pop
时间复杂度:O(1)
(2)非保序的元素删除(不常见)remove
时间复杂度:O(1)
(3)保序的元素删除
时间复杂度:O(n)
如图,
删除元素

五、顺序表的实现

Python中的list和tuple两种类型采用了顺序表的实现技术。
tuple是不可变类型,即不变的顺序表,不支持改变的操作,因此采用一体式的结构。

list的基本实现技术:

list是一种元素个数可变的线性表,可以添加和删除元素,并在各种操作中维持已有元素的顺序(即保序)。
特征:
※基于下标(位置)的高效元素访问和更新,时间复杂度为O(1)。
list采用顺序表结构,表中元素保存在一块连续的存储区中。
※允许任意加入元素,而且在不断加入元素的过程中,表对象的标识(id值)不变。
采用分离式结构实现,是一种动态顺序表,能更换元素存储区,并且为保证更换存储区时list对象的标识id不变。

解答3:

列表append之后,id值为什么不变,即地址不变
list是采用分离式结构实现的动态顺序表,表对象里只保存与整个表有关的信息(即容量和元素个数),实际数据元素存放在另一个独立的元素存储区里,append是对独立的元素存储区进行操作,不会改变表对象,也就不会改变列表对象的地址了。

六、单向链表介绍&变量标识的本质

1.为什么需要链表:

顺序表的构建需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁,所以使用起来并不是很灵活。
链表结构可以充分利用计算机内存空间,实现灵活的动态内存管理。

2.链表的定义

链表是一种常见的基础的数据结构,是一种线性表,不像顺序表一样连续存储数据,而是在每个节点(数据存储单元)里存放下一个节点的位置信息(即地址)。
分为单链表、双向链表、循环链表
线性表:顺序表+链表

3.单链表

单链表,即单向链表,是链表中最简单的形式,每个节点包括两个域,元素域链接域
链接指向链表中的下一个节点,顺序单一,最后一个节点的链接域指向空值。
节点 = 数据区 + 链接区
如图,
单链表
其中,
表元素域elem用来存放具体的数据;
链接域next用来存放下一个节点的位置(python中的标识);
变量p指向链表的头节点(首节点)的位置,从p出发能找到表中的任意节点。
单链表存储的简单示意:
单链表存储

4.Python变量标识的本质:

变量交换:

#a的地址指向10
a = 10
#b的地址指向20
b = 20
#a指向20,b指向10
a,b = b,a

变量标识的本质就是指向变量所表示的对象的地址
公众号二维码
大家也可以关注我的公众号:Python极客社区,在我的公众号里,经常会分享很多Python的文章,而且也分享了很多工具、学习资源等。另外回复“电子书”还可以获取十本我精心收集的Python电子书

发布了51 篇原创文章 · 获赞 184 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/CUFEECR/article/details/102976684