当前位置: 首页 > news >正文

数据结构和算法(1):数组

目录

  • 概述
  • 动态数组
  • 二维数组
  • 局部性原理
  • 越界检查

概述

定义

在计算机科学中,数组是由一组元素(值或变量)组成的数据结构,每个元素有至少一个索引或键来标识

In computer science, an array is a data structure consisting of a collection of elements (values or variables), each identified by at least one array index or key

因为数组内的元素是连续存储的,所以数组中元素的地址,可以通过其索引计算出来,例如:

int[] array = {1,2,3,4,5}

知道了数组的数据起始地址 BaseAddressBaseAddressBaseAddress,就可以由公式 BaseAddress+i∗sizeBaseAddress + i * sizeBaseAddress+isize 计算出索引 iii 元素的地址

  • iii 即索引,在 Java、C 等语言都是从 0 开始
  • sizesizesize 是每个元素占用字节,例如 intintint444doubledoubledouble888

小测试

byte[] array = {1,2,3,4,5}

已知 array 的数据的起始地址是 0x7138f94c8,那么元素 3 的地址是什么?

答:0x7138f94c8 + 2 * 1 = 0x7138f94ca

空间占用

Java 中数组结构为

  • 8 字节 markword
  • 4 字节 class 指针(压缩 class 指针的情况)
  • 4 字节 数组大小(决定了数组最大容量是 2322^{32}232
  • 数组元素 + 对齐字节(java 中所有对象大小都是 8 字节的整数倍[^12],不足的要用对齐字节补足)

例如

int[] array = {1, 2, 3, 4, 5};

的大小为 40 个字节,组成如下

8 + 4 + 4 + 5*4 + 4(alignment)

随机访问性能

即根据索引查找元素,时间复杂度是 O(1)O(1)O(1)

逻辑大小和物理大小

数组的物理大小是它的数组单元的总数,或者说是在创建数组的时候,用来指定其容量的数字。

数组的逻辑大小,是它当前已供应用程序使用的项的数目。

当数组总是满的时候,不用担心他俩的区别,但是这种情况很少。

通常,逻辑大小的物理大小会告诉我们数组状态的几件重要的事:

  • 如果逻辑大小为0,数组为空,则说明该数组不包含数据项;
  • 如果数组包含的数据项,数组最后一项的索引为逻辑大小减1;
  • 如果逻辑大小等于物理大小,数组已经被数据填满。

动态数组

java 版本

import java.util.Arrays;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.stream.IntStream;/*** @author Ethan* @date 2023/3/20* @description*/
public class Ds01DynamicArray implements Iterable<Integer> {/***  逻辑大小*/private int size = 0;/***  容量*/private int capacity = 8;/*** 初始化数组为空*/private int[] array = {};/*** 向任意位置添加元素** @param index   索引位置* @param element 待添加元素*/public void add(int index, int element) {// 检查容量大小,不够要扩容checkAndGrow();// 如果插入的位置效益逻辑大小,那么要先把位置腾出来,索引位置以后得元素都要后移一位if (index >= 0 && index < size) {// 向后挪动, 空出待插入位置,使用数组的copy方法// 从哪书分别是源数组、源数组起始位置、目标数组、目标数组的起始位置、copy元素个数System.arraycopy(array, index,array, index + 1, size - index);}// 在指定位置插入元素array[index] = element;// 逻辑大小+1size++;}/*** 向最后位置 [size] 添加元素** @param element 待添加元素*/public void addLast(int element) {// 复用任意位置添加元素,插入位置是逻辑大小add(size, element);}/*** 容量检查,不够进行扩容*/private void checkAndGrow() {// 容量检查if (size == 0) {array = new int[capacity];} else if (size == capacity) {// 进行扩容, 1.5 1.618 2capacity += capacity >> 1;int[] newArray = new int[capacity];System.arraycopy(array, 0,newArray, 0, size);array = newArray;}}/*** 从 [0 .. size) 范围删除元素** @param index 索引位置* @return 被删除元素*/public int remove(int index) { // [0..size)// 要删除的元素int removed = array[index];// 如果要删除的元素索引小于逻辑大小-1,那么把目标索引的后面元素都向前移动一位if (index < size - 1) {// 向前挪动System.arraycopy(array, index + 1,array, index, size - index - 1);}// 逻辑大小-1size--;return removed;}/*** 查询元素** @param index 索引位置, 在 [0..size) 区间内* @return 该索引位置的元素*/public int get(int index) {return array[index];}/*** 遍历方法1** @param consumer 遍历要执行的操作, 入参: 每个元素*/public void foreach(Consumer<Integer> consumer) {// 使用Consumer把拿到的元素交给调用者来使用,具体使用方法取决于调用者for (int i = 0; i < size; i++) {// 提供 array[i]// 返回 voidconsumer.accept(array[i]);}}/*** 遍历方法2 - 迭代器遍历,这个类要实现Iterator接口*/@Overridepublic Iterator<Integer> iterator() {// 使用匿名内部类,直接返回一个迭代器,实现接口的两个方法return new Iterator<Integer>() {int i = 0;@Overridepublic boolean hasNext() { // 有没有下一个元素return i < size;}@Overridepublic Integer next() { // 返回当前元素,并移动到下一个元素return array[i++];}};}/*** 遍历方法3 - stream 遍历** @return stream 流*/public IntStream stream() {return IntStream.of(Arrays.copyOfRange(array, 0, size));}}
  • 这些方法实现,都简化了 index 的有效性判断,假设输入的 index 都是合法的

插入或删除性能

**头部位置:**因为要把头部后面的元素都移动一位,所以时间复杂度是 O(n)O(n)O(n)

**中间位置:**一样要移动指定索引位置后的元素,所以时间复杂度是 O(n)O(n)O(n)

**尾部位置:**可直接通过索引找到最后一个元素,且不需要移动元素,所以时间复杂度是 O(1)O(1)O(1)(均摊来说)

二维数组

所谓的二维数组就是数组中的数组,数组嵌套数组。如下:

int[][] array = {{11, 12, 13, 14, 15},{21, 22, 23, 24, 25},{31, 32, 33, 34, 35},
};

内存图如下

在这里插入图片描述

  • 最上面的二维数组占 32 个字节,其中 array[0],array[1],array[2] 三个元素分别保存了指向三个一维数组的引用

  • 三个一维数组各占 40 个字节

  • 它们在内层布局上是连续

更一般的,对一个二维数组 Array[m][n]Array[m][n]Array[m][n]

  • mmm 是外层数组的长度,可以看作 row 行
  • nnn 是内层数组的长度,可以看作 column 列
  • 当访问 Array[i][j]Array[i][j]Array[i][j]0≤i<m,0≤j<n0\leq i \lt m, 0\leq j \lt n0i<m,0j<n时,就相当于
    • 先找到第 iii 个内层数组(行)
    • 再找到此内层数组中第 jjj 个元素(列)

小测试

Java 环境下(不考虑类指针和引用压缩,此为默认情况),有下面的二维数组

byte[][] array = {{11, 12, 13, 14, 15},{21, 22, 23, 24, 25},{31, 32, 33, 34, 35},
};

已知 array 对象起始地址是 0x1000,那么 23 这个元素的地址是什么?

答:

  • 起始地址 0x1000
  • 外层数组大小:16字节对象头 + 3元素 * 每个引用4字节 + 4 对齐字节 = 32 = 0x20
  • 第一个内层数组大小:16字节对象头 + 5元素 * 每个byte1字节 + 3 对齐字节 = 24 = 0x18
  • 第二个内层数组,16字节对象头 = 0x10,待查找元素索引为 2
  • 最后结果 = 0x1000 + 0x20 + 0x18 + 0x10 + 2*1 = 0x104a

局部性原理

这里只讨论空间局部性

  • cpu 读取内存(速度慢)数据后,会将其放入高速缓存(速度快)当中,如果后来的计算再用到此数据,在缓存中能读到的话,就不必读内存了
  • 缓存的最小存储单位是缓存行(cache line),一般是 64 bytes,一次读的数据少了不划算啊,因此最少读 64 bytes 填满一个缓存行,因此读入某个数据时也会读取其临近的数据,这就是所谓空间局部性

对效率的影响

比较下面 ij 和 ji 两个方法的执行效率

int rows = 1000000;
int columns = 14;
int[][] a = new int[rows][columns];StopWatch sw = new StopWatch();
sw.start("ij");
ij(a, rows, columns);
sw.stop();
sw.start("ji");
ji(a, rows, columns);
sw.stop();
System.out.println(sw.prettyPrint());

ij 方法

public static void ij(int[][] a, int rows, int columns) {long sum = 0L;for (int i = 0; i < rows; i++) {for (int j = 0; j < columns; j++) {sum += a[i][j];}}System.out.println(sum);
}

ji 方法

public static void ji(int[][] a, int rows, int columns) {long sum = 0L;for (int j = 0; j < columns; j++) {for (int i = 0; i < rows; i++) {sum += a[i][j];}}System.out.println(sum);
}

执行结果

0
0
StopWatch '': running time = 96283300 ns
---------------------------------------------
ns         %     Task name
---------------------------------------------
016196200  017%  ij
080087100  083%  ji

可以看到 ij 的效率比 ji 快很多,为什么呢?

  • 缓存是有限的,当新数据来了后,一些旧的缓存行数据就会被覆盖
  • 如果不能充分利用缓存的数据,就会造成效率低下

以 ji 执行为例,第一次内循环要读入 [0,0][0,0][0,0] 这条数据,由于局部性原理,读入 [0,0][0,0][0,0] 的同时也读入了 [0,1]...[0,13][0,1] ... [0,13][0,1]...[0,13],如图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ap5uUytD-1679319472567)(.\imgs\image-20221104164329026.png)]

但很遗憾,第二次内循环要的是 [1,0][1,0][1,0] 这条数据,缓存中没有,于是再读入了下图的数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zuvKO099-1679319472568)(.\imgs\image-20221104164716282.png)]

