【基础篇】3 # 数组:为什么很多编程语言中数组都从0开始编号?

news2024/11/24 0:05:09

说明

【数据结构与算法之美】专栏学习笔记

什么是数组?

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

线性表和非线性表

  • 线性表(Linear List):就是数据排成像一条线一样的结构,每个线性表上的数据最多只有前和后两个方向,比如:数组,链表、队列、栈都是线性表结构。
  • 非线性表:数据之间并不是简单的前后关系,比如二叉树、堆、图等。

数组是如何实现根据下标随机访问数组元素的?

随机访问的意思,就是可以随机选择下标,进行数据访问,根据下标随机访问的时间复杂度为 O(1)。

如下图:计算机给数组 int a[10],分配了一块连续内存空间 1000~1039。

在这里插入图片描述

当计算机需要随机访问数组中的某个元素时,会首先通过计算机寻址公式计算出该元素存储的内存地址:

a[i]_address = base_address + i * data_type_size
  • data_type_size:表示数组中每个元素的大小,a[10] 是 int 类型,该值为 4 个字节
  • base_address:内存块的首地址,a[10] 这里就是 1000

插入和删除操作为什么低效?

插入操作

假设数组的长度为 n,如果将一个数据插入到数组中的第 k 个位置,那么需要将第 k~n 这部分的元素都顺序地往后挪一位,把第 k 个位置腾出来给新来的数据。

插入操作的时间复杂度分析:

  • 最坏时间复杂度为:开头插入元素,所有的数据都需要依次往后移动一位,时间复杂度为 O(n)
  • 最好时间复杂度:末尾插入元素,无需移动,时间复杂度为 O(1)
  • 平均时间复杂度:插入到每一个位置的概率都是 1/n,插入到数组的第一个位置需要移动 n 个元素;插入到数组的第二个位置需要移动 n -1 个元素,以此类推,插入到数组中的最后一个位置,需要移动1个元素.,(n + n - 1 + n - 2 + ... + 1)/n = (n+1)/2,时间复杂度为 O(n)

如果数组中存储的数据并没有任何规律,数组只是被当作一个存储数据的集合,直接将第 k 位的数据搬移到数组元素的最后,把新的元素直接放入第 k 个位置,这样时间复杂度就会降为 O(1)。

示意图如下:

在这里插入图片描述

删除操作

如果要删除第 k 个位置的数据,为了内存的连续性,需要移动数据。

删除操作的时间复杂度分析:

  • 最坏时间复杂度为:删除开头的数据,所有的数据都需要依次往前移动一位,时间复杂度为 O(n)
  • 最好时间复杂度:删除数组末尾的数据,无需移动,时间复杂度为 O(1)
  • 平均时间复杂度:和插入类似,时间复杂度为 O(n)

如果不一定非得追求数组中数据的连续性,可以将多次删除操作集中在一起执行,提高删除的效率。比如:标记清除垃圾回收算法里就有用到。

数组访问越界问题

看个例子:

int main(int argc, char* argv[]){
    int i = 0;
    int arr[3] = {0};
    for(; i <= 3; i++){
        arr[i] = 0;
        printf("hello world\n");
    }
    return 0;
}

如果 i < 3,那么结果输出如下:

在这里插入图片描述

上面这段代码是 i <= 3,当 i = 3 时,数组 a[3] 访问越界,而 a[3] 内存地址指向的是 i 对应内存地址的位置,所以修改 a[3] 的值,也就是修改 i 的值,这时 i 也等于 0,就出现了死循环。

在这里插入图片描述

因为 C 语言里并没有规定数组访问越界时编译器应该如何处理,而 Java 会做越界检查,超出就会抛出:java.lang.ArrayIndexOutOfBoundsException。,访问数组的本质就是访问一段连续内存,只要数组通过偏移计算得到的内存地址是可用的,那么程序就可能不会报任何错误。

