数据结构之数组

183 阅读5分钟

数组是数据结构中的基本模块之一。为了更好的理解数组,在正式介绍数组之前,我们需要了解数组和列表、集合在概念上的区别。

集合

概念:由一个或多个确定的元素所构成的整体。

特性

  1. 集合里的元素类型不一定相同。比如,可以将教室中的学生看作是一个集合,也可以将教室中的学生和物品看作是一个集合。

  2. 集合里的元素没有顺序。我们形容集合中的元素从来不会说“集合中的第七个元素”,因为集合中的元素没有顺序。

事实上,广义上的集合并不直接存在于编程语言中。但是,编程语言中的很多数据结构都是基于集合,它们在集合的基础上加上一些规则就形成了它们的数据结构。

列表

概念:又称线性列表,是一种由数据项按照一定线性顺序,排列而成的数据项的集合。

特性

  1. 列表里的元素虽然类型不一定相同,但是按照一定顺序进行了排列

  2. 列表里的元素数目是可变的,可以向列表中增加或删除元素

在编程语言中,列表最常见的表现形式有数组链表队列

数组

概念:数组是列表的实现方式之一,是数据结构中的基本模块之一。

特性

  1. **数组会用名为“索引”的数字来标识每个数据项在数组中的位置。**且在大多数编程语言中,索引是从0算起的,我们可以根据数组中的索引,快速读取数组中的元素。而列表中没有索引,这是数组与列表最大的区别。

  2. 数组中的元素在内存中是连续存储的,且每个元素占用相同大小的内存。而列表中的元素在内存中可能相邻,也可能不相邻,比如列表的另一种实现方式——链表,它的元素在内存中则不一定是连续存储的。

数组的操作

读取元素

读取数组中的元素,是通过访问索引的方式来读取的,索引一般从0开始。

在计算机中,内存可以看做是一些已经排列好的格子,每个格子代表一块内存地址。一般情况下,数据会分散地存储在内存中,即不同的格子中。

**对于数组来说,由于其元素在内存中是连续存储的,所以计算机会在内存中为其申请一段连续的内存空间,并且记下索引为0处的内存地址。**以["C", "O", "D", "E", "R"]数组为例,它的各元素对应的索引及内存地址如下图所示。

假如我们想要访问索引3处的元素"E"时,计算机会进行以下计算:

  • 找到该数组的索引0的内存地址,即2008。
  • 将内存地址加上索引值,作为目标元素的地址,即2008 + 3 = 2011,对应的元素为"E",这时就找到了目标元素。

我们知道,计算内存地址这个过程是很快的,而我们一旦知道了内存地址就可以立即访问到该元素,因此读取数组元素的时间复杂度为常数级别,O(1)。

查找元素

与读取元素类似,由于计算机只保存了索引为0处的内存地址,因此在查找元素时,只需要从数组开头逐步向后查找就可以了。如果数组中的某个元素为目标元素,则停止查找,否则继续搜索直到达到数组的末尾。例如,查找数组["C", "O", "D", "E", "R"]中的元素"E",过程如下图所示。

我们发现,最坏的情况是搜索元素"R"或者数组中不包含目标元素时,我们需要查找n次,n为数组的长度,因此查找数组的时间复杂度为O(N)。

插入元素

假如我们想在数组["C", "O", "D", "E", "R"]中插入一个元素"S"。

如果将该元素插入到数组的末尾,只需要一步。即计算机通过数组的长度和位置计算出即将插入元素的内存地址,然后将该元素插入到指定位置即可。过程如下图所示。

但是,如果要将该元素插入到数组中的其他位置,则会有所区别,这时我们首先需要为该元素所要插入的位置腾出空间,然后进行插入操作。例如,我们想要在索引2处插入"S"。过程如下图所示。

我们发现,频繁地对数组进行插入操作,会造成时间的浪费。

删除元素

删除元素与插入元素的操作类似,当删除掉数组中的某个元素后,数组中会留下空缺的位置,而数组中的元素在内存中是连续的,这就使得后面的元素需对该位置进行填补操作。例如,删除数组["C", "O", "D", "E", "R"]中索引1中的元素"O",过程如下图所示。

当数组长度为n时,最坏情况下,我们删除第一个元素,共需要的步骤为1 + (n - 1) = n 步,其中,1为删除操作,n - 1为移动其余元素的步骤数。删除操作具有线性时间复杂度,即时间复杂度为O(N),N为数组长度。