这显然是一种浪费,因为 [0,1]...[0,13][0,1] ... [0,13][0,1]...[0,13] 包括 [1,1]...[1,13][1,1] ... [1,13][1,1]...[1,13] 这些数据虽然读入了缓存,却没有及时用上,而缓存的大小是有限的,等执行到第九次内循环时

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q4dHdf2Y-1679319472568)(.\imgs\image-20221104164947154.png)]

缓存的第一行数据已经被新的数据 [8,0]...[8,13][8,0] ... [8,13][8,0]...[8,13] 覆盖掉了,以后如果再想读,比如 [0,1][0,1][0,1],又得到内存去读了

同理可以分析 ij 函数则能充分利用局部性原理加载到的缓存数据

举一反三

  1. I/O 读写时同样可以体现局部性原理

  2. 数组可以充分利用局部性原理,那么链表呢?

    答:链表不行,因为链表的元素并非相邻存储

越界检查

java 中对数组元素的读写都有越界检查,类似于下面的代码

bool is_within_bounds(int index) const        
{ return 0 <= index && index < length(); 
}
  • 代码位置:openjdk\src\hotspot\share\oops\arrayOop.hpp

只不过此检查代码,不需要由程序员自己来调用,JVM 会帮我们调用

相关文章:

数据结构和算法(1):数组

