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

Java从入门到精通!第三天(数组)

十、数组


1. 数组(Array)
多个相同数据类型的数据按照一定的顺序组成的集合,通过一个名字(数组名)来引用它,并通过编号(下标)的形式来对数据进行统一管理。
(1) 数组本身是引用数据类型
数组本身是引用数据类型,但数组里面的元素既可以是基本类型,也可以是引用类型。


(2) 创建数组之后,会在内存中开辟一个连续的内存空间(堆)来存放数据,而数组名(栈)就引用了这个内存空间
补充知识:Java的内存划分
为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的数据方式和内存管理方式。JVM的内存划分:
1)寄存器:给CPU使用的高速缓存,和我们Java开发无关
2)本地方法栈:JVM在使用操作系统的功能的时候使用,和我们Java开发无关
3)方法区:存储可运行的.class文件(字节码文件)
4)堆内存:存储对象或数组,new操作符创建出来的,都是放在堆内存中的,堆中开辟的内存的特点是不会自动释放,需要手动释放内存
5)方法栈:即栈,方法(函数)运行时使用的内存,比如main方法运行,要进入方法栈中执行,执行完之后又会弹出栈,出栈之后方法就会被释放,所以方法中的局部变量和参数等都会被自动释放。
示例:

内存结构图:

总结数组的内存结构:
在栈中有一个数组名的引用类型变量,保存了指向堆内存的地址,在堆中一个连续的内存空间保存数组各个元素的值,其中第一个元素的内存地址就是数组名变量保存的内存地址。
(1)数组的长度一旦确定,不能修改
(2)数组元素是通过下标进行引用


2. 声明数组的语法
数据类型[] 数组名 = new 数据类型[数组长度];
数据类型 数组名[] = new 数据类型[数组长度];


3. 引用数组元素的语法
通过下标进行引用的,下标的取值范围为:0~数组长度-1,0 表示第一个元素,1 表示第二个元素,......
示例:


 
4. 数组的初始化

(1) 动态初始化:声明数组,并为其分配内存空间,然后赋值
示例:


上述例子对应的内存图:



(2) 静态初始化:在定义数组的同时就为其分配空间并赋值


 
总结:

1)数组的使用必须先为其分配内存空间之后才能使用
2)数组元素的引用:数组名[下标];
3)数组元素的下标只能是整数常量或整型表达式
4)数组下标的取值只能是0到数组长度-1的范围
5)每个数组都有一个属性length,表示该数组的长度
6)定义数组时,不能像C语言这样定义:int arr[6];
示例:使用数组的属性length


5. 数组元素的默认初始值

当我们定义一个数组的时候,并为其分配内存空间,根据不同的数据类型,数组中各个元素默认的初始值是不同的:

示例:


练习:定义一个数组,里面包含一些随机的 100 以内的整数,找出数组中的最大值。提示,使用Math.random() 方法可以生成 0~1 之间的随机浮点数。


6. 多维数组
(1) 二维数组:相当于就是一个表格

我们将二维数组看成一个一个的一维数组中的各个元素又包含了一维数组,所以从数组的底层来说,其实没有多维数组。
(2) 二维数组初始化
1) 方式一

2) 方式二

3) 方式三

示例1:


内存结构图:

示例2:二维数组的使用



十一、数组牵涉到的算法


1.算法效率
(1) 如何去衡量一个算法的好坏?
通常我们从时间效率和空间效率两方面去分析算法的好坏。时间效率即时间复杂度,空间效率被称为空间复杂度。时间复杂度主要是衡量一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的额外空间。
常见的复杂度大小比较:O(2^N) > O(N^2) > O(N*logN) > O(N) > O(logN) > O(1),效率递增


(2) 时间复杂度
定义:定量描述了该算法的运行时间。一个算法执行所耗费的时间与其中语句的执行次数成正比例,算法中基本的执行次数,即算法的时间复杂度。
通常在计算算法的时间复杂度时,并不一定要计算精确的执行次数,而只需要计算大概执行次数,我们通常使用大O渐进法来表示。
a. 用常数 1 来取代运行时间中所有的执行次数为常数;
b. 只保留最高的阶项;
c. 如果最高项存在且不是 1,则去除与这个项目相乘的常数,得到的结果就是大 O 阶.
1)代码演示如下:

对于上述代码进行时间复杂度分析:
第一个嵌套 for 循环的执行次数为 N^2,第二个 for 循环的执行次数为 2N,第三个 while 循环的执行次数是 10, 则 F(N)=F(N^2) + F(2N)+10,根据大 O 渐进表示法,该算法的时间复杂度为 O(N^2).
算法的时间复杂度存在最好、平均、最坏情况:
最好情况:任意输入规模的最大运行次数(上界)
平均情况:任意输入规模的期望运行次数
最坏情况:任意输入规模的最小运行次数(下界)
注意: O(1) 表示基本语句的执行次数是一个常数,一般来说,只要算法中不存在循环语句,时间复杂度就为 O(1)。
2)下面选取一些常见的进行讲解
a. 常数阶 O(1)
无论代码执行了多少行,只要是没有循环等复杂结构,那这个代码的时间复杂度就都是 O(1),如:

上述代码在执行的时候,它消耗的时间并不随着某个变量的增长而增长,那么无论这类代码有多长,即使有几万几十万行,都可以用 O(1) 来表示它的时间复杂度。
b. 线性阶 O(n)

这段代码,for 循环里面的代码会执行 n 遍,因此它消耗的时间是随着 n 的变化而变化的,因此这类代码都可以用 O(n) 来表示它的时间复杂度。
c. 对数阶 O(logN):时间复杂度的对数log都是以2为底

我们假设循环 x 次之后,i 就大于 n 了,此时这个循环就退出了,也就是说 2 的 x 次方等于 n,那么 x = log2^n,也就是说当循环 log2^n 次以后,这个代码就结束了。因此这个代码的时间复杂度为:O(logn)
d. 线性对数阶 O(nlogN)
线性对数阶 O(nlogN) 其实非常容易理解,将时间复杂度为 O(logn) 的代码循环 N 遍的话,那么它的时间复杂度就是 n * O(logN),也就是了 O(nlogN)。
就拿上面的代码加一点修改来举例:

e. 平方阶 O(n²)
平方阶 O(n²) 就更容易理解了,如果把 O(n) 的代码再嵌套循环一遍,它的时间复杂度就是 O(n²) 了。
举例:

这段代码其实就是嵌套了 2 层 n 循环,它的时间复杂度就是 O(n*n),即 O(n²)
立方阶 O(n³)、K 次方阶 O(n^k),参考上面的 O(n²) 去理解就好了,O(n³) 相当于三层 n 循环,其它的类似。
f. 指数阶 O(2^N)
递归的时间复杂度计算: 递归的次数 * 每次递归后代码的执行次数 (也是用大 O 渐进法表示)

上面的代码是一个计算斐波那契数列的方法,使用的是递归的方法,我们知道每一次调用这个方法时,我们的时间复杂度是一个常数,那么这个递归的时间复杂度就是我们一共调用了多少次方法,现在我们来分析一下我们到底调用了多少次这个方法,如下:

当我们输入 N 时,方法会进行两调用,然后不断地调用,那么我们执行调用的总次数为 F(N)=2⁰+2¹+2²+…+2^N-1,取最高阶,所以该算法的时间复杂度为 O(2^N)。
由以上的计算我们就可以发现用递归来算斐波那契数的算法的时间复杂度太高了,也就说明了这个算法的低效,可以改为循环的方式。
总结:
时间复杂度只需大概想想执行次数n的表达式,取次数最大的那项,也不用理会n的常系数。
如果涉及递归,要想想递归函数的执行次数:

(3)空间复杂度
空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度。空间复杂度不是程序占用了多少 bytes 的空间,所以空间复杂度算的是变量的个数,其计算规则与时间复杂度类似,也采用大 O 渐进表示法.
注意:函数运行时所需要的栈空间在编译期间已经确定好了,因此空间复杂度主要通过函数在运行时候显式申请的额外空间来确定(主要是指占堆空间的大小)。
a. 一个算法在计算机上占用的内存包括:程序代码所占用的空间,输入输出数据所占用的空间,辅助变量所占用的空间这三个方面,程序代码所占用的空间取决于算法本身的长短,输入输出数据所占用的空间是通过调用函数传递而来,只有辅助变量是算法运行过程中临时占用的存储空间,与空间复杂度相关;
b. 通常来说,只要算法不涉及到动态分配的空间(堆空间),以及递归、栈所需的空间,空间复杂度通常为 O(1);

