在编程语言中,常采用实现集合的两种数据结构是数组和链表结构。这两种类型的结构采用不同的方法在计算机内存中存储和访问数据。这些方法反过来导致了操作该集合的算法中的不同的时间/空间取舍。
目录
1. 数组数据结构
引用维基百科上的定义:数组由相同类型的元素(element)的集合所组成的数据结构,分配一块连续的内存来存储。利用元素的索引(index)可以计算出该元素对应的存储地址。数组表示的是可以在给定的索引位置访问或替代的项的一个序列。这和python中的列表非常相似,实际上pyhon列表的底层数据结构就是数组。数组的长度或容量在创建的时候就固定下来了。python中的array模块包含了array类,但是它只能存储同种类型的变量。
接下来用python内置的array模块简单的创建一个数组实例对象,这不是我们本篇文章的重点,所以只做简单演示:
python的第三方库numpy中的array的功能也非常强大,有兴趣的可以自己查阅下资料。
我们也可以自己定义一个Array类,来实现一些简单的功能
1.1 随机访问和连续内存
计算机通过为数组项分配一段连续的内存单元,从而支持对数组的随机访问。数组在内存中是按顺序存放的,可以通过下标直接定位到某一个元素存放的位置。所以不管数组多大,它访问第一个元素所需的时间和访问最后一个元素需要的时间是一样的。
上面是数组的内存图。假设第一个元素的地址值为2000,由于数组中存储的是int整数,一个整数占4个字节,那么第二个元素的地址值为2004,第三个为2008,第四个为2012。如果第i个位置处的地址值可以通过获取。其中第一个元素的地址值为数组的基本地址。
1.2 静态内存和动态内存
有些语言中,数组是静态数据结构。数组的长度和大小都是在编译时确定的。但是在显示情况中,数组的长度是变化的,我们并无法事先确定数组的大小。为了保险起见我们可以定义一个长度尽量长的数组,但是如果我们只存储一个元素,那么这显然是对空间的浪费;为了节省空间我们定义一个长度较短的数组,同样的,我们要存储的元素数大于数组长度时,这个数组就"放不下”需要存储的数据。在Java和C++中支持动态数组,动态数组同样占据了连续的内存块并支持随机访问。在运行时不需要指定动态数组的长度,只需要在实例化的时候指定动态数组的长度即可。python中同样支持动态数组。
1.3 物理大小和逻辑大小
物理大小:数组单元的总数,或者说创建数组的时候,用来指定其容量的数字。
逻辑大小:当前可供应用程序使用的项的数目,也就是被占用的单元数
2. 数组的操作
2.1 增加数组的大小
当要插入新的项时,并且此时数组满了,也就说物理大小和逻辑大小相等,这时候就需要增加数组的大小了。调整大小的过程包括3个步骤:
1. 创建一个新的,更大的数组
2. 将数组从旧的数组复制到新的数组中
3. 将旧的数组变量重新设置为新的数组对象
DEFALUT_CAPACITY = 5
logicalSize = 0
a = Array(DEFALUT_CAPACITY)
if logicalSize == len(a):
temp = Array(len(a) + 1) # 创建临时数组,长度为原数组+1
for i in range(logicalSize): # 将旧数组中的元素拷贝到临时数组中
temp[i] = a[i]
a = temp # 将临时数组赋值给a
给数组添加n项的时间复杂度为。当创建临时数组的时候,我们double数组的大小,即为:
temp = Array(len(a) * 2)
这样时间复杂度降低了,但是牺牲的是内存空间。
2.2 减小数组的大小
当数组的逻辑大小缩小时,就会浪费内存空间。将数组中逻辑大小与物理大小之比定义为装载因子(load factor),当装载因子小于0.25时,我们可以采取减小数组的操作,这是跟增加数组是相反的操作,其步骤如下:
1. 创建一个新的,更小的数组
2. 将数组从旧的数组复制到新的数组中
3. 将旧的数组变量重新设置为新的数组对象
if logicalSize <= len(a) // 4 and len(a) >= DEFAULT_CAPACITY * 2: # 当装载因子小于0.25且数组长度大于等于默认容量的2倍时
temp = Array(len(a) // 2) # 创建临时数组,大小为原数组长度的二分之一
for i in range(logicalSize):
temp[i] = a[i]
a = temp
2.3 向数组中插入元素
向数组中插入元素步骤如下:
1. 因为插入元素会改变数组的逻辑大小,先看看数组的物理大小够不够,若不够那么增加数组大小
2. 从数组的逻辑末尾开始,直到目标索引位置,将每一项向后移动一位。
3. 将新的元素赋值给目标索引位置
4. 将逻辑大小增加1
# 如果需要,增加数组的物理大小
for i in range(logicalSize,targetIndex,-1): # 对数组中元素进行下移操作
a[i] = a[i-1]
a[targetIndex] = newItem # 将新的元素赋值给目标索引处
logicalSize += 1 # 将逻辑大小增加1
2.4 从数组中删除元素
从数组中删除元素是向数组中插入元素的反过程。步骤如下:
1. 从目标索引位置后一位置开始,到数组的逻辑末尾,将每一项都向前移动一位。
2. 将逻辑大小减1
3. 检查浪费的空间,有必要的话,将物理大小减1
for i in range(targetIndex,logicalSize-1): # 对数组中元素进行上移操作
a[i] = a[i + 1]
logicalSize -= 1 # 将逻辑大小减1
# 如果需要,减小物理大小
3. 二维数组
前面讲述的都是一维数组,二维数组类似于矩阵或者说是一个表格。使用两个下标来指定其行和列的位置。
3.1 定义Grid类
3.2 剑指offer:二维数组中的查找
实际上数组在python语言都可以被列表来代替,或者直接使用numpy.array来将列表转换为数组格式。