Chapter 4 数组和链表

定义一个Python的数组类Array

与列表array的差异在于数组不能添加或删除某个位置的项,或让数组长度变大或变小

下面给出Array类的具体功能


下面给出Array类的代码

"""An array is like a list, but the client can use only [], len, iter and str.To instantiate, use = Array(, )The fill value is None by default."""
class Array(object):
"""Represents an array"""
def __init__(self, capacity, fillValue = None):
"""capacity is the static size of the array.fillValue is placed at each position"""
self._items = list()
for count in range(capacity):
self._items.append(fillValue)
def __len__(self):
"""-> The capacity of the array."""
return len(self._items)
def __str__(self):
"""-> The string representation of the array."""
return str(self._items)
def __iter__(self):
"""Supports traversal with a for loop."""
return iter(self._items)
def __getitem__(self, index):
"""Subscript operator for access at index"""
return self._items[index]
def __setitem__(self, index, newItem):
"""Subscript operator for replacement at index"""
self._items[index] = newItem

下面给出了数组的用法

from arrays import Array
a = Array(5)
len(a)
5
print(a)
[None, None, None, None, None]
for i in range(len(a)):
a[i] = i + 1
a[0]
1
for item in a:
print(item)
1
2
3
4
5

可以看到,数组是列表的一个非常受限制的版本

4.1.1 随机访问和连续内存


为了简化,假设每一个数据项占用一个内存单元。数组的基本地址是数组的第一项的机器地址。

获取数组内存块的基本地址

给这个地址加上偏移地址(索引),返回最终结果

例如示例中内存块的基本地址是,并且每一项都需要一个单个的内存单元。然后,位于索引位置2的数据项的地址就是


需要注意的重要的一点是随机访问不一定在数组总搜索一个给定单元,否则计算机必须要从第一个单元开始访问,直到打到了第n个单元。

常量时间的随机访问,可能是数组最想要的功能。然而,这一功能要求数组必须用一段时间连续的内存来表示,当在数组上实现其他操作的时候,这会需要一些代价。

4.1.2 静态内存和动态内存

在FORTRAN和Pascal这样层级的语言中,数组是静态的数据结构。数组的长度和大小都是在编译时确定的。缺点就是内存过剩或者内存不足。

Java和C++的现代语言,通过允许程序员创建动态数组来弥补这一问题,动态数组占据了连续的内存块且支持随机访问。

Java和C++可以在实例化时指定动态数组长度。

动态调整逻辑

程序开始时创建一个具有合理默认大小的数组

当这个数组不能保存更多数据时,创建一个新的、更大的数组,将旧数组数据转移到新的数组

当数组在浪费内存的时候,以类似上述方式减少长度

4.1.3 物理大小和逻辑大小

物理大小即数组单元总数,逻辑大小是可供应用程序使用项的大小。

b = Array(3)
b[0] = 1
print(b)
[1, None, None]

上述数组b的物理大小为3,逻辑大小为1

4.2 数组的操作

DEFAULT_CAPACITY = 5 #物理大小为5
logicalSize = 0 #初始逻辑大小为0
a = Array(DEFAULT_CAPACITY)

4.2.1 增加数组大小

当需要插入新的项且数组的逻辑大小等于其物理大小,那么就该增加数组的大小,Python的list类型通过调用insert或append方法来执行这种操作。调整过程如下:

if logicalSize == len(a):
temp = Array(len(a)+1)
for i in range(logicalSize):
temp[i] = a[i]
a = temp

此时旧数组的内存留给了内存回收程序。当调整数组大小时,上述方法是线性的,即给一个数组添加n项的时间是,即


增加数组大小时可以通过给数组大小加倍,从而取得一个较为合理的时间性能,如:

temp = Array(len(a) * 2)

但上述时间性能的获取是对内存的浪费

4.2.2 减少数组大小

当数组的逻辑大小小于或等于其物理大小的四分之一,并且物理大小至少是其创建数组默认容量两倍时,该算法将数组容量减少为原来的一半,代码如下:

if logicalSize <= DEFAULT_CAPACITY//4 and len(a) >= DEFAULT_CAPACITY * 2:
temp = Array(len(a)//2)
for i in range(logicalSize):
temp[i] = a[i]
a = temp

4.2.3 插入数组一项

插入与替代不同,插入之前需要做如下

插入或增加数目之前,检查可用空间

从数组的逻辑末尾开始,直到目标索引位置,将每一项向后移动一个单元,在目标索引出为新项打开一个洞

将新的项赋值给目标索引位置

将逻辑大小增加1

代码如下, 在这样的操作中,移动项的平均时间是线性的,因此插入操作是线性的。

# Increase physical size of array if necessary
# Shift items down by one position
for i in range(logicalSize, targetIndex, -1): #此时logicalSize的大小即为数组最后一项的索引+1,注意range区间前开后闭,逆序亦如此
a[i] = a[i-1]
# Add new item and increment logical size
a[targetIndex] = newItem
logicalSize += 1

4.2.4 从数组中删除一项

删除是插入的反过程,该过程步骤如下:

从紧跟目标索引位置的后一项开始,直到数组的逻辑末尾,将每一项向前移动一位

将逻辑大小减少1

检查浪费空间,如有必要,将数组物理大小减少1

# Shitf items up by one position
for i in range(targetIndex, logicalSize-1):
a[i] = a[i+1]
# Decrement logical size
logicalSize -= 1
# Decrease size of array if necessary

4.3 多维数组

"""二维数组"""
from arrays import Array
class Grid(object):
"""Represents a tow-dimensional array"""
def __init__(self, rows, columns, fillValue = None):
self._data = Array(rows)
for row in range(rows):
self._data[row] = Array(columns, fillValue)
def getHeight(self):
"""Returns the number of rows"""
return len(self._data)
def getWidth(self):
"""Returns the number of columns"""
return len(self._data[0])
def __getitem__ (self, index):
"""Supports two-dimensional indexing with [row][column] """
return self._data[index]
def __str__ (self):
"""Returns a string representation of grid"""
result = ""
for row in range(self.getHeight()):
for col in range(self.getHeight()):
result += str(self._data[row][col]) + " "
result += "\n"
return result
from grid import Grid
table = Grid(4, 5, 0)
print(table)
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
# Go through rows
for row in range(table.getHeight()):
# Go through columns
for col in range(table.getWidth()):
table[row][col] = int(str(row)+str(col))
print(table)
0 1 2 3 4
10 11 12 13 14
20 21 22 23 24
30 31 32 33 34