数组理论基础
数组可以说是程序中最基础的数据结构,不管在学习还是面试中一般思维上都不难,但是想要好好的实现和掌握也并不是一件简单的事情。接下来将从数组的概念、数组的内存关系以及一些经典的使用数组的算法思想题目来进行介绍。
什么是数组
数组是存放在连续内存空间上的相同类型数据的集合。 数组可以方便的通过下标索引的方式获取到下标对应的数据。
在程序中,经常需要将一组(通常为同一类型)数据元素作为整体管理和使用,需要创建这种元素组,用变量记录他们,元素可能加入或者删除,元素在序列里的位置和顺序表示实际应用的某种有意义的信息或是数据关系,数组(或称线性表)就是这样一组元素的抽象。
本次虽然不会针对数组的具体实现进行深入,但是从数据结构的实现上来说,在设计的时候应该考虑实现如下功能。
ADT List: # 抽象数据类型
List(self) # 构造操作,创建一个新数组
is_empty(self) # 判断self是否为空
len(self) # 获取self的长度
prepend(self, elem) # 将元素加入数组作为第一个元素
append(self, elem) # 将元素加入数组作为最后一个元素
insert(self, elem, i) # 将元素加入数组作为第i个元素,其他元素的顺序不变
del_first(self) # 删除首元素
del_last(self) # 删除尾元素
del(self, i) # 删除第i个元素
search(self, elem) # 查找元素elem在list中出现的位置,不出现时返回-1
forall(self, op) # 对数组的每个元素执行操作op
数组的内存管理
首先在计算机中,程序直接使用的数据保存在内存中,内存是cpu可以直接访问的数据存储设备,与之相对的是外存,如光盘、磁带等,保存在外存的数据要先装入内存,cpu才能使用它们。
内存的基本结构是线性排列的的一批存储单元,每个单元的大小相同,可以保存一个单位大小的数据。具体单位大小可能因计算机不同而不同,常见情况下一个单元可以保存一个字节(8位二进制码)数据,因此存放一个整数或者浮点数需要连续几个单元,如标准浮点数需要8个单元(flot64 8个字节)。
内存单元有唯一编号,称为单元地址或内存地址,而在数据中通常数据存放情况如下:
| 内存地址 | 200 | 201 | 202 | 203 |
|---|---|---|---|---|
| 字符数组 | C | O | D | E |
| 下标 | 0 | 1 | 2 | 3 |
测试代码如下:
func main() {
arrary := []byte{'c', 'o', 'd', 'e'}
for i, _ := range arrary {
fmt.Println(&arrary[i])
}
}
打印的结果如下,可以看到是连续的四个字节
0xc0000ae004
0xc0000ae005
0xc0000ae006
0xc0000ae007
需要注意:
- 数组下标都是从0开始的。
- 数组内存空间的地址是连续的
- 动态语言数组的内存地址不严格连续如python,因为连续的数组内存中存储引用即是元素的地址,虽然对于数组本身来说是连续的,但是元素对象可能地址不连续
而因为这个连续的特性,我们在操作数组元素的时候可能会难免移动其他元素的地址。如果要删除上述示例的第三个元素,则会变成如下:
| 内存地址 | 200 | 201 | 202 |
|---|---|---|---|
| 字符数组 | C | O | E |
| 下标 | 0 | 1 | 2 |
那么二维数组会是什么样呢,看下表
| 列索引0 | 列索引1 | 列索引2 | 列索引3 | |
|---|---|---|---|---|
| 行索引0 | 3 | 1 | 2 | 8 |
| 行索引1 | 4 | 5 | 6 | 2 |
| 行索引2 | 4 | 5 | 2 | 3 |
二维数组的内存地址是否连续在不同语言的实现有所不同,下面以golang为例
func main() {
// 创建一个二维数组
array := [3][4]int{
{3, 1, 2, 8},
{4, 5, 6, 2},
{4, 5, 2, 3},
}
// 打印二维数组中每个元素的地址
for i := 0; i < len(array); i++ {
for j := 0; j < len(array[i]); j++ {
fmt.Printf("array[%d][%d] = %d, Address = %p\n", i, j, array[i][j], &array[i][j])
}
}
}
打印结果如下
array[0][0] = 3, Address = 0xc000068060
array[0][1] = 1, Address = 0xc000068068
array[0][2] = 2, Address = 0xc000068070
array[0][3] = 8, Address = 0xc000068078
array[1][0] = 4, Address = 0xc000068080
array[1][1] = 5, Address = 0xc000068088
array[1][2] = 6, Address = 0xc000068090
array[1][3] = 2, Address = 0xc000068098
array[2][0] = 4, Address = 0xc0000680a0
array[2][1] = 5, Address = 0xc0000680a8
array[2][2] = 2, Address = 0xc0000680b0
array[2][3] = 3, Address = 0xc0000680b8
可以看到每个元素地址之间相差八个字节(我所运行的环境是64位系统,int为8字节大小),并且在换到第二行数组的地址的时候也是连续的(注意16进制,满16进1)
数组相关的介绍就到这里了,和数组有关的算法会分类写在后面的文章。