一:数组的概述
什么是数组?
- 数组(Array),是多个相同类型数据按一定顺序排列的集合,并使用一个名字命名,并通过编号的方式对这些数据进行统一管理。
数组常见概念:数组名,下标(或索引),元素,数组的长度(元素的个数)。
数组的特点:
- 创建数组对象会在内存中开辟一整块连续的空间(在堆空间中),而数组名中引用的是这块连续空间的首地址。
- 有序排列的(下标是有序的),
- 使用下标访问指定位置上的元素。(数组的角标(或索引)是从0开始的,到 数组的长度-1 结束)。
- 数组的长度一旦确定,就不能修改。
- 数组本身是引用数据类型,而数组中的元素可以是任何数据类型。
- 在数据结构中属于顺序表结构。
数组的分类:
- 按照维度:一维数组、二维数组、三维数组 …
- 按元素的数据类型分:元素为基本数据类型的数组、元素为引用数据类型的数组(即对象数组)
数组是一个类吗?
- 数组是有对应的类,这个类是在JVM运行时创建的,所以没有对应的class 文件。
- 数组的类名是:
开头的,和普通类的不一样。[
- 数组类中不包含任何成员和变量(可以通过getClass拿到 Class 对象来查看),数组的长度length是通过JVM的指令 arraylength 直接得到的。
- 数组的类和一般类在JVM中是区分对待的,JVM会对数组类做一些特殊的操作,比如数组类的对象创建是通过JVM指令直接执行的,比如
创建一个数组对象,newarray
创建多个数组对象。multianewarray
- 数组类并不是只有一个类,而是会有很多个。数组类的类型是由数组的内容和维度同时决定的。比如:int[] 的类名是:
;int[][] 的类名是:[I
(其中的[[I
是 int 类型的在虚拟机指令中数据类型)。这是两个不同的类。I
二:数组的使用
一维数组的使用
- 创建数组:创建数组对象,赋给一个数组变量。
//数组类型变量的声明:数据类型[] int[] nums;//常用 int nums[]; //效果一样:变量名都是nums //初始化:也叫创建数组对象 new //静态初始化:数组的初始化和数组元素的赋值操作同时进行 nums = new int[]{1001,1002,1003,1004}; //自定义初始化数据,[]里不能有值,长度根据{}里元素的个数。 int[] arr = {1,2,3}; //类型推断 int[] a; a = {1,3,2};//编译不通过。类型推断不能这样用,类型推断必须和声明一起用。 //动态初始化:数组的初始化和数组元素的赋值操作分开进行 String[] names = new String[5];//有默认的初始化值。[]里的数一定要给的,表示长度,不然编译不通过。 /* 总结:数组一旦初始化完成后,其长度就确定了。 */
- 数组元素的默认初始化值:这是Java对于各个数据类型默认值的规范。Java语言中其他地方也这么用。
整型数组:0 浮点型:0.0 字符型:0 或 '\u0000' ,而非'0' 布尔型:false 引用数据类型:null
- 调用数组中的元素:根据索引取值,也可以根据索引赋值。
String[] names = new String[5]; //通过角标的方式调用 //数组的角标(或索引)是从0开始的,到 数组的长度-1 结束 String name = name[0];//取值 names[0] = “你好”;//赋值 names[6] = "n";//超出数组的长度,编译通过,但运行会报数组角标越界异常。
- 获取数组的长度:数组的属性:length
int[] names = new int[]{1001,1002,1003,1004}; //属性:length int l = names.length;
- 遍历数组:
int[] names = new int[]{1001,1002,1003,1004}; for(int i = 0; i < names.length; i++) { System.out.println(names[i]); }
- 数组的内存解析 数组中的数据最终都是(指向)方法区中的。
多维数组的使用
说明:
- Java 语言里提供了支持多维数组的语法
- 从数组底层的运行机制来看,其实没有多维数组
- 以二维数组为例:对于二维数组的理解,可以看成是一维数组 array1 又作为另一个一维数组 array2 的元素而存在。多维数组就是以这种方式衍生出来。其实,从数组底层的运行机制来看,其实没有多维数组。
使用(以二维数组为例):
- 声明和初始化的方式。(原理和一维数组无异,是由一维数组衍生出来的)
//声明 int[][] a1; int a2[][]; int[] a3[]; //初始化 //静态初始化 int[][] arr1 = new int[][]{{1,2,3},{4,5},{6,7,8}}; int[][] arr5 = {{1,2,3},{4,5},{6,7,8}};//类型推断,注意类型推断须和声明一起使用。 //动态初始化1 String[][] arr2 = new String[3][2];//使用此方式时各个维度的长度都已确定。 //动态初始化2 String[][] arr3 = new String[3][];//这里第二个[]不用指定长度是因为其作为数组中的元素有默认值null //String[][] arr3 = new String[][];//是错的,编译不通过
- 数组的默认初始化值
//规定,二维数组分外层元素和内层元素 //外层元素:arr[0],arr[1]等 //内层元素:arr[0][0],arr[1][2]等 针对初始化方式一:如:int[][] arr = new int[4][3]; 》外层元素:地址值 》内层元素:和一维数组一样 针对初始化方式二:如:int[][] arr = new int[4][]; 》外层元素:null 》内层元素:不能调用,否则空指针异常
int[][] arr = new int[4][3]; System.out.println(arr[0]);//[[email protected] (地址值:指向堆空间的数组对象) System.out.println(arr[0][0]);//0 (数组的默认初始化值) System.out.println(arr);//[[[email protected] (地址值)
- 数组的默认初始化值
- 调用数组的指定位置的元素,根据索引取值,也可以根据索引赋值。
int[][] arr1 = new int[][]{{1,2,3},{4,5},{6,7,8}}; String[][] arr2 = new String[3][2]; String[][] arr3 = new String[3][]; //取值 arr1[0][1]; //2 arr2[1][1]; //null,前面讲过的默认初始化值 arr3[1][0]; //空指针,arr3[1]是null,null的[0]:null没有索引,所以空指针。 //将其声明为一个数组类型,就不会报空指针: //arr3[1] = new String[4];//赋值 //arr3[1][0]; //null
- 获取数组长度
int[][] arr1 = new int[][]{{1,2,3},{4,5},{6,7,8}}; //使用数组的属性:length arr1.length; //3 arr1[1].length; //2
- 遍历
for(int i = 0; i < arr4.length; i++) { for(int j = 0; j < arr4[i].length; j++) { System.out.pritnln(arr4[i][j]); } }
- 数组的内存解析
例题
1、从键盘读入学生成绩,找出最高分,并输出学生成绩等级。
成绩>=最高分-10 等级为’A’
成绩>=最高分-20 等级为’B’
成绩>=最高分-30 等级为’C’
其余 等级为’D’
提示:先读入学生人数,根据人数创建int数组,存放学生成绩
import java.util.Scanner; public class Test { public static void main(String[] args) throws Exception { //1.使用Scanner,读取学生个数 Scanner scanner = new Scanner(System.in); System.out.print("请输入学生人数:"); int number = scanner.nextInt(); //2.创建数组,存储学生成绩:动态初始化 int[] scores = new int[number]; //3.给数组中的元素赋值 //4.获取数组中的元素的最大值:最高分 int maxScore = 0; System.out.println("请输入" + number + "个学生成绩:"); for (int i = 0; i < scores.length; i++) { scores[i] = scanner.nextInt(); if (maxScore < scores[i]) maxScore = scores[i]; } System.out.println("学生成绩的最高分是:" + maxScore); //5.根据每个学生成绩与最高分的差值,得到每个学生的等级,并输出等级和成绩 char level; for (int i = 0; i < scores.length; i++) { if (maxScore - scores[i] <= 10) { level = 'A'; } else if (maxScore - scores[i] <= 20) { level = 'B'; } else if (maxScore - scores[i] <= 30) { level = 'C'; } else { level = 'D'; } System.out.println("student" + i + "'s score is" + scores[i] + "and grade is " + level); } //关闭资源 scanner.close(); } }
//运行结果 请输入学生人数:5 请输入5个学生成绩: 62 30 92 85 78 学生成绩的最高分是:92 student0's score is62and grade is C student1's score is30and grade is D student2's score is92and grade is A student3's score is85and grade is A student4's score is78and grade is B
2、声明:int[] x,y[]; 在给x,y变量赋值以后,以下选项允许通过编译的是:
**a ) x[0] = y; ** no
**b) y[0] = x; **yes
**c) y[0][0] = x; **no。理解:y[0][0]是int型的,x是int[]型的
**d) x[0][0] = y; **no
**e) y[0][0] = x[0]; **yes
**f) x = y; **no
提示:
一维数组:int[] x 或者int x[]
二维数组:int[][] y 或者 int[] y[] 或者 int y[][]
三:数组涉及的常见算法
- 数组元素的赋值(杨辉三角、回形数等)
- 求数值型数组中元素的最大值、最小值、平均数、总和等
- 数组的复制、反转、查找(线性查找、二分法查找)
- 复制
int[] array1 = new int[]{2, 3, 5, 7, 11, 13, 17, 19}; //实现复制 int[] array3 = new int[array1.length]; for (int i = 0; i < array3.length; i++) { array3[i] = array1[i]; }
- 反转
String[] arr = new String[]{"jj", "aa", "bb", "cc"}; //数组的反转 for (int i = 0; i < arr.length / 2; i++) { String temp = arr[i]; arr[i] = arr[arr.length - i - 1]; arr[arr.length - i - 1] = temp; }
- 查找
- 线性查找:就简单的,同时效率也是最差的。
String[] arr = new String[]{"jj", "aa", "bb", "cc"}; //线性查找 String test = "bb"; for (int i = 0; i < arr.length / 2; i++) { if (test.equals(arr[i])) { System.out.println("找到了"); break; } }
- 常用的有:二分法查找等
- 线性查找:就简单的,同时效率也是最差的。
- 复制
- 数组元素的排序算法
例题
1、使用二维数组打印一个 10 行杨辉三角。
【提示】
1. 第一行有 1 个元素, 第 n 行有 n 个元素
2. 每一行的第一个元素和最后一个元素都是 1
3. 从第三行开始, 对于非第一个元素和最后一个元
素的元素。即:
yanghui[i][j] = yanghui[i-1][j-1] + yanghui[i-1][j];
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IApjrAf4-1625911129420)(C:\Users\17812\Desktop\笔记(work)]\typora_images\image-20210331223910731.png)
import java.util.Scanner; public class Test { public static void main(String[] args) throws Exception { //1.声明并初始化二维数组 int[][] yangHui = new int[10][]; //2.给数组的元素赋值,并输出 for (int i = 0; i < yangHui.length; i++) { yangHui[i] = new int[i + 1]; //给首末元素赋值 yangHui[i][0] = yangHui[i][i] = 1; //给每行的非首末元素赋值 for (int j = 1; j < yangHui[i].length - 1; j++) { yangHui[i][j] = yangHui[i - 1][j - 1] + yangHui[i - 1][j]; } //输出 for (int a = 0; a < yangHui[i].length; a++) { System.out.print(yangHui[i][a] + "\t"); } System.out.println(); } } }
2、定义一个int型的一维数组,包含10个元素,分别赋一些随机整数,
然后求出所有元素的最大值,最小值,和值,平均值,并输出出来。
要求:所有随机数都是两位数。
提示;
[0,1) * 90 -> [0,90) -> 10 -> [10,100) -> [10,99]
(int)(Math.random() * 90 + 10)
import java.math.*; public class Test { public static void main(String[] args) throws Exception { int[] arr = new int[10]; //最大、最小值 int maxValue = 0; int minValue = 0; //总和 int sum = 0; //平均数 double avgValue; for (int i = 0; i < arr.length; i++) { arr[i] = (int) (Math.random() * 90 + 10); //求数组元素的最大值、最小值 if (i == 0) { maxValue = arr[0];//更完美,int maxValue = 0; => 不适合有全为负数的情况 minValue = arr[0]; } else { if (maxValue < arr[i]) maxValue = arr[i]; if (minValue > arr[i]) minValue = arr[i]; } sum += arr[i];//求总和 System.out.print(arr[i] + "\t"); } System.out.println(); avgValue = (double) sum / arr.length;//求平均数 System.out.println("最大值为:" + maxValue); System.out.println("最小值为:" + minValue); System.out.println("总和为:" + sum); System.out.println("平均数为:" + avgValue); } }
3、使用简单数组
(1)创建一个名为ArrayTest的类,在main()方法中声明array1和array2两个变量,
他们是int[]类型的数组。
(2)使用大括号{},把array1初始化为8个素数:2,3,5,7,11,13,17,19。
(3)显示array1的内容。
(4)赋值array2变量等于array1,修改array2中的偶索引元素,使其等于索引值
(如array[0]=0,array[2]=2)。打印出array1。
思考:array1和array2是什么关系?
拓展:修改题目,实现array2对array1数组的复制
import java.math.*; public class Test { public static void main(String[] args) throws Exception { int[] array1, array2, array3; array1 = new int[]{2, 3, 5, 7, 11, 13, 17, 19}; for (int i = 0; i < array1.length; i++) { System.out.print(array1[i] + "\t"); } System.out.println(); array2 = array1; for (int i = 0; i < array2.length; i++) { if (i % 2 == 0) array2[i] = i; } for (int i = 0; i < array1.length; i++) { System.out.print(array1[i] + "\t"); } System.out.println(); /** * 结果: * 2 3 5 7 11 13 17 19 * 0 3 2 7 4 13 6 19 * * 思考: * 》array1和array2的地址相同,指向堆空间中同一个数组实体。这不能称作数组的复制。 */ array1 = new int[]{2, 3, 5, 7, 11, 13, 17, 19}; for (int i = 0; i < array1.length; i++) { System.out.print(array1[i] + "\t"); array2[i] = array1[i]; } System.out.println(); //实现复制 array3 = new int[array1.length]; for (int i = 0; i < array3.length; i++) { array3[i] = array1[i]; } for (int i = 0; i < array3.length; i++) { if (i % 2 == 0) array3[i] = i; } for (int i = 0; i < array1.length; i++) { System.out.print(array1[i] + "\t"); } System.out.println(); /** * 结果: * 2 3 5 7 11 13 17 19 2 3 5 7 11 13 17 19 思考: 》此时,修改array3并不会改变array1的值,两者互不影响,在内存中各有独立的空间。实现了数组的复制。 */ } }
算法了解
算法的5大特征:
输入( Input) | 有0个或多个输入数据,这些输入必须有清楚的描述和定义 |
---|---|
输出(Output) | 至少有1个或多个输出结果,不可以没有输出结果 |
有穷性(有限性,Finiteness) | 算法在有限的步骤之后会自动结束而不会无限循环,并且每一个步骤可以在可接受的时间内完成 |
确定性(明确性,Definiteness) | 算法中的每一步都有确定的含义,不会出现二义性 |
可行性(有效性,Effectiveness) | 算法的每一步都是清楚且可行的,能让用户用纸笔计算而求出答案 |
说明:满足确定性的算法也称为:确定性算法。现在人们也关注更广泛的概念,例如考虑各种非确定性的算法,如并行算法、概率算法等。另外,人们也关注并不要求终止的计算描述,这种描述有时被称为过程(procedure)。
查找算法
二分法查找
前提:所要查找的数组必须有序
思路:
算法思想为:从数组中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组已经为空,则表示找不到指定的元素。二分法查找每次是使查找范围缩小一半,其时间复杂度是O(log(n))
int[] arr = new int[]{-98,-34,2,34,54,66,79,105,210,333};
int dest = -34;//想找的元素
int head = 0;//初始的首索引
int end = arr.length -1; //初始的末索引
boolean isFlag =true;
while(head <= end) {
int middle = (head + end)/2;
if(dest == arr[middle]) {
System.out.println("找到了指定元素,位置为:"+ middle);
isFlag = false;
break;
} else if(arr[middle] > dest) {
end = middle - 1;
} else {
head = middle + 1;
}
}
if(isFlag) {
System.out.println("很抱歉,没有找到")
}
排序算法
目的是快速查找
总结使用排序算法的优缺点:
- 优点:
- 性能上的优越。(运行速度快)
- 缺点:
- 有些会存在排序的不稳定。
- 需要大量辅助空间。
- 不适于少量数据排序。
衡量一个排序算法的优劣的要素:
- 时间复杂度:分析关键字的比较次数和记录的移动次数
- 空间复杂度:分析排序算法中需要多少辅助内存
- 稳定性:若两个记录A和B的关键字值相等,但排序后A、B的先后次序保持不变,则称这种排序算法是稳定的。
排序算法分类:内部排序和外部排序。
- 内部排序:整个排序过程不需要借助于外部存储器(如磁盘等),所有排序操作都在内存中完成。
- 外部排序:参与排序的数据非常多,数据量非常大,计算机无法把整个排序过程放在内存中完成,必须借助于外部存储器(如磁盘)(内部排序完,存储在外部设备中,为内存腾出排序空间)。外部排序最常见的是多路归并排序。可以认为外部排序是由多次内部排序组成。
十大内部排序算法:
- 选择排序
- 直接选择排序、堆排序
- 交换排序
- 冒泡排序、快速排序
- 插入排序
- 直接插入排序、折半插入排序、Shell排序(希尔排序)
- 归并排序
- 桶式排序
- 基数排序
各种内部排序方法性能比较
- 从平均时间而言:快速排序最佳。但在最坏情况下时间性能不如堆排序和归并排序。
- 从算法简单性看:由于直接选择排序、直接插入排序和冒泡排序的算法比较简单,将其认为是简单算法。对于Shell排序、堆排序、快速排序和归并排序算法,其算法比较复杂,认为是复杂排序。
- 从稳定性看:直接插入排序、冒泡排序和归并排序时稳定的;而直接选择排序、快速排序、 Shell排序和堆排序是不稳定排序
- 从待排序的记录数n的大小看,n较小时,宜采用简单排序;而n较大时宜采用改进排序。
性能对比:
排序算法的选择(n:待排序的记录数)
- 若n较小(如n≤50),可采用直接插入或直接选择排序。
- 当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插入,应选直接选择排序为宜。
- 若文件初始状态基本有序(指正序),则应选用直接插入、冒泡或随机的快速排序为宜;
- 若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
冒泡排序
- 介绍:
- 冒泡排序的原理非常简单,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。
- 排序思想:
- 比较相邻的元素。如果第一个比第二个大(升序),就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较为止。
实现:
public class Test {
public static void main(String[] args) throws Exception {
int[] arr = new int[]{43, 32, 76, -98, 0, 64, 33, -21, 32, 99};
//冒泡排序
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
//遍历输出
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "\t");
}
}
}
快速排序
- 介绍:
- 快速排序通常明显比同为O(nlogn)的其他算法更快,因此常被采用,而且快排采用了分治法的思想,所以在很多笔试面试中能经常看到快排的影子。可见掌握快排的重要性。
- 快速排序(Quick Sort)由图灵奖获得者Tony Hoare发明,被列为20世纪十大算法之一,是迄今为止所有内排序算法中速度最快的一种。冒泡排序的升级版,交换排序的一种。快速排序的时间复杂度为O(nlog(n))
- 排序思想:
- 从数列中挑出一个元素,称为"基准"(pivot),
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
- 递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
- 演示:
实现:(不考虑稳定性的,注意:好的排序算法是要考虑稳定性的)
package com.atguigu.array.sort;
/**
* 快速排序
* 通过一趟排序将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分关键字小,
* 则分别对这两部分继续进行排序,直到整个序列有序。
* @author shkstart
* 2018-12-17
*/
public class QuickSort {
private static void swap(int[] data, int i, int j) {
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
private static void subSort(int[] data, int start, int end) {
if (start < end) {
int base = data[start];
int low = start;
int high = end + 1;
while (true) {
while (low < end && data[++low] - base <= 0)
;
while (high > start && data[--high] - base >= 0)
;
if (low < high) {
swap(data, low, high);
} else {
break;
}
}
swap(data, start, high);
subSort(data, start, high - 1);//递归调用
subSort(data, high + 1, end);
}
}
public static void quickSort(int[] data){
subSort(data,0,data.length-1);
}
public static void main(String[] args) {
int[] data = { 9, -16, 30, 23, -30, -49, 25, 21, 30 };
System.out.println("排序之前:\n" + java.util.Arrays.toString(data));
quickSort(data);
System.out.println("排序之后:\n" + java.util.Arrays.toString(data));
}
}
四:Arrays 工具类
- 介绍:
类即为操作数组的工具类java.util.Arrays
- 包含了用来操作数组(比如排序和搜索)的各种方法。 如:
- 有许多重载方法用于不同数据类型的使用,结构都一样,下面只展示一种。
- 下面展示的源码都是jdk1.8的
- equals(…):
//源码 public static boolean equals(int[] a, int[] a2) { if (a==a2) return true; if (a==null || a2==null) return false; int length = a.length; if (a2.length != length) return false; for (int i=0; i<length; i++) if (a[i] != a2[i]) return false; return true; }
- toString(…):
//源码 public static String toString(int[] a) { if (a == null) return "null"; int iMax = a.length - 1; if (iMax == -1) return "[]"; StringBuilder b = new StringBuilder(); b.append('['); for (int i = 0; ; i++) { b.append(a[i]); if (i == iMax) return b.append(']').toString(); b.append(", "); } }
- fill(…):
//源码 public static void fill(long[] a, long val) { for (int i = 0, len = a.length; i < len; i++) a[i] = val; }
- sort(…):
//源码 public static void sort(int[] a) { DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0); /** DualPivotQuicksort:双基点快排。Java将其封装成一个类供使用。 结论: 》底层用的是快排。 */ }
- binarySearch(…)
//源码 public static int binarySearch(int[] a, int key) { return binarySearch0(a, 0, a.length, key); } private static int binarySearch0(int[] a, int fromIndex, int toIndex, int key) { int low = fromIndex; int high = toIndex - 1; while (low <= high) { int mid = (low + high) >>> 1; int midVal = a[mid]; if (midVal < key) low = mid + 1; else if (midVal > key) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found. }
- 包含了用来操作数组(比如排序和搜索)的各种方法。 如: