一、什么是数组:
数组本质上就是让我们能"批量"创建相同类型的变量,一次定义N个相同数据类型的变量,我们就把这种结构称之为数组。比如:假设我现在要一次性创建1w个整型变量,我们就要用到数组这个结构
二、什么是数据结构:把一堆数字保存起来的结构就是数据结构,数据结构关心的是如何高效的读写数据
三、数组的创建于初始化:
1、数组的动态初始化:数据类型[] 数组名称 = new 数据类型{初始化数据},其中大括号中的内容可选
也可以数据类型[] 数组名称 = new 数据类型[num],num指的是当前数组的最大元素个数
比如:int[] arr = new int {1,3,5,7,9};第一种动态初始化方法是每个元素同时赋值,而int[] arr = new int[5];这个是指在创建数组时,若没有使用{}来初始化每个元素的值,每个元素都是该数据类型的默认值,即0,0,0,0,0
2、数组的静态初始化:数据类型[] 数组名称 = {初始化数据};比如:int[] arr = {1,3,5,7,9};和动态初始化本质一样,因为语法糖,经过javac编译之后,就是动态初始化,语法糖:只存在编译期之前的语法,编译器为了方便程序员简化写法,右键open in terminal可查看,经编译后,转化为int[] arr = new int[] {1,3,5,7,9};
四、数组的使用:
1、 获取一个数组的长度(最多保存的元素个数),使用数组名称.length 示例如下:
public class ArrTest { public static void main(String[] args) { int[] arr = new int[]{1,3,5,7,9}; int[] arr1 = new int[5]; System.out.println(arr.length);//长度都为5 System.out.println(arr1.length);//长度都为5 } }
2、如何访问数组元素
使用数组名称[要访问的元素相较于第一个元素的偏移量]
使用数组名称[元素的索引] 比如:int[] arr1 = new int[]{1,3,5,7,9};要获取第一个元素 就是arr1[0];数组的索引从0开始,最后一个元素的索引arr1.length-1
为什么要从0开始呢?索引其实就是"偏移量",相较于数组的第一个元素的单位长度,数组在内存中存储时,每个元素之间都是顺序存储的,保存的其实是数组的首元素地址。要找到其他元素,只要知道其他元素相较于第一个元素的距离就能找到。(一层楼有10个宿舍,我要找第5个宿舍,首先你得找到第一个宿舍在哪)
如果访问了一个数组索引并不存在的元素会咋样?比如:System.out.println(arr[5]);一定是访问了一个非法索引,这个索引在当前数组中根本就不存在,会显示数组越界异常的错误
遍历数组的每个元素 示例如下:
int[] arr2 = new int[]{1,3,5,7,9}; //访问数组arr2的每个元素 //第一种方法,此时i表示数组中每个元素的索引下标 for (int i = 0; i < arr2.length; i++) { System.out.print(arr[i] +"、"); } //第二种方法,JDK1.5引入的for-each循环,增强型for循环 //此处的i指的是从数组第一个元素开始取值,第一次把第一个元素的值赋值一份给i,第二次循环把第二个元素的值赋值一份给i //依此类推,直到整个数组都遍历结束 for (int i:arr2){ System.out.print(i + "、");
其中,关于for-each循环:
//此时for-each循环的i只能读取数组的元素值,无法修改!!! //i是原数组每个元素的值拷贝,并不是实实在在的数组元素 for (int i:arr2){ if (i == 5){ i = 55; } } System.out.println("第三个元素的值为:" + arr2[2]);
五、数组作为第一个引用数据类型 int[] arr = new int[5]具体来说就是整型数组的引用,数组和方法之间存在一些关系(重点!!!重点!!!)
1、数组作为方法的参数
创建一个方法,接收任意的整型数组并打印。示例如下:
System.out.println(); int[] arr3 = {1,3,5}; int[] arr4 = {2,4,6,8}; printNum(arr3); printNum(arr4); } public static void printNum(int[] num){ for (int i:num){ System.out.print(i+"、"); } }
2、关于引用数据类型的理解问题:
引入:(1)参考笔记实参与形参的关系,实参是位于主方法的临时变量,形参是位于swap方法的临时变量,而JVM把内存划分为6个区域,今天重点记栈区和堆区,方法的调用就是在栈区进行的,每个方法的调用过程,就是一个栈帧的入栈以及出栈的过程。栈采取LIFO 先进后出,后进先出的结构,所以先将main方法入栈,然后将swap方法入栈,方法中的局部变量和形参都在栈中存储,swap方法交换了x和y的值,可是由于后进先出,当方法调用结束出栈时,临时变量会被销毁。因此不改变main方法中实参的值,图见word文档
(2) JVM的另一块内存区域被称为"堆区",所有对象都在堆区存储,数组对象,累的实例化对象,接口的对象
示例:int[] arr = new int[]{10,20};new后面代表数组对象,在堆上存储,等号前面代表数组引用,引用就是起了个"别名",保存的数值就是该对象的地址。对于数组对象来说,数组引用实际上就是保存了数组的首元素地址。(重点!)类比现实生活:我就是一个人类对象,实实在在存在的,明哥、小明-->引用(给对象起了个名字);再比如,电视机--->实实在在存在的对象,遥控器就是这个电视机的引用,一个电视机可以有多个遥控器,拿着遥控器就能操作电视机的本质,就是遥控器内部保存了这个电视机的地址
(3) 实参到形参的传递仍然满足值传递,只是把主方法中int[] arr的地址拷贝一份复制给swap方法中 int[] arr,图见word文档,此时修改堆中数组对象的值,main方法中arr5中可见,因为main和swap方法都同时指向堆中同一区域,示例如下:
public class ArrTest { public static void main(String[] args) { int[] arr5 = new int[]{10,20}; swapArr(arr5); System.out.println("arr[0] = " + arr5[0] +",arr[1] = " + arr5[1] ); } public static void swapArr(int[] arr){ //拿着swapArr方法中的数组引用swap-arr修改了堆中数组对象的值,这个修改对于主方法中arr5是可见的 //本质:这两个引用指向了堆中同一块内存区域!!! //arr = new int[]{10,20};若加上这句代码,看见new关键字,一定要在堆中开辟新的空间,此时会有两个电视机,两个遥控器各自指各自的电视机,所以主方法中arr5的值不改变,图见word文档 int temp = arr[0]; arr[0] = arr[1]; arr[1] = temp; } }
(4) 补充:若此时想在main方法中调用swapArr中的数组值,先返回swap中的数组名称,然后要操作堆中的对象,必须通过引用来进行,必须知道这个对象的名字才能操作,示例如下:
public class ArrTest { public static void main(String[] args) { int[] arr5 = new int[]{10, 20}; int[] ret = swapArr(arr5); System.out.println("arr[0] = " + arr5[0] + ",arr[1] = " + arr5[1]); System.out.println("ret[0] = " + ret[0] + ",ret[1] = " + ret[1]); } public static int[] swapArr(int[] arr){ arr = new int[]{10,20}; int temp = arr[0]; arr[0] = arr[1]; arr[1] = temp; return arr; } }
数组练习题1:实现数组拷贝并打印数据元素的值
public class ArrayHomeWork { public static void main(String[] args) { //数组练习题1:实现数组拷贝并打印数据元素的值 int[] arr = new int[] {1,2,5,8}; int[] newArr = copyOf(arr); printArray(newArr); } //因为是拷贝一个整型数组,所以返回值是int[] public static int[] copyOf(int[] arr){ int[] newArr = new int[arr.length]; for (int i = 0; i < arr.length; i++) { newArr[i] = arr[i]; } return newArr; } public static void printArray(int[] arr){ for (int i :arr){ System.out.print(i + " "); } } }
数组练习题2:数组对象转化为字符串对象
以后看到JDK中的某些类,在类后面加s,这种类都是工具类,提供了大量有用的方法,Arrays数组的工具类--包含数组转字符串的方法,数组排序的方法等操作,collections集合工具类,使用工具类会产生导包
import java.util.Arrays; public class ArrayHomeWork { public static void main(String[] args) { //数组练习题2:数组对象转化为字符串对象 int[] arr = new int[]{1, 2, 5, 8}; //方法一:利用Arrays工具进行转化 String str = Arrays.toString(arr); System.out.println(str); //方法二:利用定义方法来转化 System.out.println(arrToStr(arr)); } public static String arrToStr(int [] arr){ //利用String的拼接来做 String str = "["; for (int i = 0; i < arr.length; i++) { str+=arr[i]; if (i != arr.length-1){ str+=", "; } } str+="]"; return str; } }
数组练习题3:利用Arrays类实现数组拷贝,并分类讨论新数组长度与原数组长度的不同情况
public class ArrayHomeWork { public static void main(String[] args) { //数组练习题3:利用Arrays类实现数组拷贝,并分类讨论新数组长度与原数组长度的不同情况 //第一种情况:部分拷贝,完全拷贝就是一模一样 int[] newArr = Arrays.copyOf(arr,2); System.out.println(arrToStr(newArr)); //第二种情况:新数组长度大于原数组长度,全拷贝,剩余的数据元素用该数据类型的默认值补齐 int[] newArr1 = Arrays.copyOf(arr,5); System.out.println(arrToStr(newArr1)); //第三种情况:使用索引部分拷贝,一般都是左闭右开 int[] newArr2 = Arrays.copyOfRange(arr,0,2); System.out.println(arrToStr(newArr2)); } }
数组练习题4:给定一个整数数组,如何找出这个数组的最大值
public class ArrayHomeWork { public static void main(String[] args) { int[] arr = new int[]{1, 2, 5, 8}; System.out.println(max(arr)); } public static int max(int[] arr){ //默认取第一个数组元素假定为最大值或最小值,而不能随机取一个数字 int max = arr[0]; for (int i = 0; i < arr.length; i++) { if (arr[i]>max){ max = arr[i]; } } return max; } }
数组练习题5:查找一个数组中是否包含指定元素,若存在,返回该元素的索引,若不存在,返回-1
public class ArrayHomeWork { public static void main(String[] args) { int[] arr = new int[]{1, 2, 5, 8}; System.out.println(find(arr,5)); } public static int find(int[] arr,int toFind){ for (int i = 0; i < arr.length; i++) { if (toFind==arr[i]){ return i; } } //若没有找到指定元素 return -1; } }
数组练习题6.重点题 二分查找法 前提是数组有序 找到元素返回索引,没有找到返回-1
public class ArrayHomeWork { public static void main(String[] args) { //数组练习题6.重点题 二分查找法 前提是数组有序 找到元素返回索引,没有找到返回-1 System.out.println(binarySearch(arr,8)); } //二分查找法 public static int binarySearch(int[] arr,int toFind){ int left = 0; int right = arr.length-1; //在arr[left..right]区间中查找元素toFind的索引 //left==right,说明区间中还剩下一个元素没有判断,就要判断这个最后的元素 //循环终止的条件一定是left>right,区间中一个元素都没有才终止循环 while (left<=right) { //中间数据元素的下标 int mid = (left + right) / 2; if (toFind==arr[mid]){ System.out.println("找到元素了!"); return mid; }else if (toFind>arr[mid]){ left = mid+1; }else { right = mid-1; } } //该区间没有指定元素 return -1; } }
数组练习题7:尝试用递归的方法实现二分法查找
public class ArrayHomeWork { public static void main(String[] args) { //数组练习题7:尝试用递归的方法实现二分法查找 System.out.println(binarySearchRecursion(arr,5,0,arr.length-1)); } public static int binarySearchRecursion(int[] arr,int toFind,int left,int right){ //1、终止条件,如果left>right,空数组,返回-1,跳出 if (left>right){ return -1; } //若left<=right时 int mid = (left+right)/2; if (toFind==arr[mid]){ System.out.println("找到该元素了!"); return mid; }else if (toFind<arr[mid]){ return binarySearchRecursion(arr,toFind,left,mid-1); } return binarySearchRecursion(arr,toFind,mid+1,right); } }
补充:通查找思路和二分查找思路的对比,示例如下:
当一个有序集合的长度为n时,顺序查找法,比较的次数n;
二分查找:n/2/2/2/2一直到==1 n=log2N(以2为底,N的对数) 对数操作:当一个算法不断除以一个数知道除为1或0,这个算法就是对数级别算法
public class ArrayHomeWork { static int count = 0; public static void main(String[] args) { //补充:普通查找思路和二分查找思路的对比 int[] data = new int[10000]; for (int i = 0; i < data.length; i++) { data[i] = i+1; } //顺序查找9999,需要查找多少次9999次 System.out.println(find(data,9999)); System.out.println(binarySearch(data,9999));//结果为倒数第二个的索引9998 System.out.println("二分查找一共比较了:"+count+"次");//16次 } public static int find(int[] arr,int toFind){ for (int i = 0; i < arr.length; i++) { if (toFind==arr[i]){ return i; } } //若没有找到指定元素 return -1; } //二分查找法 public static int binarySearch(int[] arr,int toFind){ int left = 0; int right = arr.length-1; //在arr[left..right]区间中查找元素toFind的索引 //left==right,说明区间中还剩下一个元素没有判断,就要判断这个最后的元素 //循环终止的条件一定是left>right,区间中一个元素都没有才终止循环 while (left<=right) { //可用于记录查找的次数 count++; //中间数据元素的下标 int mid = (left + right) / 2; if (toFind==arr[mid]){ System.out.println("找到元素了!"); return mid; }else if (toFind>arr[mid]){ left = mid+1; }else { right = mid-1; } } //该区间没有指定元素 return -1; } }
补充:了解知识,二维数组,相比一维数组只是多了列这种概念
语法:数据类型 数组名称 = new 数据类型行数{可选初始化数据},内部一个{}代表一行,行与行之间用逗号隔开,那么如何遍历二维数组呢?
public class ArrayHomeWork { public static void main(String[] args) { //补充:如何遍历二维数组 int[][] arr1 = new int[][]{ {1,2,3,4}, {5,6,7,8}, {9,10,11,12} }; for (int row = 0; row < arr1.length; row++) { for (int col = 0; col < arr1[row].length; col++) { System.out.print(arr1[row][col] + "\t"); } } } }
递归重点习题:汉诺塔问题
public class ArrayHomeWork { public static void main(String[] args) { //递归重点习题:汉诺塔问题 int n =3; Tower(n,'A','B','C'); } public static void Tower(int nDisks,char A,char B,char C){ //1、终止条件,或边界条件,当盘子数为1时,直接从A到C if (nDisks==1){ move(nDisks,A,C); return; } //nDisks>=2时 //核心步骤1,将n-1个盘子由A借助C移动到B Tower(nDisks-1,A,C,B); //核心步骤2,将A中最大的盘子由A移动到C move(nDisks,A,C); //核心步骤3,将B中n-1个盘子借助A由B移动到C Tower(nDisks-1,B,A,C); } //将原盘子从原塔移动到目标塔 public static void move(int nDisks,char sourceTower,char destTower){ System.out.println("编号为"+nDisks+"的盘子正在从"+sourceTower+"->"+destTower); } }