目录概述动态数组二维数组局部性原理越界检查概述 定义 在计算机科学中&#xff0c;数组是由一组元素&#xff08;值或变量&#xff09;组成的数据结构&#xff0c;每个元素有至少一个索引或键来标识 In computer science, an array is a data structure consisting of a col…...

python+django+vue全家桶鲜花商城售卖系统

重点&#xff1a; &#xff08;1&#xff09; 网上花店网站中各模块功能之间的的串联。 &#xff08;2&#xff09; 网上花店网站前台与后台的连接与同步。 &#xff08;3&#xff09; 鲜花信息管理模块中鲜花的发布、更新与删除。 &#xff08;4&#xff09; 订单…...

一文带你领略 WPA3-SAE 的 “安全感”

引入 WPA3-SAE也是针对四次握手的协议。 四次握手是 AP &#xff08;authenticator&#xff09; 和 &#xff08;supplicant&#xff09;进行四次信息交互&#xff0c;生成一个用于加密无线数据的秘钥。 这个过程发生在 WIFI 连接 的 过程。 为了更好的阐述 WPA3-SAE 的作用 …...

Python解题 - CSDN周赛第38期

又来拯救公主了。。。本期四道题还是都考过&#xff0c;而且后面两道问哥在以前写的题解里给出了详细的代码&#xff08;当然是python版&#xff09;&#xff0c;直接复制粘贴就可以过了——尽管这样显得有失公允&#xff0c;考虑到以后还会出现重复的考题&#xff0c;所以现在…...

Android绘制——自定义view之onLayout

简介 在自定义view的时候&#xff0c;其实很简单&#xff0c;只需要知道3步骤&#xff1a; 测量——onMeasure()&#xff1a;决定View的大小&#xff0c;关于此请阅读《Android自定义控件之onMeasure》布局——onLayout()&#xff1a;决定View在ViewGroup中的位置绘制——onD…...

用Qt画一个温度计

示例1 以下是用Qt绘制一个简单的温度计的示例代码&#xff1a; #include <QPainter> #include <QWidget> #include <QApplication> class Thermometer : public QWidget { public:Thermometer(QWidget *parent 0); protected:void paintEvent(QPaintEvent …...

Java设计模式 04-建造者模式

