python数据结构学习笔记(五)

 

5 Array-Based Sequence

5.2.1 referential arrays

数组在内存中是连续的地址单元,而且每个单元的大小是相同的。对于python的list来说,里面可以存储不同长度的 字符串或者其他元素,为了存下最长的那个字符串,list必须给每个单元很大的空间,这样实际上有很多单元只利用 了小部分的存储,内存的利用率很低。实际上,python的list中每个单元只存储了一个对象的引用,相当于 只保存了它的地址,这样所有对象的地址都是统一长度的.list中空的位置的引用指向None对象

5.2.2 compact arrays in python

string是字符数组,这与一般的list不同,list中存储的是引用,而string中是直接存储字符,所以可以说是 紧密的的数组。如果string中存储的是unicode,则每个字符需要2bytes的空间。

与紧密数组相比,普通list需要更多的空间,比如我们想要存储一百万个64-bit整数,我们希望每个整数只使用64 bits 来存储。实际上我们需要在数组中给每个整数存储一个64位地址的引用,另外每个int类型在python中是14byte,所以 实际上每个整数用了18bytes来存储。

array.array
from array import array
primes = array('i', [2,3,5,7])

array函数提供紧密数组,其中'i'代表integers,array()函数第一个参数是里面元素的类型。

python中的ctype模块里提供了类似c语言中的紧密数组。

5.3 dynamic arrays and amortization

创建一个低级的紧密数组的时候,必须声明数组的大小,因为系统必须给数组分配连续的内存空间。

python中的list类则提供了一种可动态扩展的数组,比如可以随时增加一个元素。在创建一个list的时候,实际上会 比它现在的长度分配多一点空间,用来给新增加的元素。如果原来预留的空间都使用完了,则list会重新向系统申请 新的空间,新的空间又比现在所有存储的元素预留多一些空间。这种做法就跟螃蟹成长的过程不断换壳一样。

len(list)可以获取当前list里面存的元素个数,但不是系统真正分配给list的内存。sys.getsizeof(list)可以 list真正的bytes。

import sys
data = []
for k in range(n):
    a = len(data)
    b = sys.getsizeof(data)
    print "length: {0:3d}; size in types: {1:4d}".format(a,b)
    data.append(None)

结果如下:

Length: 0;size in bytes: 72
Length: 1;size in bytes: 104
Length: 2;size in bytes: 104
Length: 3;size in bytes: 104
Length: 4;size in bytes: 104
Length: 5;size in bytes: 136
...

可以发现初始化一个空数组时已经分配了72bytes的空间,后面不断增加元素之后每次增加32bytes。

5.3.1 implementing a dynamic array

要实现动态增长的数组,我们可以先用一个固定数组a来存储,当a满的时候,创建更大的数组b,先使得b[i]=a[i], 然后将a指向b,这时候就可以插入新的元素了。如何确定新数组b的容量比较合适?一种做法是取b的容量刚好是a的2倍

import ctypes

class DynamicArray:
    """a dynamic array class like a simplified python list"""

    def __init__(self):
        self._n = 0
        self._capacity = 1
        self._A = self._make_array(self._capacity) # low-level array

    def __len__(self):
        return self._n

    def __getitem__(self, k):
        if not 0 <= k < self._n:
            raise IndexError('invalid index')
        return self._A[k]

    def append(self, obj):
        if self._n == self._capacity:
            self._resize(2 * self._capacity)
        self._A[self._n] = obj
        self._n += 1

    def _resize(self, c):
        """resize internal array to capacity c"""
        B = self._make_array(c)
        for k in range(self._n):
            B[k] = self._A[k]
            self._A = B
            self._capacity = c

    def _make_array(self, c):
        return (c * ctypes.py_object)()

5.4 efficiency of python's sequence types

  • tuple: nonmutating
  • list

constant-time operation

返回序列的长度只需要常数时间,因为序列中维护有这一信息可以直接返回。同样是常数时间的有下标访问data[i]

字符串拼接

如要把文档中的所有字母字符取出组成一个字符串, bad code:

letters = ''
for c in document:
    if c.isalpha():
        letters += c

这段代码是非常低效的。因为string类型是immutable的,每次执行letters += c,都要重新创建一个string,然后 对letters重新赋值,而每次创建一个字符串的时间与该字符串长度成线性关系,所以总共需要1+2+...+n = O(n*n)的时间。

一种改进的方法是使用一个list代替string拼接,最后再一次性拼接给string,时间是O(n)

temp = []
for c in document:
    if c.isalpah():
        temp.append(c)
letters = ''.join(temp)

注意最后一行''.join(temp)只需要n的时间

实际上即使是每次对list进行append操作,虽然摊还时间是O(1),但是仍然可能需要多次动态扩建list,效率不如下面这种 使用comprehension syntax理解性语法。

letters = ''.join([c for c in document if c.isalpha()])

或者连创建list的过程都不需要

letters = ''.join(c for c in document if c.islpha())

因为string是immutable的,所以很多时候要对string进行操作的时候可以先讲string转化为list,然后对其进行修改, 操作完成之后再重新赋值给string。string转为list方式如list('bird')可以得到['b', 'i', 'r', 'd']. 反过来list转为string则可以通过''.join(['b','r','i','d'])

5.6 multidimensional data sets

二维数组通常也叫矩阵。python中可以用嵌套的list来实现。

创建二维矩阵的一个错误方法如data = ([0] * c ) * r,因为结果还是一个一维矩阵。

改进一下,用data = [[0] * 3] * 2来创建2 * 3矩阵,得到结果[[0,0,0],[0,0,0]],好像满足要求。 但实际上这样做还不行,因为里面的两个list实际上指向同一个list,修改其中一个会导致两个都同时改变。

为了使里面的每个子list都是相互独立的,可以使用理解性语法来创建这样的二维list。 data = [[0] * c for j in range(r)]

posted @ 2015-07-09 10:57  Jolin123  阅读(1482)  评论(1编辑  收藏  举报