四步轻松掌握数组基本操作

1、计算机内存管理

我们每天都在面对着如电脑、手机等电子产品,每一台电子设备都有自己的内存大小,如:64G、256G、512G甚至更多

内存到底有什么作用呢?

简单来说,计算机的程序都是运行在内存中的

那么内存是如何存储的呢?

我们可以简单的将内存当作寄存柜

假设一家人要去逛超市,身上共带有四个小包,需要将身上的东西放在四个储物柜中。每个储物柜都有自己对应的编号,方便我们去查找和记录。逛完超市后,再从这四个储物柜中将东西取走

这就是计算机内存的工作原理

每一个储物柜都可以当作一个存储单元,而储物柜编号可以当作每个存储单元的内存地址

扩展知识:为什么连续两个方格内存地址差 8?

因为现在电脑基本是 64 位,64 位表示内存存储最小使用单位是 64 个 bit,也就是 8 个 byte

2、数组的存储和读取

提到数组,大家一定不陌生,也肯定会有很多人会说数组很简单。确实,数组是计算机中最基础的数据结构,每个编程语言中基本都有数组。尽管数组看起来很简单易懂,但是大家有没有想过一个问题:为什么数组的索引会从 0 开始呢

数组存储

要回答上面这个问题,首先要搞清楚数组到底是怎么在内存中存储的,我们从数组的定义开始

数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同数据类型的数据

线性数据结构:表示数组中的数据都是按照前后顺序这种线性顺序排列的

相同数据类型:数组中的每个值的数据类型都相同

数组读取

每个数组都有对应的内存地址,我们称其为数组的开始地址:start_address

数组在定义的时候就已经确定好该数组的数据类型了,我们也就清楚了每个元素需要的内存空间大小,称其为:item_size

因此数组中每个元素的内存地址都可以被计算出来:

// 第一个元素地址
start_address

// 第二个元素地址
start_address + item_size * 1

// 第三个元素地址
start_address + item_size * 2
    
// 第N个元素地址
start_address + item_size * (N - 1)

综上可知:

  1. 时间复杂度

    数组的索引访问的时间复杂度是O(1)

  2. 为什么数组的索引是从0开始

    内存地址的计算规则设置开始地址为 start_address + item_size * 0,在计算机中为了方便位置的计算,所以数组索引从 0 开始

3、数组的插入和删除

数组插入

往数组里面插入一个元素的速度,取决于你需要把它插入到哪个位置上

假设我们在为周末的事情做计划,现在暂定计划如下:

eat breakfast  // 吃早饭
reading        // 看书
have lunch     // 吃午饭
have dinner    // 吃晚饭

把周末安排设置为一个类,我们预留20个计划,那么代码如下:

public class Plan {
    
    String[] array = new String[20];
    private int size = 0;
    
    // 内置4个购物计划
    public Plan() {
        array[0] = "eat breakfast";
        array[1] = "reading";
        array[2] = "have lunch";
        array[3] = "have dinner";
        this.size = 4;
    }
    
    public void print() {
        for (int i = 0; i < this.size; i++){
      		System.out.print(this.array[i] + " ");
    	}
    }
    
    public static void main(String[] args) {
        Plan p = new Plan();
        p.print();
    }
    
}

代码内置了4个周末的计划,并且设置计划数量 size 为 4

代码中提供了 print 方法,用于打印计划

尾部插入

现在我们突然想在吃完饭之后去购物(shopping),该怎么处理呢

每个数组我们都知道开头的内存地址,也知道数组包含多少个元素以及每个元素占用的内存大小是多少。所以在尾部插入元素很容易,尾部的内存地址为:

// n 为当前数组中元素的个数
start_address + item_size * n  

因此只需一步就可以插入成功,只需要在原来的代码中加入 add 方法:

public void add(String target) {
    this.array[this.size] = target;
    this.size++;
}

接下来就可以根据当前计划的数量 size,在后面继续添加新的计划

中间插入

如果希望在下午来一顿下午茶(afternoon tea),应该怎么处理呢?

我们知道,需要在数组中间部分插入数据,但是为了保证数组的顺序和连续的内存空间,插入地方后面部分数据,需要往后依次移动,步骤如下:

  1. 移动 shopping
  2. 移动 have dinner
  3. 插入 afternoon tea

同理,如果需要插入在开始的地方,那么就需要 N

因此平均步数为 (1 + 2+ 3 + ... + N)/N 最终时间复杂度为 O(N),也就是数组的插入时间复杂度为线性时间复杂度

复杂度相关的内容大家可以参考 数据结构与算法之复杂度三步走 一文

我们在代码块中新增一个 insert 函数,在索引位置为 3 的地方插入一项任务

public void insert(String target) {
    // 索引值为3的地方
  	int index = 3;
  	// 第一步:从右侧开始依次右移
    for (int i = this.size - 1; i >= index; i--) {
        this.array[i+1] = this.array[i];
    }
    // 第二步:插入元素
    this.array[index] = target;
    // 调整size
    this.size++;
}

空间不够

如果空间不够了的话,系统会自动抛出异常 — Out Of Memory 内存不够啦

因此我们可以:

  1. 开辟新的内存空间
  2. 复制原来的数组到新的内存空间
  3. 再进行插入操作

数组删除

数组的删除操作和插入操作刚好相反

  • 删除尾部元素:

    直接删除

  • 删除中间元素:

    1. 删除中间元素
    2. 该元素的右侧元素依次左移

因此我们可知:数组删除的时间复杂度也为 O(N)

4、总结

通过上面的分析,我们可以得出数组的优势与劣势:

  • 优势

    数组的查询数据特别快,时间复杂度是 O(1)

  • 劣势

    数组的插入和删除比较慢,时间复杂度是 O(N)

    需要连续的内存空间,并且会申请额外的内存空间便于其扩展,这种数据结构对内存要求比较高,利用率低

由于这些优势与劣势,我们在高频查询,低频插入和删除的情况下,可以选择数组作为底层数据结构

Java中的 ArrayList,实际上在底层就是利用数组实现的

最后,小橘子希望大家如果觉得本文有用的话来个 点赞 + 关注,或者分享给身边的朋友们,也希望各位大家能够给小橘子一些建议,加油!

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值