数组从 0 开始编号的原因

  1. 底层计算机寻址指令可以少计算一个减法
  2. 历史原因:沿用了 C 语言从 0 开始计数的习惯

从数组存储的内存模型上来看,下标最确切的定义应该是偏移(offset)。

a[0] 表示偏移为 0 的位置,也就是首地址,a[k] 表示偏移 k 个 type_size 的位置,计算 a[k] 的内存地址:

a[k]_address = base_address + k * type_size

如果从 1 开始计算,每次随机访问数组元素都多了一次减法运算,对于 CPU 来说,就是多了一次减法指令。

a[k]_address = base_address + (k - 1) * type_size

JavaScript 中的数组

JavaScript 中的数组数据可以是不同类型,它的语法相对宽松。

数组的创建与读写

字面量方式创建数组:

var kaimo = [3, 1, 3];

构造函数方式创建数组:

var kaimo = new Array(3, 1, 3);

判断一个对象是否是数组:

Array.isArray(kaimo);

可以使用循环读写数组:

var kaimo = [3, 1, 3];
for (var i = 0; i < kaimo.length; i++) {
	console.log(kaimo[i]);
}

数组的深复制与浅复制

  • 浅复制:当把数组赋给另外一个数组,然后改变其中一个数组的值,另一数组也会随之改变,这就是数组的浅复制。
  • 深复制:指的就是不改变原来的数组而去创建一个新的数组,这种情况是经常使用的,为了不破坏原数组。

存取函数

JavaScript 提供了一组用来访问数组元素的函数,叫存取函数。最常用的存取函数就是 indexOf() 函数,还有 join 和 toString 函数,concat 和 splice 函数。

可变函数

不去引用数组中的某个元素,就能改变数组内容,这种函数称它为可变函数

  • push() 方法可以在数组末尾添加元素
  • unshift() 方法可以在数组开头添加元素
  • pop() 可以删除数组末尾的元素
  • shift() 删除数组的第一个元素
  • splice() 不仅可以用来删除元素,还可以添加元素进数组
  • sort() 可以为数组排序
  • reverse() 将数组内的元素翻转

sort() 方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的 UTF-16 代码单元值序列时构建的。

var kaimo = [30, 100, 40, 500, 200];
kaimo.sort();

在这里插入图片描述

解决这种排序的错误:在调用 sort() 的时候传入一个函数,该函数可以比较出大小。

function compare(a1, a2) {
    return a1 - a2;
}
var kaimo = [30, 100, 40, 500, 200];
kaimo.sort(compare);

在这里插入图片描述

迭代器方法

迭代函数通过对数组中的元素逐个应用,来操作返回相应的值。

不返回新数组forEach() 、every()、some()、reduce()

  • every() 返回值为布尔类型,对于应用的所有元素,该函数返回 true,则该方法返回 true
  • some()every() 的不同就是只要有一个元素使改函数返回 true ,那么该方法就返回 true
  • reduce() 可以对数组元素进行求和、也能将数组元素连接成字符串。

返回新数组map() 、filter()

map 的作用与 forEach 是一样的,区别就是 map 函数返回的是一个新数组。

filter 和 every 相似,区别在于当所有的元素使改函数为 true 时,它并不返回布尔类型,而是返回一个新数组。

二维数组

JavaScript 可以通过在数组里在嵌套一个数组来形成二维数组。

var kaimo = [
    [11, 12, 13, 14],
    [21, 22, 23, 24],
    [31, 32, 33, 34],
    [41, 42, 43, 44]
];
console.log(kaimo[1][2]); // 23

二维数组的处理可以分为两种:

  • 按列访问,外层循环对应行,内层循环对应列。
  • 按行访问,外层循环对应列,内层循环对应行。

JavaScript 可以处理一些参差不齐的数组,JavaScript 可以处理运行而不报错。

对象数组

数组里面的元素可以是对象,可以用 push() 等操作方法来操作对象数组。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/165452.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第07章 面向对象编程(中级部分OOP)

