数组
数组
数组的定义和特点
数组是一种线性结构,它将所有类型相同的元素存储在一块连续的内存空间中,可以通过索引去访问这块内存空间中的任意元素。
索引的本质是内存地址的偏移量,访问数组中的某个元素,就是通过数组首元素的内存地址 + 数组的元素大小 * 索引,得到索引的内存地址(也就是内存地址的偏移量),来访问数组中对应索引位置上的元素。
数组的常用操作
1. 初始化数组
数组可以通过两种方式初始化,无初始值或给定初始值。大多数编程语言中,无初始值时,会将数组元素初始化为0。
/**
* 初始化数组
*/
public void init() {
// 无初始值
int[] array = new int[10];
// 给定初始值
int[] nums = {1, 2, 3, 4, 5};
}
2.访问指定索引的元素
数组支持随机访问,可以在O(1)时间内访问任意元素,这是因为数组存储在连续的内存空间中,通过计算数组中要访问的索引所在的内存地址即可直接访问。
/**
* 随机访问元素
*
* @param nums 数组
* @return 访问的元素
*/
public int randomAccess(int[] nums) {
int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length);
int num = nums[randomIndex];
return num;
}
3. 遍历元素
遍历数组,即访问数组中的每个元素。
/**
* 遍历数组
*/
public void traverse(int[] nums) {
// 通过索引遍历数组
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
// 直接遍历数组
for (int num : nums) {
System.out.println(num);
}
}
4. 查找指定元素
遍历数组查找某个元素,这是一种线性查找,时间复杂度为O(n)
/**
* 查找数组中的元素,并返回索引
*
* @param nums 数组
* @param target 要查找的元素
* @return 元素的索引
*/
public int find(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
if (target == nums[i]) {
return i;
}
}
return -1;
}
5. 插入元素
向数组中某个位置插入元素,需要将该位置以及该位置后的所有元素都向后移动一位,时间复杂度为O(n)。由于数组长度固定,插入可能会导致末尾元素丢失。
插入操作存在的缺点:
- 时间复杂度高:数组的插入和删除的平均时间复杂度均为 O(n) ,其中n为数组长度。
- 丢失元素:由于数组的长度不可变,因此在插入元素后,超出数组长度范围的元素会丢失。
- 内存浪费:我们可以初始化一个比较长的数组,只用前面一部分,这样在插入数据时,丢失的末尾元素都是“无意义”的,但这样做会造成部分内存空间浪费。
/**
* 向数组中插入元素
*
* @param nums 数组
* @param index 插入的位置
* @param value 插入的值
*/
public void insert(int[] nums, int index, int value) {
// 把索引index以及之后的元素都往后移动一位
for (int i = nums.length - 1; i < index; i--) {
nums[i] = nums[i - 1];
}
// 将value赋值给index处的元素
nums[index] = value;
}
6. 删除元素
删除数组中某个元素,需要将该位置后的所有元素都向前移动一位,时间复杂度为O(n)。
删除操作存在的缺点:
时间复杂度高: 删除操作的平均时间复杂度为O(n),其中n为数组长度。
/**
* 删除数组index位置上的元素
*
* @param nums 数组
* @param index 要删除的元素的位置
*/
public void delete(int[] nums, int index) {
// 把index位置后的所有元素都往前移动一位
for (int i = index; i < nums.length; i++) {
nums[i] = nums[i - 1];
}
}
7. 扩容
由于大多数编程语言数组长度不可变,扩容需要创建一个更大的数组,并将原数组复制过去,时间复杂度为O(n)。
/**
* 扩容
*
* @param nums 要扩容的数组
* @param newSize 扩容后数组的长度
* @return 扩容后的数组
*/
public int[] expand(int[] nums, int newSize) {
int[] newNums = new int[newSize];
for (int i = 0; i < nums.length; i++) {
newNums[i] = nums[i];
}
return newNums;
}
数组的优点与局限性
数组的优点:
- 空间效率高:数组存储在连续的内存空间中,不需要像链表那样需要额外的空间来存储元素之间的关系。
- 随机访问:数组支持随机访问,可以在O(1)时间内访问任意元素。
- 内存连续:数组存储在连续的内存空间中,访问速度快。
数组的局限性:
- 插入和删除操作效率低:当数组中元素较多时,插入和删除操作需要移动大量元素,时间复杂度为O(n)。
- 长度固定:数组初始化后长度固定,扩容需要创建一个更大的数组,并将原数组复制过去,开销很大,时间复杂度为O(n)。
- 空间浪费:如果数组分配的大小超过实际所需,那么多余的空间就被浪费了。
数组的应用场景
- 随机访问:根据数组随机访问的特性,当我们需要随机抽取一些样本时,可用使用数组存储样本。
- 排序和查找:数组是排序和搜索算法最常用的数据结构。快速排序、归并排序、二分查找等都主要在数组上进行。
- 数据结构实现:数组可以用于实现栈、队列、哈希表、堆、图等数据结构。
- 其他:机器学习、图像处理、音频处理等。

浙公网安备 33010602011771号