1.前言:
数组相当于程序中的容器,是一种重要的基础数据结构,用于存储一系列相同类型的数据。
1.1 数组(Array)概念
用于存储固定大小的同类型元素的集合。每个数组都有一个固定的长度 (一旦创建,长度不可更改),并通过一个索引(从0开始)来访问数组中的元素。
1.2 构成
- 数组名
- 索引、index、下标(找到指定数组元素所使用的编号)
- 元素
- 数组长度(存储数组容器中的存储的元素的个数)
图解:
1.3 数组的特点
- 数组本身是 引用数据类型 ,数组中的元素可以是 任何数据类型 。
- 创建数组对象会在内存中开辟一整块“连续的空间”,占用内存空间大大小,取决于数组的长度和数组中元素的类型。
- 数组中的元素在内存中是依次紧密排列的,有序的。
- 数组的初始化完成,长度就确定,无法更改。
- 数组名中引用的是这块连续空间的首地址。
1.4 为什么要使用数组
-
存储多个值:数组能够在单一变量中存储多个数据值,简化了数据的管理和操作。
-
快速访问:通过索引访问数组中的元素非常快速,时间复杂度为O(1)。这使得数组特别适合需要频繁访问和操作数据的场景。
-
内存管理:数组允许在内存中连续存储数据,提高了内存的使用效率。
-
数据结构基础:数组是实现其他复杂数据结构(如列表、栈、队列等)和算法(如排序、查找等)的基础。
1.5 数组的分类
数组维数分类:
- 一维数组:存储一系列同类型的元素,类似于线性列表。
- 多维数组:由多个一维数组组成,通常用于表示矩阵或表格数据。最常用的是二维数组。
2.一维数组
2.1 声明和初始化,索引
/*数组的声明和初始化
声明数组*/
int[] numbers;
/*初始化数组:
使用 new 关键字:*/
// 创建一个长度为5的整数数组
numbers = new int[5];
//在声明时直接初始化:
int[] numbers = {1, 2, 3, 4, 5};
/*2. 调用数组的指定元素
要访问数组中的特定元素,可以使用索引,索引从0开始。例如:*/
int firstElement = numbers[0]; // 访问数组中的第一个元素
int secondElement = numbers[1]; // 访问数组中的第二个元素
//可以通过索引来修改数组的元素:
numbers[0] = 10; // 将第一个元素的值修改为10
2.2 长度与遍历
/*数组的长度,用来描述数组容器中容量的大小
使用length属性表示:
*/
System.out.printlt(数组名.length);
// 使用传统的 for 循环
int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
// 使用增强的 for 循环
for (int item : arr) {
System.out.println(item);
}
2.3 数组元素的默认初始化值
数组的元素在创建时会被自动初始化为其默认值。默认值取决于数据类型:
- 对于整数类型(如
int
,long
,short
,byte
),默认值为0
。 - 对于浮点类型(如
float
,double
),默认值为0.0
。 - 对于字符类型(
char
),默认值为'\u0000'
(空字符)。 - 对于布尔类型(
boolean
),默认值为false
。 - 对于引用数据类型,默认值为
null
。
public class Person {
public static void main(String[] args) {
//采用int类型
int[] arr = new int[5];
System.out.println("这是int类型的默认初始化值: " + arr[0]);
short[] arr2 = new short[4];
System.out.println("这是short类型的默认初始化值: " + arr2[0]);
//采用浮点类型
double[] arr3 = new double[3];
System.out.println("这是double类型的默认初始化值: " + arr3[0]);
//采用字符类型
char[] arr4 = new char[3];
System.out.println("这是char类型的默认初始化值: " + arr4[0]);
//采用布尔类型
boolean[] arr5 = new boolean[2];
System.out.println("这是boolean类型的默认初始化值: " + arr5[0]);
//采用引用数据类型
String[] arr6 = new String[5];
for (int i = 0; i < arr6.length; i++) {
System.out.println("这是String类型的默认初始化值: " + arr6[i]);
}
}
}
结果:
2.4 内存解析
一维数组的内存分配是动态的。当你使用 new
关键字创建数组时,Java虚拟机(JVM)在堆内存中分配相应大小的内存空间。数组的长度在创建时被固定,数组中的每个元素都会占用相同大小的内存块。
内存空间可以分为5个部分:程序计数器、虚拟机栈、本地方法栈、堆、方法区。
与目前数组相关的内存结构:
- 虚拟机栈:用于存放方法中的声明的变量。如:arr。
- 堆:用于存放数组的实体(数组中的所有元素)如:1.2.3
3.二维数组
理解:可以堪称是一维数组arr1又作为另一个一维数组arr2的元素而存在。即嵌套一维数组
3.1 声明和初始化,索引
public class TwoDimensionalArrayExample {
public static void main(String[] args) {
//静态声明并初始化二维数组
int[][] array = new int[][]{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
//动态初始化
String[][] arr2 = new String[3][4]
// 访问第二行第三列的元素,值为 6
int value = array[1][2];
/*对于内层元素的调用,要将元素的行列写清楚
对于外层元素的调用,只要写清楚行*/
System.out.println(array[0][1]);//内层元素
System.out.println(array[0]);//外层元素
}
}
3.2 长度与遍历
//通过 length 属性获取二维数组的行数和列数:
int rows = array.length; // 行数
int cols = array[0].length; // 列数(假设所有行列数相同)
//使用嵌套的 for 循环遍历二维数组:
for (int i = 0; i < array.length; i++) { // 遍历行
for (int j = 0; j < array[i].length; j++) { // 遍历列
System.out.print(array[i][j] + " ");
}
System.out.println(); // 换行
}
3.3 默认初始化值
二维数组的元素会被默认初始化为以下值:
外层:
- 表示地址值
System.out.println(arr[3]);//@32674235
内层:
- 对于
int
类型:0
- 对于
boolean
类型:false
- 对于
char
类型:'\u0000'
(空字符) - 对于引用数据类型:
null
System.out.println(arr3[0][1]);//值为null
3.4 二维内存解析
Java 中的二维数组实际上是一个数组的数组。在内存中,它的分配是非连续的,每个内层数组的内存地址可能不在同一位置。这种结构使得可以有不规则的数组(即每一行的列数可以不同)。
解释:
先开辟了arr1的三行两列的空间,arr2的空间则是4行n列,arr2的第二行有5列,由于是int类型默认为0,再将arr2的第二行第二列从0变1。由于arr2的第三行没有说明列数,所以arr的第三行第三列不存在,报错。
解释:
先给arr1的4行n列开辟空间,再给arr1的第一行开辟了三列的空间,int类型默认值为0。arr1的第二行也开辟了三列的空间,但有赋值1,2,3。将arr1的第一行第三列的值改为5。最后相当于初始化了一个新的arr1。
4.数组的常见算法操作
4.1 特征值计算
示例:
import java.util.Random;
public class ArrayTest {
public static void main(String[] args) {
// 创建一个大小为10的int型数组
int[] numbers = new int[10];
// 创建一个Random对象用于生成随机数
Random random = new Random();
// 使用循环为数组赋随机值
for (int i = 0; i < numbers.length; i++) {
// 生成0到100之间的随机整数
numbers[i] = random.nextInt(101); // 包含0到100的随机数
}
// 初始化最大值、最小值和总和
int max = numbers[0];
int min = numbers[0];
int sum = 0;
// 遍历数组,计算最大值、最小值和总和
for (int number : numbers) {
if (number > max) {
max = number; // 更新最大值
}
if (number < min) {
min = number; // 更新最小值
}
sum += number; // 累加到总和
}
// 计算平均值
double average = (double) sum / numbers.length;
// 输出结果
System.out.println("数组元素: ");
for (int number : numbers) {
System.out.print(number + " ");
}
System.out.println("\n最大值: " + max);
System.out.println("最小值: " + min);
System.out.println("总和: " + sum);
System.out.println("平均值: " + average);
}
}
结果:
4.2 数组元素的赋值与反转
赋值示例:杨辉三角,10行
public class YangHuiTriangle {
public static void main(String[] args) {
// 定义二维数组,用于存储杨辉三角的10行
int[][] triangle = new int[10][];
// 构造杨辉三角
for (int i = 0; i < 10; i++) {
// 每一行的长度为i + 1
triangle[i] = new int[i + 1];
// 第一列和最后一列的元素为1
triangle[i][0] = 1;
triangle[i][i] = 1;
// 计算每一行的其他元素
if (i > 1) { // 从第三行开始
for (int j = 1; j < i; j++) {
triangle[i][j] = triangle[i - 1][j - 1] + triangle[i - 1][j];
}
}
}
// 打印杨辉三角
for (int i = 0; i < 10; i++) {
for (int j = 0; j < triangle[i].length; j++) {
System.out.print(triangle[i][j] + " ");
}
System.out.println(); // 每行输出后换行
}
}
}
结果:
数组反转的示例:反转这个一维数组
public class Person {
public static void main(String[] args) {
int[] fanzhuan = new int[]{1,2,3,4,5};
//遍历原本的顺寻
for (int i = 0; i < fanzhuan.length; i++) {
System.out.print(fanzhuan[i]+ " ");
}
System.out.println(); // 每行输出后换行
//反转操作
for (int i = 0; i < fanzhuan.length/2; i++) {
int temp = fanzhuan[i];
fanzhuan[i] = fanzhuan[fanzhuan.length-1-i];
fanzhuan[fanzhuan.length-1-i] = temp;
}
//
for (int i = 0; i < fanzhuan.length; i++) {
System.out.print(fanzhuan[i]+ " ");
}
System.out.println(); // 每行输出后换行
}
}
结果:
4.3 数组的扩容和缩容
示例:int[] arr = new int[] {1,2,3,4,5};将数组扩容一倍,并插入6,7,8三个元素到数组中
public class Person {
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4,5};
int[] newArr = new int[arr.length << 1];
for (int i = 0; i < arr.length; i++) {
newArr[i] = arr[i];
}
newArr[arr.length] = 6;
newArr[arr.length+1] = 7;
newArr[arr.length+2] = 8;
arr = newArr;
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] +"\t");
}
}
}
结果:
示例:int[] arr = {1,2,3,4,5,6},现需删除数组中索引为3的元素,也就是删除4,即缩容一列。
public class Person {
public static void main(String[] args) {
int[] arr = new int[]{1,2,3,4,5,6,7};
int index1 = 3;
/*不使用新数组
for (int i = index1; i < arr.length-1; i++) {
arr[i] = arr[i+1];
}
arr[arr.length-1] = 0;
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
}
*/
//使用新数组
int[] newArr = new int[arr.length - 1];
for (int i = 0; i < index1; i++) {
newArr[i] = arr[i];
}
for (int i = index1; i < arr.length - 1; i++) {
newArr[i] = arr[i+1];
}
for (int i = 0; i < newArr.length; i++) {
System.out.print(newArr[i]);
}
}
}
结果:
4.4 查找数组元素
4.4.1 线性查找
示例:int[] arr1 = new int[]{55,66,77,1,2,3,4,5,76,34,12};查找元素77是否出现过,出现求索引值
public class Person {
public static void main(String[] args) {
int[] arr1 = new int[]{55,66,77,1,2,3,4,5,76,34,12};
int target = 77;
//线性查找
boolean isFlag = true;
for (int i = 0; i < arr1.length; i++) {
if (target == arr1[i]){
System.out.println("找到啦"+ target + ",对应位置为:"+ i);
isFlag = false;
break;
}
}
if (isFlag) {
System.out.println("没找到");
}
}
}
结果:
4.4.2 二分法查找
说明:适用于在有序数组中查找特定元素。其基本思路是将数组分成两半,逐步缩小查找范围。
步骤:
- 初始化:设置两个指针,
low
指向数组的开始,high
指向数组的结束。 - 查找:
- 计算中间索引
mid = (low + high) / 2
。 - 比较
mid
位置的元素与目标值:- 如果相等,返回
mid
(找到了目标值)。 - 如果目标值小于
mid
位置的元素,则在左半部分继续查找,将high
更新为mid - 1
。 - 如果目标值大于
mid
位置的元素,则在右半部分继续查找,将low
更新为mid + 1
。
- 如果相等,返回
- 计算中间索引
- 结束:如果
low
大于high
,说明目标值不在数组中,返回一个表示未找到的值(例如 -1)。
图解:
示例:int[] arr2 = new int[]{55,66,77,1,98,45,69,2,3,4,5,76,34,12};查找元素55是否出现过,出现求索引值
public class Person {
public static void main(String[] args) {
int[] arr2 = new int[]{55,66,77,1,98,45,69,2,3,4,5,76,34,12};
int target = 77;
int low = 0;//首索引
int high = arr2.length - 1;//默认的尾索引
int mid;
boolean isFlag = false;
while(low <= high) {
mid = (high + low) / 2;
if (target == arr2[mid]) {
System.out.println("找到了 " + target + ",对应位置为:" + mid);
isFlag = true;
break;
}else if(target > arr2[mid]){
high = mid +1;
}else {
low = mid - 1;
}
}
if (!isFlag){
System.out.println("没找到");
}
}
}
结果:
4.4.3 优缺点
线性查找:
- 优点:算法简单
- 缺点:执行效率低,执行的时间复杂度0(N)
二分法查找:
- 优点:执行效率高,执行时间复杂度0(logN)
- 缺点:偏难,前提:数组必须有序
4.5 数组元素排序
算法概述:用于将一组数据按照特定顺序排列。
目的;为了快速查找;
衡量排序算法的优劣:
- 时间复杂度:关键字的比较次数和记录的移动次数。
- 空间复杂度:分析排序算法中需要多少辅助内存。
- 稳定性:若A与B的关键字值相等,但排序后A、B的先后次序保持不变。
4.5.1 冒泡排序
说明:它通过重复遍历待排序的数列,比较相邻的元素,并在顺序错误的情况下交换它们。这个过程持续进行,直到没有需要交换的元素为止,即数列已经有序。因为每次比较后,较大的元素会“冒泡”到数列的末尾。
冒泡排序的工作原理
- 比较相邻元素:从数组的第一个元素开始,比较当前元素与下一个元素的大小。
- 交换:如果当前元素大于下一个元素,则交换它们。
- 重复过程:对每一对相邻元素重复以上过程,直到数组的末尾。
- 缩小范围:每一趟结束后,最后一个元素是当前的最大值,下次比较时可以忽略它。
- 终止条件:如果在某一趟中没有进行任何交换,表示数组已经有序,可以提前终止算法。
示例:
public class BubbleSort {
public static void bubbleSort(int[] arr) {
int n = arr.length;
boolean swapped;
// 外层循环控制趟数
for (int i = 0; i < n - 1; i++) {
swapped = false; // 每趟开始前重置交换标志
// 内层循环进行相邻元素比较
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换 arr[j] 和 arr[j + 1]
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = true; // 标记发生了交换
}
}
// 如果没有发生任何交换,提前结束
if (!swapped) {
break;
}
}
}
// 测试冒泡排序
public static void main(String[] args) {
int[] arr = {64, 34, 25, 12, 22, 11, 90};
System.out.println("原始数组:");
printArray(arr);
bubbleSort(arr);
System.out.println("排序后的数组:");
printArray(arr);
}
// 辅助方法,打印数组
public static void printArray(int[] arr) {
for (int value : arr) {
System.out.print(value + " ");
}
System.out.println();
}
}
结果:
4.5.2 快速排序
说明:通过选择一个“基准”元素(pivot),将数组分成两个子数组,其中一个子数组的所有元素都小于基准元素,另一个子数组的所有元素都大于基准元素。然后,递归地对这两个子数组进行排序,最终实现整个数组的排序。
快速排序的基本步骤
- 选择基准元素:从数组中选择一个元素作为基准。
- 分区操作:重排数组,将比基准元素小的元素放在其左边,比基准元素大的元素放在其右边。此时,基准元素的最终位置已确定。
- 递归排序:对基准元素左边和右边的子数组分别进行快速排序。
示例:
public class QuickSort {
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
// 获取分区索引
int pi = partition(arr, low, high);
// 递归排序分区
quickSort(arr, low, pi - 1); // 左子数组
quickSort(arr, pi + 1, high); // 右子数组
}
}
private static int partition(int[] arr, int low, int high) {
// 选择最后一个元素作为基准
int pivot = arr[high];
int i = (low - 1); // 小于基准的元素索引
for (int j = low; j < high; j++) {
// 如果当前元素小于或等于基准
if (arr[j] <= pivot) {
i++; // 增加小于基准的元素索引
swap(arr, i, j); // 交换元素
}
}
// 将基准元素放到正确的位置
swap(arr, i + 1, high);
return i + 1; // 返回基准元素的索引
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
public static void main(String[] args) {
int[] arr = {10, 7, 8, 9, 1, 5};
int n = arr.length;
quickSort(arr, 0, n - 1);
System.out.println("排序后的数组: ");
for (int num : arr) {
System.out.print(num + " ");
}
}
}
结果:
4.5.3 小结
排序的分类:内部排序(内存中排序),外部排序(外部存储设备+内存)
关注:
- 冒泡排序:最简单,要求会敲。
- 快速排序:最快的,开发中的默认排序方式,要掌握其实现思路。