文章目录
Python序列类型
列表(list)、元组(tuple)、字符串(str)
每个类别都使用数组这种低层次的概念表示序列。
低层次数组
计算机存储:
- 典型的存储单元为字节,相当于8位。
- 每个单元与存储地址相关联。
- 访问元素的时间为 O ( 1 ) O(1) O(1)。
- 存储在主存(随机存储存储器RAM)当中,任何字节都能被有效地访问。
编程语言:
- 标识符与地址构建对应关系;
- 数组:一组变量能一个接一个地存储在计算机存储器的一块连续区域内。
例子:
- 每个Unicode字符对应16位。
- 我们将数组中的每个位置称为单元(不再拘泥于8位)。
- 数组中的每个单元占据着相同数量的字节。目的是为了允许索引值能在常量时间内访问数组内的任一单元。
- 索引方式: s t a r t + c e l l s i z e ∗ i n d e x start+cellsize*index start+cellsize∗index
引用数组
缘由:存储元素不同,所需要的内存也不同,但数组要求单元大小一致,这会严重浪费内存空间,因此采用引用数组。
- 在底层,存储的是一块连续的内存地址序列,这些地址指向一组元素序列。
- 每个元素存储地址的位数是固定的(32/64)
- 当列表元素改变时,不过是更改地址。两个列表元素一致时,指向的可能是同一个元素,这称为元素共享。
当序列不可变时,以上的分析无效。
拷贝
a = [1,2,3]
b = list(a)
以上是一个浅拷贝操作,相当于构建了一个和a指向同样元素的列表,当a中存在可变元素时,改变这个可变元素的值,b也会发生改变,但是深拷贝就不会,浅拷贝复制的列表指向了不同的地址的相同元素。
Example
counters = [0]*8
若counters[2]+=1,不过是改变了存储地址。
extend()的作用也是一样的,将引用添加至末尾。
紧凑数组(Compact Arrays)
缘由:紧凑数组的优势:
1.占用更少的内存;(不用存储地址)
2.原始数据在内存中是连续存放的。便于高性能的计算。
创建:
import array
import sys
arr_1 = ('i',[1,2,3,4,5])
arr_2 = [1,2,3,4,5]
print(sys.getsizeof(arr_1),sys.getsizeof(arr_2)) #56 96
不支持用户自定义数据类型。
动态数组
以下内容与元组和字符串无关。
动态数组是一种算法技巧,是底层数组的一种特性。
- 创建数组时,必须声明其长度。
- 一张列表通常关联着一个底层数组,该数组通常比列表长度更长。
- 当容量全部被填满时,动态数组机制触发。
数组扩展流程如下:
1.A数组存储已满,新的元素需要加入;
2.向系统申请新的数组B,数组B的长度通常为为A的两倍;
3.元素进入B,数组A舍弃;
4.新的元素进入B。
Example
我们创建一个数组,当数组中不含任何元素时,已经占用了一些内存,其中保存了一些数组的信息。当我们给予第一个元素时,数组扩展了32个字节,预留了3个隐藏字节。当填入第5个元素时,给元素提供的内存达到了64,扩展了一倍,这与先前所介绍的完全吻合。
摊销
命题-1:设S是一个由具有初始大小的动态数组实现的数组,实现策略为:当数组已满时,将此数组大小扩大为原来的2倍。S最初为空,对S连续执行n个添加操作的运行时间为O(n)。
该命题的证明方法采用了摊销分析。
每次操作的摊销运行时间为O(1)。
摊销在这里,是一种评估运行时间的操作,因为数组添加操作的运行时间不是统一的,我们通过取平均的方式予以可度量化。
在实际的操作过程中,每一个增添步骤的时间复杂度不是一致的,在摊销边界处,时间复杂度会更大一些。
事实上,在数组未满时,添加操作的复杂度是 O ( 1 ) O(1) O(1);
而扩张时,复杂度为 O ( n ) O(n) O(n)
一般而言,当数组满时,增添元素,数组长度变为原来的两倍。减少元素,当为原来的四分之一时,数组长度变为原来的一半。
大小按几何增长
数组大小以任意几何增长基数扩大,每次操作的摊销运行时间仍然为 O ( 1 ) O(1) O(1).
避免使用等差数列
等差数列策略:每次调整数组大小时,预留固定数量的额外单元。
命题-2:对初始为空的动态数组执行连续n个增添操作,若每次调整数组大小时采用固定的增量,则运行时间
Ω
(
n
2
)
\Omega(n^2)
Ω(n2)
也就是说,执行n个append操作花费的时间为
O
(
n
2
)
O(n^2)
O(n2);
而采用几何增长的话,是
O
(
n
)
O(n)
O(n)。
Python序列类型和效率
Python的列表和元组类
元组比列表的内存利用率更高,因为元组固定不变,不需要创建额外剩余空间。
- 常数时间操作:1&2;
- 查找:3&4&5;
- 比较:6;
- 创建新的对象:7&8&9。
- 改变列表中的值(9):
Python的字符串类
其中最值得一提的是
str1 += str2
这一操作与列表中的可不一样。
比如我们想从一个字符串中提取所有的字母:
这一操作的时间复杂度是
O
(
n
2
)
O(n^2)
O(n2)。
在操作过程中,是一个求和公式。
这是因为,字符串不可变,每次都是创建一个新的字符串。
为了更高效的解决这个问题,我们可以这么做:
这样,花费的时间仅仅为
O
(
n
)
O(n)
O(n)。
排序
插入排序
最坏的情况(反序)为
Ω
(
n
)
\Omega(n)
Ω(n),最好的情况(顺序)为
O
(
n
)
O(n)
O(n)
#升序 7行
for k in range(1,len(A)): # 起始不是0
cur = A[k] # 给为排序最左标记
j = k
while j > 0 and A[j-1] > cur: # 满足两个条件
A[j] = A[j-1] # 移动位置
j -= 1
A[j] = cur # 最后才插入
归并排序
共logn层,每一层有n个操作。共O(nlogn)。
多维数据集
list、tuple、string都是一维的;
但是很多应用都需要高维数据:
- 计算机图形:2D or 3D;
- 地理信息: 2D;
- 医学图像: 3D;
- …
data = [[1,2,3],[4,5,6],[7,8,9]]
data[1][2]
二维数据集通常表示为列表的列表,索引方式也已经给出。
创建多维列表
data = ([0]*c)*r : 长度为rc的一维数组
data = [[0]*c]*r : 结构类似,但是是重叠的。
数组的数组,其内部是可变元素(数组),因此出现了混叠,和浅拷贝类似。
data = [[0]*c for j in range(r)]