✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉
🍎个人主页:Leo的博客
💞当前专栏: Java从入门到精通
✨特色专栏: MySQL学习
🥭本文内容:Java基础篇 | 数组
🖥️个人小站 :个人博客,欢迎大家访问
📚个人知识库: 知识库,欢迎大家访问
大家好,我是Leo🫣🫣🫣,最近在复习Java基础内容,这个专栏后续也会一直更新下去,Java基础乃是咱
们Java的根基,俗话说,基础不牢,地动山摇。今天我们主要学习Java数组相关知识,话不多说,
让我们开始吧😎😎😎。
在开始之前,先通过一张图来了解一下我们今天要学习的数组的主要大纲吧
1. 数组的概述
1.1 为什么需要数组
我们下面先看一个简单的例子
一个养鸡场有 6 只鸡,它们的体重分别是 3kg,5kg,1kg,3.4kg,2kg,50kg 。请问这六只鸡的总体重是多少?平 均体重是多少? 请你编一个程序。
思路: 定义 6个变量, 加起来 总体重, 求出平均体重 引出 -> 数组
package com.Leo.array.Leo01.exer;
/**
* @author : Leo
* @version 1.0
* @date 2023/9/24/024 11:06
* @description : 数组练习3
*
* 一个养鸡场有 6 只鸡,它们的体重分别是 3kg,5kg,1kg,3.4kg,2kg,50kg 。
* 请问这六只鸡的总体重是多少?平均体重是多少? 请你编一个程序。
*/
public class ArrayExer03
{
public static void main(String[] args)
{
// 1. 使用for循环来计算以下的平均体重/ double hen1 = 3;
// double hen2 = 5;
// double hen3 = 1;
// double hen4 = 3.4;
// double hen5 = 2;
// double hen6 = 50;
// double totalWeight = hen1 + hen2 + hen3 + hen4 + hen5 + hen6;
// double avgWeight = totalWeight / 6;
// System.out.println("总体重=" + totalWeight
// + "平均体重=" + avgWeight);
//比如,我们可以用数组来解决上一个问题 => 体验
//1. double[] 表示 是 double 类型的数组, 数组名 hens
//2. {3, 5, 1, 3.4, 2, 50} 表示数组的值/元素,依次表示数组的
double[] hens = {3, 5, 1, 3.4, 2, 50, 7.8, 88.8,1.1,5.6,100};
//遍历数组得到数组的所有元素的和, 使用 for循环来得到
//1. 我们可以通过 hens[下标] 来访问数组的元素
// 下标是从 0 开始编号的比如第一个元素就是 hens[0]
// 第 2 个元素就是 hens[1] , 依次类推
//2. 通过 for 就可以循环的访问 数组的元素/值
//3. 使用一个变量 totalWeight 将各个元素累积
System.out.println("===使用数组解决===");
double totalWeight = 0;
for( int i = 0; i < hens.length; i++) {
totalWeight += hens[i];
}
System.out.println("总体重=" + totalWeight + "平均体重=" + (totalWeight / hens.length) );
}
}
1.2 什么是数组
在计算机科学中,阵列资料结构(英语:array data structure),简称数组(英语:Array),是由相同类型的元素(element)的集合所组成的 资料结构,分配一块连续的内存来存储。利用元素的索引(index)可以计算出该元素对应的储存地址。
最简单的资料结构类型是 一维阵列。例如,索引为0到9的32位元整数阵列,可作为在记忆体位址2000,2004,2008,…2036中,储存10个变量,因此索引为i的元素即在记忆体中的2000+4×i位址。阵列第一个元素的记忆体位址称为第一位址或基础位址。
简单来说数组的定义:
数组(array)是一种最简单的复合数据类型,它是有序数据的集合,数组中的每个元素具有相同的数据类型,可以用一个统一的数组名和不同的下标来确定数组中唯一的元素。根据数组的维度,可以将其分为一维数组、二维数组和多维数组等。
1.4 简述数组特性
数组的特点:
- 数组是一组数据的集合。
- 数组作为一种引用类型。
- 长度固定:Array 的长度在创建时就已经确定,并且不能被修改,但是可以指向其他数组。
- 同类型元素:Array 中的所有元素必须是同一种类型(对象类型存储的是引用)。
- 内存连续:Array 存储在连续的内存位置。
- 下标从 0 开始:Array 中的元素是通过下标来访问的,下标从 0 开始,最大下标为长度减 1,如果数组有 n 个元素,那么数组的索引是从 0 到(n-1)。
- 给定数组下标访问下标对应的元素时,的时间复杂度为 1。
- Array 是 Java 中的对象,因此可以使用对象属性
lenght
获取到 Array 的长度。 - Java 中 Array 都实现了
Cloneable
和java.io.Serializable
接口。 - 数组可以是一维数组、二维数组或多维数组。
- 数值数组元素的默认值为 0,而引用元素的默认值为 null。
- 数组元素可以是任何类型,包括数组类型。
- 数组类型是从抽象基类 Array 派生的引用类型。
数组的分类:
- 按照维度:一维数组、二维数组、三维数组、…
- 按照元素的数据类型分:基本数据类型元素的数组、引用数据类型元素的数组(即对象数组)
Java中数组是一种用来存储多个数据项的数据结构。数组可以存储基本类型和对象类型的数据,使用时需要先声明数组类型以及数组长度,如下所示
2. 数组的使用
2.1 数组的声明
数组的定义
数据类型[] 数组名 new 数据类型[大小]
int[] a = new int[5];
创建了一个数组,名字为a ,存放了5个int
说明:这是定义数组的一种方法。为了让大家明白,我画了一个数组内存图说明
2.2 数组的创建
1. 使用方式一:动态初始化
先声明数组
语法:数据类型 数组名[];
也可以 数据类型[] 数组名; int a[]; 或者 int[] a;
创建数组
语法: 数组名=new 数据类型[大小];
a = new int[5];
2. 使用方式二:静态初始化
初始化数组
语法: 数据类型[] 数组名 = {元素值,元素值, …}
int[] arr = {2,5,7,8,9,10};q
2.3 数组的使用和注意事项
-
数组是多个相同类型数据的组合,实现对这些数据的统一管理。
-
数组中的元素可以是任何数据类型,包括基本类型和引用类型,但是不能混用。
-
数组创建后,如果没有赋值,有默认值
int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000,boolean false,String null
-
使用数组的步骤 1. 声明数组并开辟空间 2 给数组各个元素赋值 3 使用数组
-
数组的下标是从 0 开始的。
-
数组下标必须在指定范围内使用,否则报:下标越界异常,比如
int [] arr=new int[5]; 则有效下标为 0-4
- 数组属引用类型,数组型数据是对象(object)
4. 数组使用案例
4.1 案例1
创建一个 char 类型的 26 个元素的数组,分别 放置’A’-‘Z’。使用 for 循环访问所有元素并打印出来。提示:char 类型 数据运算 ‘A’+2 -> ‘C’
package com.Leo.array.Leo01.exer;
/**
* @author : Leo
* @version 1.0
* @date 2023/9/24/024 11:06
* @description : 数组案例1
*
* 创建一个 char 类型的 26 个元素的数组,分别 放置'A'-'Z'。
* 使用 for 循环访问所有元素并打印出来。
* 提示:char 类型数据运算 'A'+1 -> 'B'
*/
public class ArrayCase01
{
public static void main(String[] args)
{
char[] chars = new char[26];
for (int i = 0; i <chars.length; i++) {
chars[i] = (char) ('A' + i);
System.out.print(chars[i] + " ");
}
System.out.println("初始化的字符数组: " + new String(chars));
}
}
4.2 案例2
请求出一个数组 int[]的最大值 {4,-1,9, 10,23},并得到对应的下标。
package com.Leo.array.Leo01.exer;
/**
* @author : Leo
* @version 1.0
* @date 2023/9/24/024 11:06
* @description : 数组案例2
*
*
* 请求出一个数组 int[]的最大值 {4,-1,9, 10,23},并得到对应的下标。
*/
public class ArrayCase02
{
// 1.假定 max = arr[0] 是最大值 , maxIndex=0;
// 2.从下标 1 开始遍历 arr, 如果 max < 当前元素,说明 max 不是真正的
// 最大值, 我们就 max=当前元素; maxIndex=当前元素下标
// 3. 当我们遍历这个数组 arr 后 , max 就是真正的最大值,maxIndex 最大值
public static void main(String[] args)
{
int[] array = {4,-1,9, 10,23};
//假定第一个元素就是最大值
int max = array[0];
int maxIndex = 0;
for (int i = 0; i < array.length; i++)
{
if (max < array[i])
{
max = array[i];
maxIndex = i;
}
}
System.out.println("数组最大值为: " + max + " , "+"最大值下标为:" + maxIndex);
}
}
5. 数组进阶
5.1 数组的赋值机制
-
基本数据类型赋值,这个值就是具体的数据,而且相互不影响。
int n1 = 2; int n2 = n1;
-
数组在默认情况下是引用传递,赋的值是地址。
看一个案例,并分析数组赋值的内存图(重点,难点. )。
//代码ArrayAssign.java
int[] arr1 = {1,2,3};
int[] arr2 = arr1;
用韩顺平老师的图来讲解以下。
这里的arr1和arr2相当于栈,而数组中的数据{1,2,3}就相当于与内存中的堆,arr1[]是指向{1,2,3}这个堆的,所以可以访问,当把arr1赋值给arr2时,arr2就具有了和arr1一样的访问权,因此对arr2的数据更改,会影响到arr1的数据。
如果还有人听不懂,那我就再举个很简单的例子,我们都玩过“我的世界”这款游戏。比如你和你的朋友一起玩,你的朋友做了一把木剑,放在了他自己的背包里,他又做了一个相同的木剑给了你,你们都有了木剑(这就相当于简单地值拷贝)。现在有一个箱子,你朋友做了一把木剑放在了箱子里,你觉得木剑太垃圾了,把木剑换成了钻石剑放在了箱子里,你朋友打开箱子发现木剑变成了钻石剑(这就是引用赋值)
我想到这里应该讲的也挺清楚的了,数组的赋值引用,变得是地址罢了。希望能够帮助到小伙伴们!!
5.2 数组拷贝
编写代码实现数组拷贝(内容复制) ArrayCopy.java
将int[] arr1 = {10,20,30};拷贝到arr2数组,要求数据空间是独立的.
package com.Leo.array.Leo01.exer;
/**
* @author : Leo
* @version 1.0
* @date 2023/10/9/009 14:21
* @description :
*/
public class ArrayCopy {
public static void main(String[] args) {
//将 int[] arr1 = {10,20,30};拷贝到 arr2数组,//要求数据空间是独立的.
int[] arr1 = {10,20,30};
//创建一个新的数组 arr2,开辟新的数据空间 大小 arr1.length;
int[] arr2 = new int[arr1.length];
//遍历 arr1,把每个元素拷贝到 arr2对应的元素位置
for(int i = 0; i < arr1.length; i++) {
arr2[i] = arr1[i];
}
//修改 arr2,不会对 arr1有影响
arr2[0] = 100;
//输出 arr1
System.out.println("====arr1的元素====");
for(int i = 0; i < arr1.length; i++) {
System.out.println(arr1[i]);
}
System.out.println("====arr2的元素====");
for(int i = 0; i < arr2.length; i++) {
System.out.println(arr2[i]);
}
}
}
结果:
5.3 数组反转
要求:把数组的元素内容反转。ArrayReverse01.java arr {11,22,33,44,55,66}{66, 55,44,33,22,11}
1. 方式1:通过找规律反转
package com.Leo.array.Leo01;
import java.util.Arrays;
/**
* @author : Leo
* @version 1.0
* @date 2023/9/24/024 11:06
* @description : 数组案例2 数组反转
*
*
* 要求: 把数组的元素内容反转。
* arr {11,22,33,44,55,66} {66, 55,44,33,22,11}
*/
public class ArrayReverse01
{
public static void main(String[] args)
{
// 把数组的元素内容反转
// 使用交换的方式
int[] arr1 = {11, 22, 33, 44, 55, 66};
int temp;
int len = arr1.length;
// 先遍历所有的元素
for (int i = 0; i < len/2; i++)
{
// 模拟交换数组中的两个元素
temp = arr1[i];
arr1[i] = arr1[len-1-i];
arr1[len - i -1] = temp;
}
// 打印新数组
System.out.println("反转后的数组: "+Arrays.toString(arr1));
}
}
2. 方式2:使用逆序赋值方式 ArrayReverse02.java
思路分析:
//使用逆序赋值方式
//1.先创建一个新的数组arr2 ,大小arr.length
//2.逆序遍历arr ,将每个元素拷贝到arr2的元素中(顺序拷贝)
//3.建议增加一个循环变量j -> 0 -> 5
int[] arr2 = new int[arr.length];
//逆序遍历arr
//4.当for循环结束,arr2就是一个逆序的数组{66, 55, 44,33, 22, 11}
//5.让arr指向arr2数据空间,此时arr原来的数据空间就没有变量引用
//会被当做垃圾,销毁
arr = arr2;
// 第二种方式:
int[] arr = {11, 22, 33, 44, 55, 66};
int[] arrReverse = new int[arr.length];
for (int i = arr.length -1, j = 0; i >= 0; i--, j++)
{
arrReverse[j] = arr[i];
}
// 对arrReverse进行销毁
// arr = arrReverse;
System.out.println("反转之后的数组2:" + Arrays.toString(arrReverse));
System.out.println("===========================================");
// 第二种方式: 创建一个新的数组并上移
int[] arrReverseNew = new int[arr.length];
for (int i = 0, j = arr.length -1; i < arr.length; i++, j--)
{
arrReverseNew[j] = arr[i];
}
System.out.println("反转之后的数组: "+Arrays.toString(arrReverseNew));
System.out.println("=============================================");
3. 方式3: 创建一个新的数组并下移
// 第三种方式 - 创建一个新的数组并下移
int[] arrReverseNew2 = new int[arr.length];
for (int i = arr.length -1, j = 0; i >= 0; i--, j++)
{
arrReverseNew2[j] = arr[i];
}
System.out.println("反转之后的数组: "+Arrays.toString(arrReverseNew2));
// 第四种方式: 要求在下移之后还原数组元素的相对顺序
int[] arrReverseNew3 = new int[arr.length];
int left = 0;
int right = arr.length -1;
while (left < right)
{
arrReverseNew3[left] = arr[right];
left++;
right--;
}
System.out.println("=============================================");
4. 要求在下移之后还原数组元素的相对顺序
// 第四种方式: 要求在下移之后还原数组元素的相对顺序
int[] arrReverseNew3 = new int[arr.length];
int left = 0;
int right = arr.length -1;
while (left < right)
{
arrReverseNew3[left] = arr[right];
left++;
right--;
}
System.out.println("=============================================");
5.4 数组的扩容机制
1. 介绍
在Java中,数组是一种固定长度的数据结构,一旦创建后,其长度无法改变。然而,在实际开发中,经常会遇到需要动态扩容数组的情况。为了解决这个问题,Java提供了一种机制来实现数组的动态扩容,即通过创建一个更大的新数组,然后将原有数组的元素复制到新数组中。
本文将介绍Java数组扩容的机制及其具体实现步骤。
2. 实现步骤
步骤 | 描述 |
---|---|
1 | 创建一个新的数组 |
2 | 将原有数组的元素复制到新数组中 |
3 | 更新引用,将新数组赋值给原有数组 |
下面将分别介绍每个步骤需要做的具体操作。
2.1 创建一个新的数组
首先,我们需要创建一个新的数组,用于存放扩容后的元素。根据需要扩容的大小,我们可以使用new
关键字创建一个新的数组对象,并指定新数组的长度。例如:
int newSize = oldSize * 2; // 假设需要将数组扩容为原大小的两倍
int[] newArray = new int[newSize];
2.2 将原有数组的元素复制到新数组中
接下来,我们需要将原有数组的元素复制到新数组中。Java提供了System.arraycopy()
方法来进行数组复制操作。该方法需要传入源数组、源数组的起始位置、目标数组、目标数组的起始位置以及要复制的元素数量。代码示例如下:
System.arraycopy(oldArray, 0, newArray, 0, oldSize);
2.3 更新引用,将新数组赋值给原有数组
最后,我们需要更新引用,将新数组赋值给原有数组。这样,原有数组的引用就指向了新的扩容后的数组,从而完成了数组的扩容操作。代码示例如下:
oldArray = newArray;
3.示例代码1
下面是一个完整的示例代码,演示了如何实现Java数组的扩容机制:
public class ArrayResizeExample {
public static void main(String[] args) {
int[] oldArray = new int[5]; // 原始数组的大小为5
int oldSize = oldArray.length;
int newSize = oldSize * 2; // 扩容为原大小的两倍
int[] newArray = new int[newSize]; // 创建一个新的数组
System.arraycopy(oldArray, 0, newArray, 0, oldSize); // 复制原数组的元素到新数组中
oldArray = newArray; // 更新引用,将新数组赋值给原数组
System.out.println("原始数组大小:" + oldSize);
System.out.println("扩容后数组大小:" + oldArray.length);
}
}
运行以上代码,输出结果如下:
原始数组大小:5
扩容后数组大小:10
通过以上示例,我们可以看到原始数组的大小为5,扩容后数组的大小为10,成功实现了数组的扩容操作。
3.示例代码2
要求:实现动态的给数组添加元素效果,实现对数组扩容。ArrayAdd.java
- 原始数组使用静态分配int[] arr = {1,2,3}
- 增加的元素4,直接放在数组的最后arr = {1,2,3,4}
- 用户可以通过如下方法来决定是否继续添加,添加成功,是否继续?y/n
package com.Leo.array.Leo01.exer;
import java.util.Scanner;
/**
* @author : Leo
* @version 1.0
* @date 2023/10/09/024 11:06
* @description : 数组拷贝
*
*/
public class ArrayAdd02 {
//编写一个main方法
public static void main(String[] args) {
/*
要求:实现动态的给数组添加元素效果,实现对数组扩容。
1.原始数组使用静态分配 int[] arr = {1,2,3}
2.增加的元素4,直接放在数组的最后 arr = {1,2,3,4}
3.用户可以通过如下方法来决定是否继续添加,添加成功,是否继续?y/n
思路分析
1. 定义初始数组 int[] arr = {1,2,3}//下标0-2
2. 定义一个新的数组 int[] arrNew = new int[arr.length+1];
3. 遍历 arr 数组,依次将arr的元素拷贝到 arrNew数组
4. 将 4 赋给 arrNew[arrNew.length - 1] = 4;把4赋给arrNew最后一个元素
5. 让 arr 指向 arrNew ; arr = arrNew; 那么 原来arr数组就被销毁
6. 创建一个 Scanner可以接受用户输入
7. 因为用户什么时候退出,不确定,使用 do-while + break来控制
*/
Scanner myScanner = new Scanner(System.in);
//初始化数组
int[] arr = {1,2,3};
do {
int[] arrNew = new int[arr.length + 1];
//遍历 arr 数组,依次将arr的元素拷贝到 arrNew数组
for(int i = 0; i < arr.length; i++) {
arrNew[i] = arr[i];
}
System.out.println("请输入你要添加的元素");
int addNum = myScanner.nextInt();
//把addNum赋给arrNew最后一个元素
arrNew[arrNew.length - 1] = addNum;
//让 arr 指向 arrNew,
arr = arrNew;
//输出arr 看看效果
System.out.println("====arr扩容后元素情况====");
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
//问用户是否继续
System.out.println("是否继续添加 y/n");
char key = myScanner.next().charAt(0);
if( key == 'n') {
break;
}
}while(true);
System.out.println("你退出了添加...");
}
}
6. 数组的排序与查找
6.1 排序的介绍
排序是将多个数据,依指定的顺序进行排列的过程。
1. 定义
排序算法(英语:Sorting algorithm)是一种将一组特定的数据按某种顺序进行排列的算法。排序算法多种多样,性质也大多不同。
2.稳定性
稳定性是指相等的元素经过排序之后相对顺序是否发生了改变。
基数排序、计数排序、插入排序、冒泡排序、归并排序是稳定排序。
选择排序、堆排序、快速排序、希尔排序不是稳定排序。
3.时间复杂度
以下是几种排序算法的比较。
4.空间复杂度
与时间复杂度类似,空间复杂度用来描述算法空间消耗的规模。一般来说,空间复杂度越小,算法越好。
1. 内部排序
指将需要处理的所有数据都加载到内部存储器中进行排序。包括(交换式排序法、选择式排序法和插入式排序法)。
2. 外部排序
数据量过大,无法全部加载到内存中,需要借助外部存储进行排序。包括(合并排序法和直接合并排序法)。
6.2 冒泡排序
假设有 5 个数字 3,1,6,2,5 在一个 int 数组中,要求按从小到大排序输出
如何采用冒泡排序算法呢?
冒泡排序的算法是这样的,首先从数组的最左边开始,取出第 0 号位置(左边)的数据和第 1
号位置(右边)的数据,如果左边的数据大于右边的数据,则进行交换,否而不进行交换。接 下来右移一个位置,取出第 1 个位置的数据和第 2 个位置的数据,进行比较,如果左边的数据 大于右边的数据,则进行交换,否而不进行交换。沿着这个算法一直排序下去,最大的数就会 冒出水面,这就是冒泡排序。
以上示例排序过程如下:
下面以数列{20,40,30,10,60,50}为例,演示它的冒泡排序过程(如下图)。
我们先分析第1趟排序
- 当i=5,j=0时,a[0]<a[1]。此时,不做任何处理!
- 当i=5,j=1时,a[1]>a[2]。此时,交换a[1]和a[2]的值;交换之后,a[1]=30,a[2]=40。
- 当i=5,j=2时,a[2]>a[3]。此时,交换a[2]和a[3]的值;交换之后,a[2]=10,a[3]=40。
- 当i=5,j=3时,a[3]<a[4]。此时,不做任何处理!
- 当i=5,j=4时,a[4]>a[5]。此时,交换a[4]和a[5]的值;交换之后,a[4]=50,a[3]=60。
于是,第1趟排序完之后,数列{20,40,30,10,60,50}变成了{20,30,10,40,50,60}。此时,数列末尾的值最大。
根据这种方法:
- 第2趟排序完之后,数列中a[5…6]是有序的。
- 第3趟排序完之后,数列中a[4…6]是有序的。
- 第4趟排序完之后,数列中a[3…6]是有序的。
- 第5趟排序完之后,数列中a[1…6]是有序的。整个数列也就是有序的了。
冒泡排序时间复杂度
冒泡排序的时间复杂度是O(N2)。 假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),需要遍历多少次呢? N-1次!因此,冒泡排序的时间复杂度是O(N2)。
冒泡排序稳定性
冒泡排序是稳定的算法,它满足稳定算法的定义。 算法稳定性 – 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的!
代码示例
package com.Leo.array.Leo01.sort;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
/**
* @author : Leo
* @version 1.0
* @date 2023/9/26/026 11:34
* @description : 冒泡排序
*/
public class ArrayBubbleSort {
/**
* 用于测试: 冒泡排序基本写法
*/
@Test
public void testBubbleSort01() {
int[] arr = {73,88,41, 23, 93, 14, 25, 3, 1, 10, 4, 2 };
int temp = 0;
int len = arr.length - 1;
// 外层循环负责总轮数
for (int i = 0; i < len; i++) {
// 内层循环负责每轮的第(i+1)次处理
for (int j = 0; j < len - i; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
// 循环打印每一轮的数量
System.out.println("\n第"+ (i+1)+"轮处理的数组: ");
for (int j = 0; j <arr.length; j++) {
System.out.print(arr[j] + "\t");
}
}
System.out.println();
System.out.println("新序列: " + Arrays.toString(arr));
}
/**
* 用于测试: 冒泡排序第二种写法
*
原始数据:
3,2,7,6,8
第1次循环:(最大的跑到最右边)
2,3,7,6,8 (3和2比较,2<3,所以2和3交换位置)
2,3,7,6,8 (虽然不需要交换位匿:但是3和7还是需要比较一次。)
2,3,6,7,8 (76交换位匿)
2,3,6,7,8 (虽然不需要交换位置:但是3机还是需要比较-次。)
经过第1次循环,此时菊剩下参与比较的数据:2,3,6,7进行第2次循环:
2,3,6,7 (2和3比较,不需要交换位置)
2,3,6,7 (36比较, 不需要交换位置)
2,3,6,7 (67比较, 不需要交换位置)
经过第2次循环,此时剩下参与比较的数据:2,3,6进行第3次循环:
2,3,6(2和3比较,不需要交换位置)
2,3,6(36比较,不需要交换位置)
经过第3次循环,此时剩下参与比较的数据:2,3进行第4次循环:
2,3(2和3比较,不需要交换位置)
*/
@Test
public void testBubbleSort02()
{
int[] arr = {6, 8,3,2,7, 23, 20};
int temp = 0;
int len = arr.length - 1;
// 外层循环负责总轮数
for (int i = len; i >0; i--) {
// 内层循环负责每轮的第(i+1)次处理
for (int j = 0; j < i; j++) {
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
// 循环打印每一轮的数量
System.out.println("\n第"+ (i)+"轮处理的数组: ");
for (int j = 0; j <arr.length; j++) {
System.out.print(arr[j] + "\t");
}
}
System.out.println();
System.out.println("新序列: " + Arrays.toString(arr));
}
}
6.3 选择排序
选择排序对冒泡排序进行了改进,使交换次数减少,但比较次数仍然没有减少。
假设有 5 个数字 3,1,6,2,5 在一个 int 数组中,要求按从小到大排序输出
采用选择排序,选择排序是这样的,先从左端开始,找到下标为 0 的元素,然后和后面的元素 依次比较,如果找到了比下标 0 小的元素,那么再使用此元素,再接着依次比较,直到比较完 成所有的元素,最后把最小的和第 0 个位置交换。
以上示例排序过程如下:
下面以数列{20,40,30,10,60,50}为例,演示它的选择排序过程(如下图)。
排序流程
- 第1趟: i=0。找出a[1…5]中的最小值a[3]=10,然后将a[0]和a[3]互换。 数列变化: 20,40,30,10,60,50 – > 10,40,30,20,60,50
- 第2趟: i=1。找出a[2…5]中的最小值a[3]=20,然后将a[1]和a[3]互换。 数列变化: 10,40,30,20,60,50 – > 10,20,30,40,60,50
- 第3趟: i=2。找出a[3…5]中的最小值,由于该最小值大于a[2],该趟不做任何处理。
- 第4趟: i=3。找出a[4…5]中的最小值,由于该最小值大于a[3],该趟不做任何处理。
- 第5趟: i=4。交换a[4]和a[5]的数据。 数列变化: 10,20,30,40,60,50 – > 10,20,30,40,50,60
空间复杂度
选择排序的时间复杂度是O(N2)。
假设被排序的数列中有N个数。遍历一趟的时间复杂度是O(N),需要遍历多少次呢? N-1!因此,选择排序的时间复杂度是O(N2)
稳定性
- 回顾:什么是排序算法的稳定性?
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
- 数组实现和链表实现的差异
用数组实现的选择排序是不稳定的,用链表实现的选择排序是稳定的。
不过,一般提到排序算法时,大家往往会默认是数组实现,所以选择排序是不稳定的。
- 此外,排序算法的稳定性也是可以改变的,只是需要额外的时间和空间
代码示例
package com.Leo.array.Leo01.sort;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
/**
* @author : Leo
* @version 1.0
* @date 2023/9/26/026 11:34
* @description : 选择排序
* <p>
* 选择排序比目泡排序的效率高。高在交换位置的次数上。
* 选择排序的交换位置是有意义的。
* 循环一次,然后找出参加比较的这堆数据中最小的,拿着这个最小的值和最前面的数据交换位置。
*/
public class ArraySelectSort {
/**
* 用于测试: 选择排序基本写法
*/
@Test
public void testSelectSort01() {
int[] arr = {73, 88, 41, 23, 93, 14, 25, 3,10, 4, 2,1};
int temp = 0;
for (int i = 0; i < arr.length - 1; i++) {
// 假设i是最小的
int min = i;
// i正好是参加比较的这堆数据中”最左边那个元奉的下标。
// 第二层循环指的是除了第一层的所有元素,最后剩下的元素中 最小的那个元素
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[min]) {
// 最小的是J ,并将其与后面所有元素进行交换
min = j;
}
if (min != i){
temp = arr[min];
arr[min] = arr[i];
arr[i] = temp;
}
}
}
System.out.println(Arrays.toString(arr));
}
}
6.4 二分法查找
二分法介绍
查找数组中的元素我们可以遍历数组中的所有元素,这种方式称为线性查找。线性查找适合与 小型数组,大型数组效率太低。如果一个数组已经排好序,那么我们可以采用效率比较高的二分查找或叫折半查找算法。
代码示例
package com.Leo.array.Leo01.sort;
/**
* @author : Leo
* @version 1.0
* @date 2023/10/9/009 15:02
* @description : 二分法查找
*/
public class BinarySearch {
public static void main(String[] args) {
int[] data = {11,12,13,14,15,16,17,18,19,20};
int index = binarySearch(data, 18);
System.out.println(index);
}
//采用折半法查询,必须建立在排序的基础上
private static int binarySearch(int[] data, int value) {
//开始下标
int beginPos = 0;
//结束下标
int endPos = data.length - 1;
while (beginPos <=endPos) {
int midPos = (beginPos + endPos)/2;
if (value == data[midPos]) {
return midPos;
}else if (value > data[midPos]) {
beginPos = midPos + 1;
}else if (value < data[midPos]) {
endPos = midPos - 1;
}
}
return -1;
}
}
7. 多维数组
多维数组我们只介绍二维数组。
二维数组的应用场景
比如我们开发一个五子棋游戏,棋盘就是需要二维数组来表示。如图:
7.1 二维数组的使用
1. 快速入门
TwoDimensionalArray01.java
请用二维数组输出如下图形
0 0 0 0 0 0
0 0 1 0 0 0
0 2 0 3 0 0
0 0 0 0 0 0
package com.Leo.array.Leo01.two;
/**
* @author : Leo
* @version 1.0
* @date 2023/10/9/009 15:06
* @description : 认识二维数组
*/
public class TwoDimensionalArray01 {
//编写一个main方法
public static void main(String[] args) {
/*
请用二维数组输出如下图形
0 0 0 0 0 0
0 0 1 0 0 0
0 2 0 3 0 0
0 0 0 0 0 0
*/
//什么是二维数组:
//1. 从定义形式上看 int[][]
//2. 可以这样理解,原来的一维数组的每个元素是一维数组, 就构成二维数组
int[][] arr = { {0, 0, 0, 0, 0, 0},
{0, 0, 1, 0, 0, 0},
{0,2, 0, 3, 0, 0},
{0, 0, 0, 0, 0, 0} };
//关于二维数组的关键概念
//(1)
System.out.println("二维数组的元素个数=" + arr.length);
//(2) 二维数组的每个元素是一维数组, 所以如果需要得到每个一维数组的值
// 还需要再次遍历
//(3) 如果我们要访问第 (i+1)个一维数组的第j+1个值 arr[i][j];
// 举例 访问 3, =》 他是第3个一维数组的第4个值 arr[2][3]
System.out.println("第3个一维数组的第4个值=" + arr[2][3]);
//输出二维图形
for(int i = 0; i < arr.length; i++) {
//遍历二维数组的每个元素
//遍历二维数组的每个元素(数组)
//1. arr[i] 表示 二维数组的第i+1个元素 比如arr[0]:二维数组的第一个元素
//2. arr[i].length 得到 对应的 每个一维数组的长度
for(int j = 0; j < arr[i].length; j++) {
//输出了一维数组
System.out.print(arr[i][j] + " ");
}
System.out.println();
}
}
}
7.2 二维数组的使用细节
-
一维数组的声明方式有:
int[] x或者int x[]
-
二维数组的声明方式有:
int[][] y或者int[] y[] 或者int y[][]
-
二维数组实际上是由多个一维数组组成的,它的各个一维数组的长度可以相同,也可以不相同。比如:map[][]是
一个二维数组
int map [][] = {{1,2},{3,4,5}}
由map[0]是一个含有两个元素的一维数组,map[1]是一个含有三个元素的一维数组构成,我们也称为列数不等
的二维数组。
8. 数组练习
1. 第一题
随机生成10个整数(1100的范围)保存到数组,并倒序打印以及求平均值、求最大值和最大值的下标、并查找里面是否有8
package com.Leo.array.Leo01.homework;
/**
* @author : Leo
* @version 1.0
* @date 2023/10/9/009 15:11
* @description :
*/
public class HomeWork01 {
//编写一个main方法
public static void main(String[] args) {
/*
随机生成10个整数(1_100的范围)保存到数组,
并倒序打印以及求平均值、求最大值和最大值的下标、
并查找里面是否有 8 Homework01.java
*/
int[] arr = new int[10];
//(int)(Math.random() * 100) + 1 生产 随机数 1-100
for(int i = 0; i < arr.length; i++) {
arr[i] = (int)(Math.random() * 100) + 1;
}
System.out.println("====arr的元素情况=====");
for(int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
System.out.println("\n====arr的元素情况(倒序)=====");
for(int i = arr.length -1; i >= 0; i--) {
System.out.print(arr[i] + "\t");
}
//平均值、求最大值和最大值的下标
//我们这里将需要一起完成
//
double sum = arr[0];
int max = arr[0];
int maxIndex = 0;
for(int i = 1; i < arr.length; i++ ) {
sum += arr[i];
//累积和
if( max < arr[i]) {
//说明max不是最大值,就变化
max = arr[i];
maxIndex = i;
}
}
System.out.println("\nmax=" + max + " maxIndex=" + maxIndex);
System.out.println("\n平均值=" + (sum / arr.length));
//查找数组中是否有8->使用顺序查找
int findNum = 8;
int index = -1;
//如果找到,就把下标记录到 index
for(int i = 0; i < arr.length; i++) {
if(findNum == arr[i]) {
System.out.println("找到数" + findNum + " 下标=" + i);
index = i;
break;
}
}
if(index == -1) {
System.out.println("没有找到数" + findNum );
}
}
}
9. 参考文章
- https://zh.wikipedia.org/wiki/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95
- https://blog.51cto.com/u_16175522/6812404
- https://developer.aliyun.com/article/1137886
- https://pdai.tech/md/algorithm/alg-sort-x-bubble.html#%E6%8E%92%E5%BA%8F-%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F-bubble-sort
- https://oi-wiki.org/basic/sort-intro/