< Data Structures andAlgorithms in Python > Michael T.Goodrich,Roberto Tamassia,Michael H.Goldwasser 学习笔记
python中创建数组,计算机系统先会创建一个低层次数组,以便系统为其存储分配连续内存,通常情况下,底层数组会比列表的长度更长。当我们声明一个列表list,并向其中添加元素,一旦元素数量超过底层数组的大小,列表类会向系统重新申请一个更大的底层数组,并使这个更大的底层数组的前面部分和原来的数字一样。这时原来的数组就不需要了,会给系统回收,这样一来,可以继续向列表中添加元素了(元组和字符串不需要考虑,因为元组和字符串对象实例化后就不可变,底层数组大小也就确定了)。如下代码及其结果给出证明:

import sys
data = []
n = 20
for k in range(n):
    a = len(data)
    b = sys.getsizeof(data)
    print('Length:{0:3d};Size in bytes:{1:4d}'.format(a,b))
    data.append(None)

python 动态数组常用函数 python创建动态数组_时间复杂度


sys模块中的getsizeof函数用于给出python中存储对象的字节数 在上面图片中可以看出,一个空列表占了64个字节的内存。python中每个列表对象除了存储元素之外,还需要存储一些状态,比如说列表当前存储的元素个数等。当我们添加第一个元素的时候,字节数从64调到了96,增加了32个字节,由于本实验是在64位机器上运行的,32个字节可以存储4个对象引用。这和我们之前所述一致,当对列表添加第2、3、4个元素的时候,占用的内存大小并没有变化,以此类推当我们添加第5、9、17个元素的时候,系统都会给列表重新分配新的底层数组,以便存储更多的元素。

实现动态数组

当底层数组python 动态数组常用函数 python创建动态数组_python_02已满时,需要添加元素,系统会进行如下操作:

  1. 分配一个更大的数组python 动态数组常用函数 python创建动态数组_数组_03
  2. python 动态数组常用函数 python创建动态数组_数据结构_04
  3. python 动态数组常用函数 python创建动态数组_数组_05,即之后使用python 动态数组常用函数 python创建动态数组_数组_03来代替python 动态数组常用函数 python创建动态数组_python 动态数组常用函数_07
  4. 释放原数组所占用的内存。

    创建数组python 动态数组常用函数 python创建动态数组_python 动态数组常用函数_08

    把A中元素存入python 动态数组常用函数 python创建动态数组_python 动态数组常用函数_08

    释放内存

摊销分析

根据以上分析,data.append(None)操作的时间复杂度不是每次都是python 动态数组常用函数 python创建动态数组_python_10,当底层数组已满时,进行data.append(None)操作,其时间复杂度就会变为python 动态数组常用函数 python创建动态数组_数组_11,n为原来底层数组的大小。所以列表append操作的时间复杂度和底层数组的大小有关系,如果底层数组大小无限大,那append操作的时间复杂度就是python 动态数组常用函数 python创建动态数组_python_10,但是这样会占过多的内存,明显不可取。每次底层数组存满,创建一个多大的底层数组来替换原先的底层数组会很大程度地影响append的性能。我们需要在运行效率和内存使用之间选取有个折中的方案。

大小按几何增长

假如当前底层数组大小为c,当底层数组存满时,系统创建新的底层数组,其大小为2c(c的倍数)。我们能够证明这样的机制摊销运行时间为python 动态数组常用函数 python创建动态数组_python_10,证明如下:

我们假设底层数组初始大小为c,每次增长原来底层数组的2倍。第一次添加c个元素,其时间复杂度为python 动态数组常用函数 python创建动态数组_时间复杂度_14,再添加c个元素,其时间复杂度为python 动态数组常用函数 python创建动态数组_时间复杂度_15(底层数组满时,需要创建数组并将原来的数据存入新数组,时间复杂度为python 动态数组常用函数 python创建动态数组_时间复杂度_14,再加上之后添加的c个元素,其时间复杂度为python 动态数组常用函数 python创建动态数组_时间复杂度_15),再添加2c个元素,其时间复杂度为python 动态数组常用函数 python创建动态数组_数组_18,再添加8c个元素,其时间复杂度为python 动态数组常用函数 python创建动态数组_时间复杂度_19,以此类推。

如图:

python 动态数组常用函数 python创建动态数组_数组_20

添加元素

c

c

2c

4c

8c

……

添加时间复杂度

O(c)

O(2c)

O(4c)

O(8c)

O(16c)

……

总元素n

c

2c

4c

8c

16c

……

总时间复杂度

O(c)

O(3c)

O(7c)

O(15c)

O(31c)

……

python 动态数组常用函数 python创建动态数组_python 动态数组常用函数_21
python 动态数组常用函数 python创建动态数组_python 动态数组常用函数_22
根据上面的两个公式,我们可以得出时间复杂度为python 动态数组常用函数 python创建动态数组_数组_23
因此,每个添加操作的摊销运行时间为python 动态数组常用函数 python创建动态数组_python_10

避免使用固定增量

设固定增量为c,和上面分析的一样:设起始为空底层数组,大小为c,每添加c个元素所花费的时间为c,2c,3c,……,mc,总共添加了python 动态数组常用函数 python创建动态数组_时间复杂度_25个元素。其总共花费的时间为
python 动态数组常用函数 python创建动态数组_python 动态数组常用函数_26
因此,执行python 动态数组常用函数 python创建动态数组_数据结构_27个append操作花费的时间为python 动态数组常用函数 python创建动态数组_数据结构_28,这个的性能明显几何增长的要差。

Python列表的效率

data、data1、data2的长度分别为python 动态数组常用函数 python创建动态数组_数据结构_29python 动态数组常用函数 python创建动态数组_python_30python 动态数组常用函数 python创建动态数组_python 动态数组常用函数_31

操作

运行时间

data[j] = val

data.append(value)

data.insert(k,value)

data.pop()

data.pop(k)

del data[k]

data.remove(value)

data1.extend(data2)

data1 += data2

data.reverse()

* 摊销 :改变数组大小的操作,有些情况下需要改变底层数组,所以需要进行摊销的操作。