空间复杂度:O(1)
这里需要理解:算法在运行过程中临时占用存储空间(额外)大小的量度,这里开辟 end,sorted ,i 三个临时变量,因为大 O 渐进表示法,3 为常数所以为 O(1) 。

这段代码中,第一行 new 了一个数组出来,这个数据占用的大小为 n,这段代码的 2-6 行,虽然有循环,但没有再分配新的空间,因此,这段代码的空间复杂度主要看第一行即可,即 O(n)

2. 数组涉及到的算法

(1) 打印杨辉三角形

杨辉三角形的特点:

第一行有1个元素,第n行有n个元素

每行的第一个元素和最后一个元素为1

从第三行开始,对于非第一个元素和最后一个元素,存在如下规律:

代码:打印10行杨辉三角形

算法分析

时间复杂度:

生成杨辉三角形的时间复杂度是 O(n^2),其中 n 是三角形的行数。原因如下:

外层循环遍历每一行,从 0 到 n-1,总共运行 n 次。

内层循环遍历每一行的元素,从 1 到 i-1,总共运行大约 i 次。

因此,总体时间复杂度为:O(1+2+3+...+(n−1))=O(n(n−1)/2)=O(n^2)

空间复杂度:

生成杨辉三角形所需的空间复杂度是 O(n^2)。这个复杂度来自于存储杨辉三角形的二维数组:

杨辉三角形第 i 行包含 i+1 个元素。

所有行的元素总数是:1+2+3+...+n= n(n+1)/2

因此,空间复杂度是 O(n^2)。

(2) 二分查找法

对于一个自然有序的数组(自然有序:从小到大排好序),采用从中间分为两部分,每次从左右两部分进行比对,如果刚好等于中间这个元素,则找到了,否则如果该元素比中间元素大,则在后面部分继续采用二分查找法查找,否则在前面部分采用二分查找法查找。

                  mid

2 5 7 8 10   15   18 20 22 25 28

如果要找的数比这个mid(15)大,则在后面采用二分查找法查找

           mid

18 20   22   25 28

如果要找的数比这个mid(15)小,则在前面部分采用二分查找法查找

     mid

2 5   7   8 10

依次循环比较,如果找到数等于mid,则找到了

代码:

算法分析

时间复杂度:

二分查找的时间复杂度为 O(logn),其中 n 是数组的长度。因为每次搜索都将搜索范围缩小一半,假设我们折半了x次,即n/2^x=1,则次数x=log2^n,即x=logn,得出O(logn)

空间复杂度:

二分查找的空间复杂度为 O(1),因为只需使用固定的额外空间来存储索引和中点。

(3) 插入排序

插入排序(Insertion Sort)是一种简单且直观的排序算法,适合小规模数据集的排序,其基本思想是将待排序的元素逐一插入到已排序部分的正确位置。详细分析:

插入排序的基本步骤如下:

1) 初始:将第一个元素视为已排序部分,开始从第二个元素开始排序。

2) 插入:

取出当前待排序的元素(称为关键字)。

从已排序的末尾开始,逐一于关键字进行比较,找到正确的插入位置。

将关键字插入到找到的位置。

3) 重复:对数组中未排序的每个元素重复步骤2),直到整个数组被排序。

例如存在序列:17,3,25,14,20,9,其中()里面的是有序表,后面的是无序表

代码:

算法分析

时间复杂度:

1)最优情况:当输入数组已经是有序的时,时间复杂度为 O(n),其中 n 是数组的长度。这是因为每个元素的插入操作只需要一次比较和赋值。

2)最坏情况:当输入数组是完全逆序时,时间复杂度为 O(n^2)。这是因为每个元素的插入操作需要与已排序部分的所有元素进行比较和移动。

3)平均情况:在随机排列的数组中,时间复杂度也是 O(n^2)。

空间复杂度:

空间复杂度为 O(1),因为插入排序是原地排序算法,只需使用一个额外的变量(即关键字)来完成排序操作。

稳定性:插入排序是一种稳定的排序算法,即相等的元素在排序后相对位置不会改变。

(4) 选择排序

