My-Blog-Hexo/source/_posts/Java数组.md
2024-12-18 20:27:43 +08:00

401 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Java数组
date: 2020-05-24 18:59:37
tags:
- Java
categories:
- Java基础
---
## 数组概述
**`数组的定义:`**
* 数组是<span style="color:red">相同类型数据</span>的有序集合。
* 数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成。
* 其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们。
<!--more-->
### 数组的声明创建
首先必须声明数组变量,才能在程序中使用数组。下面是声明数组变量的语法:
```java
dataType[] arrayRefVar; //首选的方法
dataType arrayRefVar[]; //效果相同,但不是首选方法,不推荐使用
```
Java语言使用`new` 操作符来创建数组,语法如下:
```java
dataType[] arrayRefVar = new dataType[arraySize];
```
数组的元素是通过索引访问的,<span style="color:red">数组索引从**0**开始</span>
获取数组长度:`arrays.length`
声明时数组在内存中并不存在,只有在创建数组时,才会在内存中为数组分配指定的空间。
### Java内存分析
![java内存](https://i.loli.net/2020/05/23/pEowZelcCNFGzQ3.png)
`数组在内存中的创建过程:`
1. 在声明数组时会在栈中压入数组名
2. 创建数组时会在堆中开辟指定的空间用来存放数组
3. 给数组赋值,将值存放在堆中数组对应的空间里
![数组在内存中的创建过程](https://i.loli.net/2020/05/23/QGIF8zbnEcasDvg.png)
### 数组的三种初始化
* **静态初始化**
```java
int[] a = {1,2,3};
Man[] mans ={new Man(1,1),new Man(2,2)};
```
* **动态初始化**
```java
int[] a = new int[2]; //创建数组 默认初始化
a[0] = 1;
a[1] = 2;
```
* **数组的默认初始化**
数组是引用类型,它的元素相当于类的实例变量,因此数组一经分配空间,其中的每个元素也被按照实例变量同样的方式被隐式初始化。
### 数组的四个基本特点
* 其长度是确定的。数组一旦被创建,它的大小就是不可以改变的。
* 其元素必须是<span style="color:red">相同类型</span>,不允许出现混合类型。
* 数组中的元素可以是任何数据类型,包括基本类型和引用类型。
* 数组变量属引用类型数组也可以看成是对象数组中的每个元素相当于该对象的成员变量。数组本身就是对象Java中对象是在堆中的因此数组无论保存原始类型还是其他对象类型<span style="color:red">数组对象本身是在堆中的</span>
### 数组边界
下标的合法区间:[0,length-1],如果越界就会报错;
```java
public static void main(String[] args){
int[] a = new int[2];
System.out.println(a[2]); //报错
}
```
<span style="color:red">ArraylndexOutOfBoundsException数组下标越界异常</span>
> **`小结`**
>
> * 数组是相同数据类型(数据类型可以为任意类型)的有序集合
> * 数组也是对象。数组元素相当于对象的成员变量
> * 数组长度的确定的不可变的。如果越界则报ArraylndexOutofBounds
## 数组的使用
数组一般可以配合循环来使用。
```java
public class ArrayDemo {
public static void main(String[] args) {
int[] arrays = {1,2,3,4,5};
//打印全部的数组元素
for (int i = 0; i < arrays.length; i++) {
System.out.println(arrays[i]);
}
//打印所有元素的和
int sum = 0;
for (int i = 0; i < arrays.length; ++i) {
sum += arrays[i];
}
System.out.println("sum="+sum);
//查找最大最小元素
int max = array[0];//创建变量,存储遍历数组时发现的最大值,
//初始值赋为数组中第一个元素而不赋为0是为了避免数组中没有比0大的值这样就会输出错误的值
int min = array[0]
for (int i = 1; i < arrays.length; i++) {
max = max > arrays[i] ? max : arrays[i];
min = min < arrays[i] ? min : arrays[i]
}
System.out.println("max="+max+"\nmin="+"min");
}
}
```
### for-each循环(增强型for循环)
```java
public class ArrayDemo {
public static void main(String[] args) {
int[] arrays = {1,2,3,4,5};
for(int array : arrays){ //这种方式没有下标,适合打印输出
System.out.println(array);
}
}
}
```
### 数组作方法入参和作返回值
```java
public class ArrayDemo {
public static void main(String[] args) {
int[] arrays = {1,2,3,4,5};
int[] arraysReverse = reverse(arrays);
printArray(arraysReverse);
}
//打印数组元素
public static void printArray(int[] arrays){ //数组作为方法的传入参数
for (int i = 0; i < arrays.length; i++){
System.out.print(arrays[i]+" ");
}
}
//反转数组
public static int[] reverse(int[] arrays){
int[] result = new int[arrays.length];
for (int i = 0; i < arrays.length; i++){
result[result.length-i-1] = arrays[i];
}
return result; //数组作为返回值
}
}
```
### 数组的常用算法
####冒泡排序
![冒泡排序](https://i.loli.net/2020/05/24/QoRPrY2VfzcnhwE.gif)
如果遇到相等的值不进行交换,那这种排序方式是稳定的排序方式。
原理:比较两个相邻的元素,将值大的元素交换到右边
思路:依次比较相邻的两个数,将比较小的数放在前面,比较大的数放在后面。
算法分析:
N个数字要排序完成总共进行N-1趟排序每i趟的排序次数为(N-i)次,所以可以用双重循环语句,外层控制循环多少趟,内层控制每一趟的循环次数
冒泡排序的优点:每进行一趟排序,就会少比较一次,因为每进行一趟排序都会找出一个较大值。如上例:第一趟比较之后,排在最后的一个数一定是最大的一个数,第二趟排序的时候,只需要比较除了最后一个数以外的其他的数,同样也能找出一个最大的数排在参与第二趟比较的数后面,第三趟比较的时候,只需要比较除了最后两个数以外的其他的数,以此类推……也就是说,没进行一趟比较,每一趟少比较一次,一定程度上减少了算法的量。
时间复杂度
1.如果我们的数据正序只需要走一趟即可完成排序。所需的比较次数C和记录移动次数M均达到最小值$C_{min}=n-1$$M_{min}=0$;所以冒泡排序最好的时间复杂度为O(n)。
2.如果很不幸我们的数据是反序的则需要进行n-1趟排序。每趟排序要进行n-i次比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值:
$$C_{max} =\cfrac{n(n-1)}{2} = O(n^2)$$
$M_{max} =\cfrac{3n(n-1)}{2} = O(n^2)$
综上所述:冒泡排序总的平均时间复杂度为:$O(n^2)$ ,时间复杂度和数据状况无关。
> Java代码实现
>
> ```java
> //冒泡排序
> public static int[] bubbleSort(int[] array){
> int temp = 0;
> //外层循环控制比较的轮次 : length-1轮
> for (int i = 0; i < array.length-1; i++){
> //内层循环控制每轮比较的次数
> //第i轮i从0开始计算比较次数为length-i-1
> for (int j = 0; j < array.length-1-i; j++) {
> if (array[j+1]<array[j]){
> temp = array[j];
> array[j] = array[j+1];
> array[j+1] = temp;
> }
> }
> }
> return array;
> }
> ```
#### 二分查找(折半查找)
**概述**
二分查找也称折半查找Binary Search它是一种效率较高的查找方法。但是二分查找要求数组数据必须采用顺序存储结构有序排列。
**原理 **
首先,假设数组中元素是按升序排列,将数组中间位置的数据与查找数据比较,如果两者相等,则查找成功;否则利用中间位置记录将数组分成前、后两个子数组,如果中间位置数据大于查找数据,则进一步查找前子数组,否则进一步查 找后子数组。
重复以上过程,直到找到满足条件的数据,则表示查找成功, 直到子数组不存在为止,表示查找不成功。
> Java代码实现
>
> ```java
> public class Demo5 {
>
> /**
> * 二分查找(折半查找)
> */
> public static void main(String[] args) {
> //定义一个有序数组
> int[] nums = {10,20,30,40,50,60,70,80,90};
> //要查找的数据
> int num = 20;
> bubbleSort(nums,num);
>
> System.out.println("位置:"+centerIndex);
>
> }
> }
> public int binarySearch(int[] arrayint num){
> //1. 最小范围下标
> int minIndex = 0;
> //2. 最大范围下标
> int maxIndex = array.length-1;
> //3. 中间数据下标
> int centerIndex = (minIndex+maxIndex)/2;
> while(true) {
> System.out.println("循环了一次");
> if(array[centerIndex]>num) {
> //中间数据较大
> maxIndex = centerIndex-1;
> }else if(array[centerIndex]<num) {
> //中间数据较小
> minIndex = centerIndex+1;
> }else {
> //找到了数据 数据位置centerIndex
> break;
> }
>
> if(minIndex > maxIndex) {
> centerIndex = -1;
> break;
> }
> //当边界发生变化, 需要更新中间下标
> centerIndex = (minIndex+maxIndex)/2;
> }
> return centerIndex;
> }
> ```
####
## 多维数组
多维数组可以看成是数组的数组,比如二维数组就是一个特殊的一维数组,其每一个元素都是一个一维数组。
### 二维数组
```java
int[][] a = new int[2][5] //二维数组a可以看成一个两行5列的数组
```
![](https://i.loli.net/2020/05/24/qOGaBP61eo5mVdU.png)
```java
public class ArrayDemo {
public static void main(String[] args) {
int[][] arrays= {{1,2},{2,3},{3,4},{4,5}};
//将数组遍历打印出来
for (int i = 0; i < arrays.length; i++){
for (int j = 0; j < arrays[i].length; j++){
System.out.print(arrays[i][j]+" ");
}
}
}
```
## Java的Arrays类
数组的工具类`java.util.Arrays`
由于数组对象本身并没有什么方法可以供我们调用但API中提供了一个工具类Arrays供我们使用从而可以对数据对象进行一些基本的操作。
Arrays类中的方法都是static修饰的静态方法在使用的时候可以直接使用类名进行调用而“不用”使用对象来调用注意是“不用”而不是“不能”
Arrays类具有以下**常用**功能
* 给数组赋值通过fill方法。
* 对数组排序:通过 sort 方法,按升序。
* 比较数组通过equals 方法比较数组中元素值是否相等。
* 查找数组元素通过binarySearch 方法能对排序好的数组进行二分查找法操作。
具体使用可参考[JDK文档](https://www.matools.com/api/java8)
## 稀疏数组
当一个数组中大部分元素为0或者为同一值的数组时可以使用**稀疏数组**来保存该数组。
稀疏数组的处理方式是:
* 记录数组一共有几行几列,有多少个不同值
* 把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模
> 如下图:左边是原始数组,右边是稀疏数组
>
> ![](https://i.loli.net/2020/05/24/4HsMGcvahmbUk72.png)
>
> | | 行 | 列 | 值 | 含义 |
> | :--: | :--: | :--: | :--: | :-------------------------------------: |
> | [0] | 6 | 7 | 8 | 表示这是一个6行7列的数组有效值有 8 个 |
> | [1] | 0 | 3 | 22 | 第一个有效值位于第0行第3列值为22 |
> | [2] | 0 | 6 | 15 | 第二个有效值位于第0行第6列值为15 |
> | [3] | 1 | 1 | 11 | 第三个有效值位于第1行第1列值为11 |
> | [4] | 1 | 5 | 17 | 第四个有效值位于第1行第5列值为17 |
> | [5] | 2 | 3 | -6 | 第五个有效值位于第2行第3列值为-6 |
> | [6] | 3 | 5 | 39 | 第六个有效值位于第3行第5列值为39 |
> | [7] | 4 | 0 | 91 | 第七个有效值位于第4行第0列值为91 |
> | [8] | 5 | 2 | 28 | 第八个有效值位于第5行第2列值为28 |
```java
/**** 转为稀疏数组 ****/
public static int[][] toSparseArray(int[][] arr){
//获取有效值的个数
int sum = 0;
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
if (arr[i][j] != 0) {
sum++;
}
}
}
//新建一个稀疏数组
int[][] arr2 = new int[sum+1][3];
arr2[0][0] = arr.length;
arr2[0][1] = arr[0].length;
arr2[0][2] = sum;
//遍历arr,将非零的值的信息,存放入稀疏数组中
int count = 0;
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
if(arr[i][j] != 0){
count++;
arr2[count][0] = i;
arr2[count][1] = j;
arr2[count][2] = arr[i][j];
}
}
}
return arr2;
}
/**** 稀疏数组转为普通数组 ****/
public static int[][] sparseToArray(int[][] arr){
//读取稀疏数组,并新建一个数组
int[][] arr2 = new int[arr[0][0]][arr[0][1]];
//还原稀疏数组
for (int i = 1; i < arr.length; i++) {
arr2[arr[i][0]][arr[i][1]] = arr[i][2];
}
return arr2;
}
```