建造者模式 一、 盖房项目需求 1)需要建房子&#xff1a;这一过程为打桩、砌墙、封顶 2)房子有各种各样的&#xff0c;比如普通房&#xff0c;高楼&#xff0c;别墅&#xff0c;各种房子的过程虽然一样&#xff0c;但是要求不要相同的. 3)请编写程序&#xff0c;完成需求. …...

安语未公告于2023年3月20日发布

因一些特殊原因&#xff0c;凡事都是有开始&#xff0c;高潮和结束三大过程&#xff0c;做出以下决定&#xff1a; 所有对 安语未文章 为之热爱、鞭策、奉献&#xff0c;和支持过的开发者&#xff1a; 注&#xff1a;所有资源以及资料都会正常下载和查看 如需联系&#xff1…...

进销存是什么?如何选择进销存系统?

什么是进销存&#xff1f;进销存软件概念起源于上世纪80年代&#xff0c;由于电算化的普及&#xff0c;计算机管理的推广&#xff0c;不少企业对于仓库货品的进货&#xff0c;存货&#xff0c;出货管理&#xff0c;有了强烈的需求&#xff0c;进销存软件的发展从此开始。 进入…...

基于BP神经网络的图像跟踪,基于BP神经网络的细胞追踪识别

目录 摘要 BP神经网络的原理 BP神经网络的定义 BP神经网络的基本结构 BP神经网络的神经元 BP神经网络激活函数及公式 基于BP神经网络的细胞识别追踪 matab编程代码 效果 结果分析 展望 摘要 智能驾驶,智能出行是现代社会发展的趋势之一,其中,客量预测对智能出行至关重要,…...

Java面试总结篇

引用介绍 1.线程安全不安全的概念 ​ 线程安全: 指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。 ​ 线程不安全: 是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏…...

100天精通Python(可视化篇)——第80天:matplotlib绘制不同种类炫酷柱状图代码实战(簇状、堆积、横向、百分比、3D柱状图)

文章目录0. 专栏导读1. 普通柱状图2. 簇状柱形图3. 堆积柱形图4. 横向柱状图5. 横向双向柱状图6. 百分比堆积柱形图7. 3D柱形图8. 3D堆积柱形图9. 3D百分比堆积柱形图0. 专栏导读 &#x1f3c6;&#x1f3c6;作者介绍&#xff1a;Python领域优质创作者、CSDN/华为云/阿里云/掘金…...

【Java】UDP网络编程

文章目录前言DatagramSocketDatagramPacket注意事项与区别代码演示前言 UDP&#xff08;user datagram protocol&#xff09;的中文叫用户数据报协议&#xff0c;属于传输层。 UDP是面向非连接的协议&#xff0c;它不与对方建立连接&#xff0c;而是直接把我要发的数据报发给对…...

Springboot源代码总结

前言 编写微服务,巩固知识 文章目录 前言springboot原理springboot启动流程SpringBoot自动配置底层源码解析自动配置到底配了什么?自动配置类条件注解Starter机制@ConditionalOnMissingBeanSpringBoot启动过程源码解析构造SpringApplication对象SpringBoot完整的配置优先级s…...

JVM监控搭建

文章目录JVM监控搭建整体架构JolokiaTelegrafInfluxdbGrafanaJVM监控搭建 整体架构 JVM 的各种内存信息&#xff0c;会通过 JMX 接口进行暴露。 Jolokia 组件负责把 JMX 信息翻译成容易读取的 HTTP 请求。Telegraf 组件作为一个通用的监控 agent&#xff0c;和 JVM 进程部署在…...

java中如何优化大量的if...else...

目录 策略模式&#xff08;Strategy Pattern&#xff09; 工厂模式&#xff08;Factory Pattern&#xff09; 映射表&#xff08;Map&#xff09; 数据驱动设计&#xff08;Data-Driven Design&#xff09; 策略模式&#xff08;Strategy Pattern&#xff09; 将每个条件分…...

24. linux系统基础

两个进程间想通讯&#xff0c;必须要通过内核&#xff0c;今天讲的信号其实本质也是讲进程间通讯的意思&#xff0c;那么你为什么可以在shell环境下&#xff0c;可以和一个进程发kill-9啊&#xff1f; shell是不是相当于一个进程&#xff1f;你自己运行的那个进程是不是也相当于…...

【C++】面试101,二叉搜索树的最近公共祖先,在二叉树中找到两个节点的最近公共祖先,序列化二叉树,重建二叉树,输出二叉树的右视图,组队竞赛,删除公共字符

