前言
数据结构始终是计算机科学绕不开的话题,是计算机中存储、组织数据的方式。学习数据结构能让我们明白,如何更高效的存、取数据。编写程序的目的就是为了处理数据,处理数据本质上就是存、取、运算。
本篇从最简单的数据结构入手,讲解数组和链表。主要讲解他们的特点、存储结构、区别、各种场景下的效率等问题。
数组
在计算机科学中,数组数据结构(英语:array data structure),简称数组(英语:Array),是由相同类型的元素(element)的集合所组成的数据结构,分配一块连续的内存来存储。利用元素的索引(index)可以计算出该元素对应的存储地址。
数组可以说是最常见的数据结构之一了,主要特点是
- 能存储一系列相同类型的元素
- 所有元素用一块连续的内存来存储
- 利用元素的索引可以直接访问对应的数据
- 数组是静态结构,初始化必须指定容量,且容量不变
Object[] arr = new Object[10];
是最常见定义数组的方式,如果要访问索引为1的数据,直接通过arr[1]
即可访问。也就是说数组支持随机访问(RandomAccess)。第一次听到“随机访问”这个词的时候非常懵逼:既然是随机,那就表示不确定,也就是说没人知道程序会访问哪个数据,那怎么保证访问的就是程序需要的数据呢?后来才知道:这个随机访问(RandomAccess)倒不如翻译成“任意访问”,就是想访问哪个数据,就可以直接访问。数组就是这样,只需要给定下标就可以直接访问。
说数组是静态结构,容量不能变。可能有同学不理解了,ArrayList
底层就是数组,但是ArrayList
是可以自动扩容的。既然这样,那我就用数组手动实现简易版的ArrayList
吧,简易的实现了ArrayList
的核心功能,先来看下定义
/**
* 动态数组实现ArrayList
*/
public class ArrayList<E> {
/**
* 默认容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 扩容倍数
*/
private static final int RESIZE_RATE = 2;
private E[] table;
private int size;
}
E[] table
用来保存添加的元素,int size
用来记录ArrayList
中元素的个数。需要注意的是,size
和容量并不一定相等,容量是table.length
,也就是数组长度。
再来看两个核心方法add(...)
和remove()
的实现思想,首先看下add(...)
方法,往指定的位置插入元素,插入过程如下
动画演示的是:把元素6插入下标为3的位置。在插入之前,必须先把下标3及以后的元素向后移动一格。并且必须按照下标递减的顺序来先后移动元素,避免元素被覆盖。
从图中便可以看出,向数组中间插入元素,时间复杂度是O(n),因为需要移动元素。但是向数组尾部插入元素,便不需要移动元素,所以时间复杂度为O(1)。由于向数组尾部插入元素有可能导致扩容操作(下面详细介绍),而扩容操作的时间复杂度为O(n),所以向数组尾部插入元素的最坏时间复杂度为O(n)。下面的代码是该逻辑的实现。
/**
* 指定位置新增
* @param index 下标
* @param e 新增元素
* @return 是否成功
*/
public boolean add(int index, E e) {
if (index < 0 || index > size) {