在计算机科学里,数组数据结构,简称数组(Array),是由相同类型的元素(element)的集合所组成的数据结构,分配一块连续的内存来存储。利用元素的索引可以计算该元素对应的存储地址。
数组如何实现随机访问?
因为数组是由相同类型的元素连续存储的,正是因为这个特性才使得数组能够随机访问。
当计算机为数组分配了一块连续的内存区域后,假设这个内存的区域的起始地址(基地址)称为base_address。我们知道我们只要通过数组下标index指明是那个元素就可以对数组进行访问。其实,当计算机需要访问数组中的某个元素时,它会通过下面的公式寻址,计算出该元素的内存地址:
a[index]_address = base_address + index * data_type_size
index就是我们在访问数组时使用的下标,data_type_size是该数组元素的数据类型所占的字节大小,如int 为4字节。
注意,数组查找的时间复杂度并不是O(1),而是通过下标访问的时间复杂度为O(1)。
低效的“插入”和“删除”
数组为了保存内存数据的连续性,会导致插入、删除操作比较低效。
1. 插入操作
如果我们的数组长度为n,现在如果我们需要将一个数据插入到数组中的第k个位置。为了把第k个位置空出来,我们需要将k~n这部分的元素依次往后挪一位。
显然在第1个位置,需要移动n个元素;第2个位置,需要移动n-1个元素;......;最后一个元素不需要移动元素。
平均需要移动(0+1+2+3+...+n)/ (n+1) ~= O(n)。最好的时间复杂度是O(1),即每次都在最后插入。 最坏的情况是O(n),即在第一个位置,那所有的元素都要移动。
2. 删除操作
删除和插入是类似的,当我们删除第一个元素时,所有元素都要往前挪一位,时间复杂度为O(n)。当我们删除最后一个元素时,不需要挪动任何数据,时间复杂度为O(1)。
在某些特定的场景下,我们并不一定非得追求数据中数据的连续性。我们可以将多次删除操作集中在一起执行。记录已经被删除的元素,直到数组所有元素都被删除,再进行依一次真正的删除,避免数据的多次挪动,提高效率。
防止数组越界
在C语言中,只要不是访问受限的内存,所有的内存空间都是可以自由访问的。若你的这些内存地址下有重要数据,很有可能会被覆盖或更改。因此,当我们用数组下标访问时,要注意避免不要超出数组大小。否则就会越界。
Leetcode刷题
给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
说明:
初始化:nums1 和 nums2 的元素数量分别为 m 和 n。 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。 示例:
输入: nums1 = [1,2,3,0,0,0], m = 3 nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解法一: 先合并后排序
先合并两个数组然后在排序。
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
//先合并
for(int i = m, j = 0; i < (m+n); i++){
nums1[i] = nums2[j++];
}
//后排序
sort(nums1.begin(), nums1.end());
}
};
//时间复杂度合并需要O((n+m)*log(n+m))
//空间复杂度为O(1)
解法二:双支针法/从前往后
由于输出的数组为nums1,需要将nums1的前m个元素放到其他地方保存起来,否则会被覆盖。
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
if(m == 0){
for(int i = 0; i < n; i++){
nums1[i] = nums2[i];
}
return;
}
int nums1_copy[m];
for(int i = 0; i < m; i++){
nums1_copy[i] = nums1[i];
}
int p1 = 0, p2 = 0, p = 0;
while(p1 < m && p2 < n){
if(nums1_copy[p1] > nums2[p2]){
nums1[p++] = nums2[p2++];
}else{
nums1[p++] = nums1_copy[p1++];
}
}
while(p1 < m){
nums1[p++] = nums1_copy[p1++];
}
while(p2 < n){
nums1[p++] = nums2[p2++];
}
}
};
//时间复杂度为O(m+n)
//空间复杂度为O(m)
解法三:双支针法/从后往前
从后往前,不会覆盖原有的数据,可以节约空间。
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
//定义双指针,从后往前
int p1 = m - 1, p2 = n - 1, index = (m + n) - 1;
while((p1 >= 0) && (p2 >= 0)){
if(nums1[p1] < nums2[p2]){
nums1[index--] = nums2[p2--];
}else{
nums1[index--] = nums1[p1--];
}
}
while(p2 >= 0){
nums1[index--] = nums2[p2--];
}
}
};
//时间复杂度O(m+n)
//空间复杂度O(1)