目录 1.二叉搜索树的最近公共祖先 2.在二叉树中找到两个节点的最近公共祖先 3.序列化二叉树 4.重建二叉树 5.输出二叉树的右视图 6.组队竞赛 7.删除公共字符 1.二叉搜索树的最近公共祖先 这是一个简单的问题&#xff0c;因为是二叉搜索树&#xff08;有序&#xff09;&am…...

Java常见面试题及解答

Java常见面试题及解答1 面向对象的三个特征2 this&#xff0c;super关键字3 基础数据类型4 public、protected、default、private5 接口6 抽象类6.1 抽象类和接口的区别7 重载&#xff08;overload&#xff09;、重写&#xff08;override&#xff09;8 final、finalize、final…...

【Docker】镜像的原理定制化镜像

文章目录镜像是什么UnionFS&#xff08;联合文件系统&#xff09;Docker镜像加载原理制作本地镜像 docker commit -m"提交的描述信息" -a"作者" 容器ID 要创建的目标镜像名:[标签名]案例演示ubuntu安装vim本地镜像发布到阿里云本地镜像发布到阿里云流程将本…...

国内版的ChatGPT弯道超车的机会在哪里?

前言 从去年11月最后一天ChatGPT诞生&#xff0c;截至目前&#xff0c;ChatGPT的热度可谓是爆了。众所周知&#xff0c;ChatGPT是美国“开放人工智能研究中心”研发的聊天机器人程序&#xff0c;它是一个人工智能技术驱动的自然语言处理工具&#xff0c;它能够通过学习和理解人…...

【字符串】

string1.char str[]类型fgets(s,10000,stdin) cin.getline(cin,10000) strlen(str)sizeof 求静态数组长度2.string类型getline(cin,a) cin.getline(cin,10000) str.lenth()str.size()cin 遇到空格就停止3.gets 函数char str[20];gets(str);4.puts 函数puts(str) 相当于 cout<…...

加载驱动之后无法在/dev/下生成vedio0

前言 环境介绍&#xff1a; 1.编译环境 Ubuntu 18.04.5 LTS 2.SDK orangepi Linux 5.4 SDK 3.uboot v2020.04 4.gcc gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf 5.单板 orangepi pc plus 一、问题 继上一篇成功加载gc2035.ko文件之后&#xff0c;理论上…...

Java之类与对象(图文结合)

目录 一、面向对象的初步认知 1、什么是面向对象 2、面向对象与面向过程 二、类定义和使用 1、简单认识类 2、类的定义格式 3、练习 &#xff08;1&#xff09;定义一个狗类 &#xff08;2&#xff09;定义一个学生类 三、类的实例化 1、什么是实例化 2、类和对象的…...

基于 VCS-NLP 的动态低功耗仿真验证介绍

&#x1f525;点击查看精选 IC 技能树系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#…...

ESP32-S3 自带usb/jtag初步尝试体验

一、背景 最近在做一台小机器&#xff0c;设备初步规划使用几个实体按钮&#xff0c;这样方便用户戴手套操作。但因为设备有一些需要配置的参数&#xff0c;有需要配备屏幕。但是开发时间比较紧。考虑再三&#xff0c;决定先在初步配备一个简单的控制箱。控制箱上不带屏幕。后…...

前端性能优化总结

前端性能优化是指在设计和开发网站时&#xff0c;采取一些措施来提升网站的性能。这对用户来说是非常重要的&#xff0c;因为高性能的网站可以带来更好的用户体验&#xff0c;同时也有助于提升搜索引擎排名。一、常见前端性能优化措施常见的前端性能优化方法有&#xff1a;压缩…...

React(四) ——hooks的使用

&#x1f9c1;个人主页&#xff1a;个人主页 ✌支持我 &#xff1a;点赞&#x1f44d;收藏&#x1f33c;关注&#x1f9e1; 文章目录⛳React Hooks&#x1f4b8;useState(保存组件状态)&#x1f948;useEffect(处理副作用)&#x1f50b;useCallback&#xff08;记忆函数&#…...

iphone手机热点卡顿多次断连解决办法

文章目录解决方法检查一下几个地方&#xff1a;1.个人热点是否打开2.查看手机是否为4g3.查看手机的最大兼容性开关是否关闭&#xff01;&#xff01;很重要解决方法 检查一下几个地方&#xff1a; 1.个人热点是否打开 这个个人热点容易自动断开&#xff0c;先检查一下是不是…...

设置Typora图床(Github)

PicGo&#xff0c;Github&#xff0c;Typora Nodejs下载&#xff1a; Node.js PicGo下载&#xff1a; GitHub - Molunerfinn/PicGo: A simple & beautiful tool for pictures uploading built by vue-cli-electron-builder 选择downloads或release. 然后进行安装。 Gith…...