文章目录IDE(集成开发环境)IDEA介绍idea运行包包的作用包基本语法包的本质分析(原理)包的命名命名规则命名规范常用的包如何引入包注意事项和使用细节访问修饰符【modifier】基本介绍使用的注意事项封装【encapsulation】介绍封装的好处和理解封装的实现步骤封装练习继承【exte…

【阶段四】Python深度学习02篇:深度学习基础知识:神经网络可调超参数:优化器

本篇的思维导图: 神经网络可调超参数:优化器 优化器相当于是用来调解神经网络模型的‘手柄’。 代码 # 编译神经网络,

【C语言】小王带您轻松实现动态内存管理(简单易懂)

在上文通讯录制作中&#xff0c;动态通讯录的使用中就用到了动态内存管理&#xff0c;如果有同学想看一看是如何运用的内存管理函数的&#xff0c;请参考这篇文章&#xff0c;接下来我们一起学习动态内存管理的相关知识。【C语言】使用C语言实现静态、动态的通讯录&#xff08;…

浅显易懂的三次握手与四次挥手

目录 一、三次握手 什么是三次握手&#xff1f; 三次握手图解&#xff1a; 过程解析&#xff1a; &#xff08;1&#xff09;第一次握手&#xff1a; &#xff08;2&#xff09;第二次握手&#xff1a; &#xff08;3&#xff09;第三次握手&#xff1a; 二、四次挥手 …

已解决Python读取20GB超大文件内存溢出报错MemoryError

已解决Python读取20GB超大文件内存溢出报错MemoryError 文章目录报错问题报错翻译报错原因解决方法1解决方法2&#xff08;推荐使用&#xff09;帮忙解决报错问题 日常数据分析工作中&#xff0c;难免碰到数据量特别大的情况&#xff0c;动不动就2、3千万行&#xff0c;如果…

操作系统进程调度算法

进程调度 高级调度&#xff08;作业调度&#xff09;&#xff1a;按一定的原则从外存的作业后备队列中挑选一个作业调入内存&#xff0c;并创建进程。每个作业只调入一次&#xff0c;调出一次。作业调入时会建立PCB&#xff0c;调出时会撤销PCB。 中级调度&#xff08;内存调度…

【历史上的今天】1 月 16 日:互联网工程任务组(IETF)成立;AMD 收购 NexGen;eBay 的第一位员工出生

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 1 月 16 日&#xff0c;在 26 年前的今天&#xff0c;国家电力公司组建成立。电力是运作着我们生活的基本&#xff0c;国家电力公司成立于 1997 年 1 月 16 日…

《深度学习入门基于python的理论与实现》chap2感知机 笔记

《深度学习入门:基于python的理论与实现》chap2 感知机 笔记 3个月前正式开始入坑AI的时候就是看的这本书&#xff0c;当时比较粗略地看到了第六章&#xff0c;没有记笔记&#xff0c;现在来重温一下 文章目录《深度学习入门:基于python的理论与实现》chap2 感知机 笔记2.1 什么…

【阶段四】Python深度学习05篇:深度学习项目实战:卷积神经网络的定义、卷积网络的结构与卷积层的原理

本篇的思维导图: 卷积神经网络的定义 卷积神经网络,简称为卷积网络,与普通神经网络的区别是它的卷积层内的神经元只覆盖输入特征局部范围的单元,具有稀疏连接(sparse connectivity)和权重共享(weight shared)的特点,而且其中的过滤器可以做到对图像关键特征的…

基于Power BI的品牌销售金额帕累托分析

一、原理 帕累托于1906年提出了著名的关于意大利社会财富分配的研究结论&#xff1a;20&#xff05;的人口掌握了80&#xff05;的社会财富。这个结论对大多数国家的社会财富分配情况都成立。因此&#xff0c;该法则又被称为80/20法则。 二、数据源 已知某终端表1《商品信息》…

GO 语言 Web 开发实战一