选择排序(Selection Sort)是一种简单的排序算法,其基本思想是每次从未排序的部分中选择最小(最大)元素,并将其放到已排序部分的末尾,以下是选择排序的基本步骤:

1) 初始:从数组的第一个元素开始,假设其为最小值。

2) 选择:

遍历未排序部分的每个元素,找出最小值。

将找到的最小值与假设的第一个元素交换位置。

3) 重复:将已排序部分向右扩展一个位置,对剩余的未排序的部分重复步骤2),直到整个数组排序完成。

先假设第一个元素49是最小值,从第二个元素38开始往后遍历找出最小的值,和第一个元素49互换,然后第一个元素位置确定,从第二个元素38开始,假设38是最小的值,从65到最后遍历找出最小值和38互换,从而确定前面两个数据排好序,依次类推...

代码:

性能分析

时间复杂度:

最坏情况:O(n^2),每次选择最小值需要遍历未排序部分,整个排序过程中需要进行 n−1 次选择。

最佳情况:O(n^2),即使数组已经排序,选择排序依然需要遍历所有未排序元素来寻找最小值。

平均情况:O(n^2),在随机排列的情况下,选择排序的时间复杂度仍为 O(n^2)。

空间复杂度:

空间复杂度为 O(1),因为选择排序是原地排序算法,仅使用了常数级别的额外空间来存储临时变量。

稳定性:选择排序是不稳定的排序算法,因为相同的元素可能会被交换位置,改变其相对顺序。

(5) 冒泡排序

冒泡排序(Bubble Sort)是一种简单的排序算法,它的基本思想就是通过重复交换相邻的逆序元素来将最大(或最小)的元素“冒泡”到数组的一端,算法的核心在于比较相邻元素并交换它们的位置,直到整个数组排序完成。

基本步骤:

1) 初始:从数组的第一个元素开始,逐个比较相邻的元素。

2) 比较和交换:

如果当前元素大于下一个元素,则交换着两个元素的位置。

经过一轮比较后,最大的元素会被移动到数组的末尾。

3) 重复:每次进行一轮比较,将未排序部分的最大(或最小)元素移动到已排序部分的末尾,重复以上过程,直到所有元素排序完成。

第一趟:

第一趟两两比较确定了97的位置,以后就不比较97了,即比较次数减1,然后再进行第二趟,第三趟的比较...

经过多趟比较之后,大的数往下沉,小的数往上冒

代码:

性能分析

时间复杂度:

最坏情况:O(n^2),当数组完全逆序时,冒泡排序需要进行最多的交换操作和比较操作。

最佳情况:O(n),如果数组已经排序,冒泡排序可以通过 swapped 标志判断没有交换发生,从而提前终止。此时的时间复杂度为 O(n)。

平均情况:O(n^2),在随机排列的情况下,冒泡排序的时间复杂度仍为 O(n2)。

空间复杂度:

空间复杂度为 O(1),因为冒泡排序是原地排序算法,仅使用了常量级别的额外空间来存储临时变量。

稳定性:冒泡排序是稳定的排序算法,因为它只交换相邻的元素,能保证相同值的元素在排序后保持原有的相对位置。

(6) 希尔排序

希尔排序是一种基于插入排序的算法,通过将数据集分成多个较小的子集进行排序,从而提高插入排序的效率。其主要思想是通过增量序列将数据分组进行局部排序,随着增量主键减小,最终达到整体有序的效果。

希尔排序的步骤如下:

1) 选择增量序列:首先定义一个增量序列,这个序列决定了数组的分组方式。最常用的增量序列是每次将增量除以2,直到增量为1。

2) 按增量分组排序:对于每一个增量值,将数组分为多个组,每组包含增量间隔的元素,对这些组进行插入排序。

3) 重复:逐步减少增量,直到增量为1,即整个数组变为一个组,此时进行最后的插入排序,完成排序过程。

比如,数组:9,8,7,6,5,4,3,2,1,0,希尔排序的过程:

代码:

性能分析:

时间复杂度

希尔排序的时间复杂度依赖于所选择的增量序列。以下是几种常见增量序列的时间复杂度:

最坏情况:

使用希尔增量(n/2, n/4, ..., 1):O(n^2)

使用 Hibbard 增量:O(n^3/2)

使用 Sedgewick 增量:O(nlog^2n)