xdm&#xff0c;咱今天分享一个 golang web 实战的 demo go 的 http 包&#xff0c;以前都有或多或多的提到一些&#xff0c;也有一些笔记在我们的历史文章中&#xff0c;今天来一个简单的实战 HTTP 编程 Get 先来一个 小例子&#xff0c;简单的写一个 Get 请求 拿句柄 设置…

VMware Workstation 17 Pro的下载和安装

目录 一、下载 二、安装 三、检查网络连接 方式一&#xff08;简便版&#xff09; 方式二&#xff08;麻烦版&#xff09; 一、下载 下载地址&#xff1a; Windows 虚拟机 | Workstation Pro | VMware | CN 1、进入该网址后&#xff0c;往下翻&#xff0c;有两个选项&…

并查集是什么?怎么模拟实现?如何应用?

目录 一、什么是并查集&#xff1f; 二、并查集可以解决哪些问题&#xff1f; 三、并查集的模拟实现 3.1、并查集的定义 3.2、查询两个元素是否是同一个集合 3.3、合并两个集合 3.4、求集合个数 3.5、并查集完整代码 小结 一、什么是并查集&#xff1f; 我们可以想象这…

九、MySQL 常用函数汇总(2)

文章目录一、条件判断函数1.1 IF(expr,v1,v2)函数1.2 IFNULL(v1,v2)函数1.3 CASE函数二、系统信息函数2.1 获取MySQL版本号、连接数和数据库名的函数2.2 获取用户名的函数2.3 获取字符串的字符集和排序方式的函数2.4 获取最后一个自动生成的ID值的函数三、加密函数3.1 加密函数…

东宝商城项目(三)——用户注册功能的实现(后端)

本文是我做项目过程中记录的学习笔记&#xff0c;用于记录项目开发流程&#xff0c;第一次做项目有很多不懂的地方&#xff0c;本文可读性暂时很差。 我目前的学习目标是走完项目开发流程&#xff0c;知道独立开发一个项目并让项目上线需要经历哪些步骤&#xff0c;需要学到哪些…

java.util.ConcurrentModificationException: null异常

创作背景&#xff1a;在加强for循环中使用了remove操作 原因&#xff1a; 在官方文档中ConcurrentModificationException的介绍如下&#xff1a; public class ConcurrentModificationException extends RuntimeException 某个线程在 Collection 上进行遍历时&#xff0c;通…

Spring入门-IOC/DI注解管理与整合mybatis及Junit(2)

1&#xff0c;核心容器 前面已经完成bean与依赖注入的相关知识学习&#xff0c;接下来我们主要学习的是IOC容器中的核心容器。 这里所说的核心容器&#xff0c;大家可以把它简单的理解为ApplicationContext&#xff0c;前面虽然已经用到过&#xff0c;但是并没有系统的学习&a…

1.15日报

完成font.css global.css login.vue request.js 今天完成了前端与后端的联通&#xff0c;并成功响应请求。返回登录成功欣喜。 遇到的问题&#xff1a; 我的body设置了&#xff1a; margin:0; padding:0; 但是页面四周还有白色留边。原因&#xff1a;body设置无边框了&a…

用Scipy理解Gamma函数

文章目录Gamma函数对数Gamma函数复数域的Gamma函数Gamma函数 Γ\GammaΓ函数是阶乘的解析延拓&#xff0c;在概率论中非常常见&#xff0c;例如Gamma分布表示某个事件在某个时刻发生第nnn次的概率&#xff1a;Gamma分布详解 Γ\GammaΓ函数显含在Γ\GammaΓ分布中&#xff0c;其…

linux基本功系列之pwd命令实战

本文目录 文章目录一. pwd命令介绍二. 语法格式及常用选项2.1 语法格式2.2 常用参数三. 参考案例3.1 显示所在目录的完整路径3.2 显示符号链接的路径 -P 参数3.3 查看上一次所在的工作目录3.4 查看PWD的版本四. pwd的命令类型总结前言&#x1f680;&#x1f680;&#x1f680; …