平均情况:由于希尔排序的时间复杂度与增量序列相关,使用合适的增量序列,平均时间复杂度可以达到 O(nlogn)。

最佳情况:在某些优化增量序列下,希尔排序的最佳时间复杂度为 O(nlogn),但通常情况下为 O(nlog^2n)。

空间复杂度

希尔排序的空间复杂度为 O(1),因为它是原地排序算法,只使用了常量级别的额外空间来存储临时变量。

稳定性

希尔排序是一种不稳定的排序算法。原因是相同的元素可能会因为不同的增量分组排序而改变它们的相对顺序。

(7) 快速排序

快速排序是一种高效的排序算法,基于分治策略。其基本思想是:

1) 选择基准:从数组中选择一个元素作为“基准”(pivot)

2) 分区操作:将数组分成两部分,一部分包含所有比基准小的元素,另一部分包含所有比基准大的元素。基准元素最终排到其正确的位置上。

3) 递归排序:对两部分子数组分别递归地执行快速排序。

下面我们以数组:10,5,30,15,6来演示:

以第一位数字为基准数(数字10)

从右边开始找第一个小于基准数的数后停止(因为基准数是最左边的数,所以只能从右边开始动)

然后从左边开始找第一个大于基准数的数后停止,在左边找的过程中不能越过右边停下来的数,此时交换

第二次交换:此时右边移动到6的位置结束,左边也是移动到6的位置结束,此时两边相遇,此时将基准数10和6进行交换,此时的数字10的位置就确定了

然后以10为分界点拆分成两个序列,左序列是6、5,右序列是15、30,按照同样的方法进行排序,直到左右序列都是不可分割的(递归的过程)。

代码:

性能分析:

时间复杂度

最坏情况:如果每次分区选择的基准是数组的最大或最小元素,时间复杂度为 O(n^2)。

平均情况:选择基准较好,时间复杂度为 O(nlogn)。

最佳情况:每次分区都能将数组分成几乎相等的两部分,时间复杂度为 O(nlogn)。

空间复杂度

空间复杂度:快速排序的空间复杂度是 O(logn)。这是因为递归调用栈的深度在平均情况下是 logn 层。

稳定性

快速排序是一种不稳定的排序算法。元素的相对顺序可能在排序过

数组的工具类 Arrays

练习:创建一个长度为6的int类型的数组,要求元素的取值范围为1~30之间,同时各个元素的值不同,然后输出。

http://www.dtcms.com/a/272419.html

相关文章:

  • Shell 中的重定向
  • C++实习面试题
  • 如何看待java开发和AI的关系?
  • GO启动一个视频下载接口 前端可以边下边放
  • 【PyTorch】PyTorch中的数据预处理操作
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | DoubleVerticalSlider(双垂直滑块)
  • 图解LeetCode:79递归实现单词搜索
  • Django+DRF 实战:自定义异常处理流程
  • 20.4 量子安全加密算法
  • 案例分享--福建洋柄水库大桥智慧桥梁安全监测(二)之数字孪生和系统平台
  • 机器学习13——支持向量机下
  • TCP传输控制层协议深入理解
  • 当CCLinkIE撞上Modbus TCP:照明控制系统的“方言战争”终结术
  • VIP可读
  • 线性回归与正则化
  • Django专家成长路线知识点——AI教你学Django
  • 【PTA数据结构 | C语言版】顺序栈的3个操作
  • 【深度学习系列--经典论文解读】Gradient-Based Learning Applied to Document Recognition
  • LINUX710 MYSQL
  • linux-用户与用户组管理
  • serialVersionUID
  • 配置 msvsmon.exe 以无身份验证启动
  • 力扣打卡第23天 二叉搜索树中的众数
  • 算法题(171):组合型枚举
  • Shusen Wang推荐系统学习 --召回 矩阵补充 双塔模型
  • 深度探索:实时交互与增强现实翻译技术(第六篇)
  • Win10用camke+gcc编译opencv库报错error: ‘_hypot‘ has not been declared in ‘std‘
  • 什么是 领域偏好学习(DPO)与多目标强化学习(PPO)
  • 在 Ubuntu 22 部署 vLLM + Qwen3 32B 模型
  • EPLAN 电气制图(六):电机正反转副勾主电路绘制