1 数组基本用法
数组: 是一块连续的内存,用来存放相同数据类型的集合。一定要注意数组中包含的变量必须是相同类型。在Java当中数组也叫做数组对象。
1.1 定义数组
如何用程序来定义一个数组?
定义数组有三种定义方式,具体写法如下所示:
- 数据类型[ ] 数组名称 = { 初始化数据 };
//这里不仅定义了一个数组,而且对这个数组同时进行了初始化,这种初始化叫做静态初始化。- 数据类型[ ] 数组名称 = new 数据类型 [数据大小] ;
//这里定义了数组,但是没有初始化,所以这个数组的默认值为0- 数据类型[ ] 数组名称 = new 数据类型 [ ] { 初始化数据 };
//这种定义方式要注意第二个[]里绝对不能写数字,它会自己判断当前数组有多大,否则会出现编译报错;而且这里不仅定义了一个数组,而且对这个数组同时进行了初始化,这种初始化叫做动态初始化。
在这里等号右面这一堆都叫做对象,数组的对象;int[]叫做所定义的是数组的类型;
array、array2、array3都叫做引用。引用就是一个变量,只不过存在它里面的值是地址。引用指向对象。
具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6,7};//这里不仅定义了一个数组,而且对这个数组同时进行了初始化,这种初始化叫做静态初始化。
int[] array2 = new int[10];//这里定义了数组,但是没有初始化,所以这个数组的默认值为0
int[] array3 = new int[]{1,2,3,4,5,6,7};//这种定义方式要注意第二个[]里绝对不能写数字,它会自己判断当前数组有多大,否则会出现编译报错;而且这里不仅定义了一个数组,而且对这个数组同时进行了初始化,这种初始化叫做动态初始化。
}
}
对于当前这个函数来说,我们的array、array2、array3属于3个变量(引用),这3个变量在main()函数当中,那么这3个变量都是局部变量,它们存储在栈上,分别会占用一块内存,并且每一个引用都会指向一个对象。但是这三个变量的对象都存储在堆上,尽管可能像array和array3 的对象都是数字1到7,但他们所指向的对象还是两个不同的对象,引用就是用来存地址的,这两个对象的地址当然也就不同。可借助下图理解引用与对象之间的关系(引用就是用来存地址的)。
注意: 引用不一定存储在栈上,但是等号右面的只要是对象,则一定是在堆上的。上面所讲解的引用存储在栈上,是因为上面的引用是一个局部变量。如果此时的引用是一个实例变量,不是局部变量,此刻它就不能放在栈上。例如下面代码中的tmp变量就是一个实例变量,不是局部变量,此刻它就不能放在栈上。
public class TestDemo {
int[] tmp = {1,2,3,4,5,6,7};
public static void main(String[] args){
int[] array = {1,2,3,4,5,6,7};
int[] array2 = new int[10];
int[] array3 = new int[]{1,2,3,4,5,6};
}
}
1.2 打印数组
如何对数组进行打印?
对数组进行打印有三种打印方式,具体写法如下所示:
1. 第一种是采用for()循环中的fori()的方式进行打印数组;
for (int i = 0; i < 数组名.length ; i++) {
System.out.print(array[ i ] + " ");
}
2. 第二种则是采用for()循环中的foreach()的方式进行打印数组;
for ( int x: 数组名 ) {
System.out.print( x + " ");
}
3. 第三种是利用Arrays.toString();方式进行打印数组。
Arrays是一个工具类,专门用来操作数组的工具类。
String 变量名 = Arrays.toString(数组名);
System.out.println(变量名);
具体代码示例如下:
public class TestDemo {
public static void main(String[] args){
int[] array = {1,2,3,4,5,6,7};
int[] array2 = new int[10];//下标可以取到0到9
int[] array3 = new int[]{1,2,3,4,5,6};
//方法1
//这个length就属于这个数组的属性,表示获取数组的长度
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
System.out.println();
System.out.println("=====================");
//方法2
for (int x:array3) {//array中的每一个引用是一个数字
System.out.print(x+" ");
}
System.out.println();
System.out.println("=====================");
/*
* 这种打印方式是将数组转化为字符串进行输出
* */
String str = Arrays.toString(array3);
System.out.println(str);
}
}
这三种打印方式的区别是:
- 对于fori()的方式进行打印数组可以拿到数组的下标,即 System.out.print()语句中的array[i]。
- 而对于foreach()的方式进行打印数组只能拿到值,即System.out.print()语句中的x。
- 而相较于前两种打印方式,利用Arrays.toString();方式进行打印数组,是将数组转化为字符串进行输出,前面两种方式打印出来的数据没有[ ],直接是1 2 3 4 5 6这样的;而这种打印方式打印出来的数据是含有[ ]的,是[1,2,3,4,5,6]这样的。
1.3 数组的使用
获取数组长度: array.length。
使用 array.length 能够获取到数组的长度,这个操作为成员访问操作符.,在面向对象中会经常用到。
访问数组元素: 使用 [ ] 按下标获取数组元素即可。
我们要注意,使用 [ ] 按下标取数组元素时,下标是从 0 开始计数;并且使用 [ ] 操作既能读取数据, 也能修改数据;下标访问操作不能超出有效范围 [0, length - 1] , 如果超出有效范围, 会出现下标越界异常。
上面两个关于数组使用的具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int[] array = {1, 2, 3};
// 获取数组长度
System.out.println("length: " + array.length); // 执行结果: 3
// 访问数组中的元素
System.out.println(array[1]); // 执行结果: 2
System.out.println(array[0]); // 执行结果: 1
array[2] = 100;
System.out.println(array[2]); // 执行结果: 100
}
}
下标越界: 下标访问操作不能超出有效范围 [0, length - 1] , 如果超出有效范围, 会出现下标越界异常。具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6,7};
System.out.println(array[7]);
}
}
发生下标越界时,虽然代码在编译过程中不会报错,但是在运行过程中会发生报错,抛出异常。这里抛出的异常为 java.lang.ArrayIndexOutOfBoundsException ,叫做数组下标超出范围异常,也称为数组越界异常。具体运行结果如下图所示:
黄色框里面的表示是异常名,紫色圆圈里面的表示此时你访问了这个数组的7下标,这是你就要明白你的数组长度一定小于等于7,使用数组一定要下标谨防越界。
认识null: null 在 Java 中表示 “空引用” , 也就是一个无效的引用。
如下面代码中的 int a = 0;这个a是一个整型类型的数据,我们初始化时直接使用0就可以。 而如果当你写一个array = 0;此时你的array它是一个引用[ ],如果此时给它赋值一个0,则会发生编译报错,因为此时0是一个整型,等号左边的array是一个引用,两边类型不匹配。 array是一个引用,当你不知道引用指向谁的时候,就把array的值赋为初值,也可以把它叫做null,这个null就是我们引用的零值,此时array这个引用,不指向任何对象。具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int a = 0;//这个a是一个整型类型的数据,我们初始化时直接使用0就可以。
int[] array = {1,2,3,4,5,6,7};
//array = 0;此时你的array它是一个引用[],如果此时给它赋值一个0,则会发生编译报错,因为此时0是一个整型,等号左边的array是一个引用,两边类型不匹配
//array是一个引用,当你不知道引用指向谁的时候,就把array的值付为初值,也可以把它叫做null,这个null就是我们引用的零值
array = null;//此时array这个引用,不指向任何对象
System.out.println(array[2]);
//或System.out.println(array.length);
}
}
运行此代码,发现在运行过程中发生报错,抛出异常。这里抛出的异常为 java.lang.NullPointerException,叫做空指针异常。原因是因为 array = null不指向任何的对象,所以编译器自然也不知道array[2]是哪个对象的2下标了,那它就会发生一个空指针异常;同理,array.length中的array也就不知道指向的是哪个对象了,也自然就求不出这个数组的长度了,那它也就会发生一个空指针异常。具体运行结果如下图所示:
如果你的代码出现了空指针异常,一定要去检查引用是否为null。
但此时针对上面的这个代码你可能又会提出,那array原来指向的对象{1,2,3,4,5,6,7},在array指向空以后又去哪了?
答:原来指向的对象{1,2,3,4,5,6,7}被JVM垃圾回收器回收了。即当堆中的对象没有引用再引用的时候,那么这个对象就会被垃圾回收器回收掉。
数组作为方法的参数: 数组作为参数传递,传过去的一定是当前这个引用的值(地址),也就是这个引用所指向的这个对象的值(地址)。
以打印数组内容为例,具体代码示例如下:
public class TestDemo {
public static void print(int[] array2){
for (int i = 0; i < array2.length; i++) {
System.out.println(array2[i]+" ");
// System.out.println(2 * array2[i]+" ");//将数组扩大2倍输出
}
System.out.println();
//array2[2]=2 * array2[2];
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
print(array);
// System.out.println(array[2]);
}
}
对于此段代码来说,array和array2都同时指向了{1,2,3,4,5,6}这同一个对象。在这个代码中int[ ] array2是函数的形参, int[ ] array 是函数实参。如果需要获取到数组长度, 同样可以使用array2.length。具体可借助下图进行理解。
所以,两个引用同时指向了一个对象,不管你通过哪个引用修改对象的值,另一个引用去访问的时候,都会受到影响。
数组作为方法的返回值1: 在Java中数组不仅可以作为方法得参数,还可以作为方法的返回值。
具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int[] ret = func1();
String str = Arrays.toString(ret);
System.out.println(str);
}
public static int[] func1(){
int[] ret = {1,2};
return ret;
//对于上面两句代码,我们有一个更直接的写法
//直接 return new int[]{1,2};
}
}
在注释里的int[ ]{1,2},我们把这个数组叫做匿名数组,代表当前这个数组是没有名字的数组。匿名数组的不好处就是你每次用都得重新new一次,得随用随取。
总结: 引用可以把它看作是一个指针,就是用来存地址的。
补充: 下面的这个代码并不会报错,它是合理的。虽然引用是不能指向引用的,但是array2这个引用,并没有指向array这个引用,而指向了array这个引用所指向的对象。
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
int[] array2 = array;
}
}
可借助下图进行理解:
引用只能同时指向1个对象: 就如下面代码中的array所指向的前两个对象{1,2,3,4,5,6}和{4,5,6,7,8,9},在被array指向第三个对象{14,15,16,17,18,19}时,前两个对象就会被垃圾回收器回收掉,所以此时array这个引用还是只指向了{14,15,16,17,18,19}这一个对象。具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
array = new int[]{4,5,6,7,8,9};
array = new int[]{14,15,16,17,18,19};
}
}
所以,上面所说如下图所示:
之前我们提到过,JVM对我们的内存进行了划分,JVM中内存共划分为5块,分别为:Java虚拟机栈(JVM Stack)、本地方法栈、堆、程序计数器和方法区。我们每一个线程(Thread)就拥有一个独立的程序计数器(PC Register)、Java虚拟机栈(JVM Stack)和本地方法栈(Native Method Stack),但你会发现并不是每一个线程(Thread)就拥有一个独立的堆(Heap)和方法区(Method Area),所以堆和方法区属于线程共享的。并且运行时常量池(Runtime Constant Pool)在JDK1.7以后已经从方法区移到堆中去了,具体如下图所示:
当数组作为方法的返回值2: 写一个方法,将数组中的每个元素都 * 2,具体代码示例如下所示:
public class TestDemo {
int[] tmp = {1,2,3,4,5,6,7};
public static int[] func2(int[] array) {
for (int i = 0; i < array.length; i++) {
array[i] = array[i] * 2;
}
return array;
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
int[] ret = func2(array);
System.out.println(Arrays.toString(ret));
}
}
如何理解这段代码呢?具体解析如下:
- 此程序执行先进入main()函数,我们遇到了第一个array,array是一个局部变量,在你的栈上就需要开辟一块内存给它,此时{1,2,3,4,5,6}就是一个对象,它被放入堆中。假设这个对象的地址是0x999,我们栈上的array里面存放的也是0x999这个地址,说明当前array这个引用 引用 的就是{1,2,3,4,5,6}这个对象。
- 下面我们调用了func2()这个函数,把上面array这个实参的值给func2()函数所对应的(int[] array)这个形参了,所以这个时候又有1个array出现了,这个array也会占用栈上的一块内存,此时这个array里面的值是所传过去的上面array这个实参的值,也就是0x999这个地址。这样一来,我们这个array引用也引用了当前堆当中的{1,2,3,4,5,6}这个对象。
- 然后就进入func2()这个方法当中了,通过第二个array这个引用遍历{1,2,3,4,5,6}这个数组中的每一个元素,将取出来的元素每一个乘以2,再把它们分别放回这个数组当中去。这样相当于把原来的{1,2,3,4,5,6}乘完以后变成{2,4,6,8,10,12}放回去。然后for()循环走完以后,返回array,返回的是func2()函数所对应的(int[] array)这个形参的array,也就是这里所说的第二个array。这里的第二个array存放的值是0x999。返回的这个值给ret这个引用(变量)了。
- 那么此时我们这个ret又是一个引用,也就是局部变量,也就需要在栈上新开辟一块内存给它,它里面放的值是刚才的返回值,也就是第二个array存放的值0x999。那么此时ret也指向了堆中的0x999这个对象,此时这个对象的具体值已经变为{2,4,6,8,10,12}了。
- 最后调用Arrays.toString(),把ret扔进去,相当于通过ret这个引用来访问这个引用所指向的对象。那么此时ret这个引用所指向的对象就是{2,4,6,8,10,12}这个对象了,打印出的结果也就为[2,4,6,8,10,12]了。
具体可借助下图进行理解:
但是这样写有一个不好的地方,你会发现我们把原来的数组值修改了,如果此时要求你不能修改原来对象数组中的值,你需要重新申请一个数组,把那个对象数组里的值扩大2倍,此时这个代码应该怎么修改?具体代码示例如下所示:
public class TestDemo {
public static int[] func3(int[] array) {
int[] ret = new int[array.length];
for (int i = 0; i < array.length; i++) {
ret[i] = array[i] * 2;
}
return ret;
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
int[] ret = func3(array);
System.out.println(Arrays.toString(ret));
}
}
此时这个代码与上面那个代码不同的是,我们在进入fun3()方法之后,重新申请了一个数组ret放在了堆中,这个数组的长度和原来那个数组的长度是一样的。然后把原来那个数组的每一个元素取出来乘以2,结果返回到ret当中。原来araay这个引用所对应的对象数组并没有改变,依旧是{1,2,3,4,5,6}。具体可借助下图进行理解:
2 数组练习
例1:用Java解决两数之和问题。
此题的详细解答过程见链接:https://blog.csdn.net/weixin_51312723/article/details/113572849
例2:找到主要元素问题。
问题描述: 数组中占比超过一半的元素称之为主要元素。给定一个整数数组,找到它的主要元素。若没有,返回-1。
- 示例1:输入:[1,2,5,9,5,9,5,5,5]; 输出:5。
- 示例2:输入:[3,2]; 输出:-1。
问题分析:
1.为了方便查找,我们首先需要将数组中的元素进行一个从小到大的排序,如下图所示将示例1的元素进行了一个从小到大的排序:
这里我们可以通过直接调用Arrays.sort()方法进行数组排序。
2.要注意这里虽然5最多,但是不一定是超过一半的元素,也就不一定是主要元素,数组中占比超过一半的元素才能称之为主要元素。
具体代码示例如下:
public class TestDemo {
public static int majorityElement(int[] nums){
int len = nums.length;
Arrays.sort(nums);//通过调用Arrays.sort()方法进行数组排序
for (int i = 0; i <= len/2; i++) {
for (int j = i+len/2; j <len ; j++) {
if(nums[i] == nums[j]){
return nums[i];
}
}
}
return -1;
}
public static void main(String[] args) {
int[] nums = {1,2,5,9,5,9,5,5,5};
System.out.println( majorityElement(nums));
}
}
例3:数组转字符串。
方法1: 直接通过Arrays.toString();方式将数组转换为字符串,具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
System.out.println(Arrays.toString(array));
}
}
方法2: 我们通过自己编写代码,实现一个自己版本的数组转字符串,具体代码示例如下:
public class TestDemo {
int[] tmp = {1,2,3,4,5,6,7};
public static String myToString(int[] array){
if(array == null){
return null;
}
String ret = "[";
for (int i = 0; i < array.length; i++) {
ret += array[i];
if(i != array.length-1){
ret += ",";
}// 借助 String += 进行拼接字符串
}// 除了最后一个元素之外, 其他元素后面都要加上 ", "
ret += "]";
return ret;
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
//System.out.println(Arrays.toString(array));
System.out.println(myToString(array));
}
}
例4:数组拷贝。
方法1: 通过for循环进行拷贝,具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
//方法1 通过for循环进行拷贝
int[] array2 = new int[array.length];
for (int i = 0; i <array.length ; i++) {
array2[i] = array[i];
}
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(array2));
}
}
此代码可借助下图进行理解:
方法2: 通过数组的工具类Arrays.copyOf()进行数组的拷贝,这里的第一个参数是你要考虑的数组为哪一个,第二个参数是要决定拷贝多长,即拷贝的长度。如果大于你所要拷贝的这个数组的长度,则此数组拷贝完以后,剩下的长度里面写的是0。具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
int[] array2 = Arrays.copyOf(array,array.length);
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(array2));
}
}
此代码可借助下图进行理解:
方法3: 通过调用System.arraycopy()方法进行数组的拷贝,这里的第一个参数表示要拷贝的数组,也就是原来的数组;第二个参数表示原来数组的位置;第三个参数表示目的地的数组,也就是拷贝后的数组;第四个参数表示目的地的位置,也就是拷贝后数组的位置;第五个参数表示拷贝的长度。具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
int[] array2 = new int[array.length];
System.arraycopy(array,0,array2,0,array.length);
//Object是所有类的父类
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(array2));
}
}
如下图所示,这里的arraycopy()是一个被native所修饰的方法。记住以后如果看到一个方法被native所修饰,说明这个方法是C或C++代码实现的,我们是看不到具体的实现的,native的特点是速度快,所以方法3要比方法2快。在JVM的本地方法栈中运行的方法就是native的。
方法4: 通过调用数组名.clone()方法进行数组的拷贝,它的原理是产生当前引用的一个副本。具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
int[] array2 = array.clone();
System.out.println(Arrays.toString(array));
System.out.println(Arrays.toString(array2));
}
}
提问:这四种拷贝方式是深拷贝还是浅拷贝?
答:浅拷贝。下面就介绍一下深拷贝和浅拷贝分别是什么:
如下图所示,引用里存放的只是简单的数值。拷贝结束之后通过 比如array2这个引用 修改所指向的对象的时候,不会影响原来array1这个引用所指向的对象,那么这个拷贝过程叫做深拷贝。
如下图所示,引用里存放的是地址。拷贝结束之后通过 比如array2这个引用 修改所指向的对象的时候,会影响原来array1这个引用所指向的对象,那么这个拷贝过程叫做浅拷贝。
那么如何将浅拷贝变为深拷贝?
答:我们就不能拷贝那个引用了,而应该拷贝那个引用所指向的对象,学习完接口后,就可以实现深拷贝,目前可根据下图先进行一个大概的理解。
数组部分拷贝: 通过数组的工具类Arrays.copyOfRange()进行数组的部分拷贝,,这里的第一个参数是你要考虑的数组为哪一个,第二个参数是要决定拷贝是从哪一个位置开始拷,第三个参数是要决定拷贝到哪一个位置结束此部分数组的拷贝。它是一个左闭右开的定义,就是说如下面代码按理来说是从第2个位置进行开始,第5个位置进行结束,输出结果为[3,4,5,6];但是因为它左闭右开的定义,第5个位置取不到,正确的输出结果应该为[3,4,5]。具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
int[] ret = Arrays.copyOfRange(array,2,5);
System.out.println(Arrays.toString(ret));
}
}
例5:找出数组中最大的元素。
类似于 “打擂台” 这样的过程,其中 max 变量作为 擂台,比擂台上的元素大,就替换上去,否则就下一个对手。具体代码示例如下:
public class TestDemo {
public static int findMax(int[] array){
int max = array[0];
for (int i = 0; i < array.length; i++) {
if(max < array[i]){
max = array[i];
}
}
return max;
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
System.out.println(findMax(array));
}
}
例6:求数组中元素的平均值。
注意元素的平均值可能是小数,所以这里就不能用整型作为返回值了,要用double作为返回值才合适。具体代码示例如下:
public class TestDemo {
public static double findAvg(int[] array){
double avg = 0.0;
int sum = 0;
for (int i = 0; i < array.length; i++) {
sum += array[i];
}
avg = 1.0*sum/array.length;
return avg;
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
System.out.println(findAvg(array));
}
}
例7:查找数组中指定元素(顺序查找)。
给定一个数组, 再给定一个元素, 找出该元素在数组中的位置。顺序查找就是我们所说的遍历的方法,就是一个一个找,直到找到自己所需要的数字为止。具体代码示例如下:
public class TestDemo {
public static int findNum(int[] array,int key){
for (int i = 0; i <array.length ; i++) {
if(array[i] == key){
return i;
}
}
return -1;
}
public static void main(String[] args) {
int[] array = {1,2,31,4,15,6,7};
System.out.println(findNum(array,7));
}
}
例8:查找数组中指定元素(二分查找)。
针对有序数组, 可以使用更高效的二分查找。记住一定得是有序的!有序分为 “升序” 和 “降序”,如 1 2 3 4 , 依次递增即为升序;如 4 3 2 1 , 依次递减即为降序。以升序数组为例, 二分查找的思路是先取中间位置的元素, 看要找的值比中间元素大还是小, 如果小, 就去左边找; 否则就去右边找。
方法1: 具体代码示例如下:
public class TestDemo {
public static int binarySearch(int[] array,int key){
int left = 0;
int right = array.length-1;
while(left <= right){
int mid = (left+right)/2;
if(array[mid] == key){
return mid;
}else if(array[mid] < key){
left = mid +1;
}else{
right = mid -1;
}
}
return -1;
}
public static void main(String[] args) {
int[] array = {1,2,31,4,15,6,7};
Arrays.sort(array);//对数组进行排序
System.out.println(Arrays.toString(array));
System.out.println(binarySearch(array,4));
}
}
方法2: 在这里我们还可以直接使用数组的工具类Arrays.binarySearch()进行数组的二分查找,这里的第一个参数表示要查找的是哪一个数组;第二个参数表示在这个数组中要查找哪一个元素。具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int[] array = {1,2,31,4,15,6,7};
Arrays.sort(array);//对数组进行排序
Arrays.binarySearch(array,4);
}
}
我们可以感受一下对于一个有序数组,二分查找的效率比顺序查找的高得多,比如针对一个长度为 10000 个元素的数组查找, 二分查找只需要循环 14 次就能完成对于元素9999的查找,而顺序查找则要循环9999次才能完成对于元素9999的查找。所以,对于有序数组来说,随着数组元素个数越多, 二分的优势就越大。具体代码示例如下:
public class TestDemo {
public static int count = 0;
public static int binarySearch(int[] array,int key){
int left = 0;
int right = array.length-1;
while(left <= right){
count++;
int mid = (left+right)/2;
if(array[mid] == key){
return mid;
}else if(array[mid] < key){
left = mid +1;
}else{
right = mid -1;
}
}
return -1;
}
public static void main(String[] args) {
int[] array = new int[1_0000];
for (int i = 0; i <array.length; i++) {
array[i] = i;
}
System.out.println(binarySearch(array,9999));
System.out.println(count);
}
}
例9:检查数组的有序性。
给定一个整型数组, 判断是否该数组是有序的(升序)。这里方法的返回类型是布尔类型,因为只用判断它是否有序,结果只有true或者false两种情况。具体代码示例如下:
public class TestDemo {
public static boolean isUp(int[] array){
for (int i = 0; i < array.length-1 ; i++) {
if(array[i] > array[i+1]){
return false;
}
}
return true;
}
public static void main(String[] args) {
int[] array = {1,2,3,10,5,6};
System.out.println(isUp(array));
}
}
例10:数组排序(冒泡排序)。
给定一个数组, 让数组升序 (降序) 排序。
算法思路: 每次尝试找到当前待排序区间中最小(或最大)的元素, 放到数组最前面(或最后面)。如下面两张图中的四个数字就经历了三趟冒泡排序,冒泡排序过程如下图所示:
所以我们在编写代码时应该先确定冒泡排序所需要的趟数,通过上述例子我们发现冒泡排序所需要的趟数就是数组长度减1。然后我们要知道上图中的 j 所走的级数的问题,我们发现 j 永远小于数组长度减1,再减去此次正在排序的趟数(但要注意,我们这里的趟数 i 是从0开始的,所以此次正在排序的趟数永远比我们正真所数的趟数少一趟)。具体代码示例如下:
public class TestDemo {
public static void bubbleSort(int[] array){
//i表示趟数
//这是已经经过一次优化的代码,如果不优化,j <array.length-1不减i也可以实现此次排序要求
for (int i = 0; i <array.length-1 ; i++) {
for (int j = 0; j <array.length-1-i ; j++) {
if(array[j] > array[j+1]){
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
}
}
}
}
public static void main(String[] args) {
int[] array = {1,2,3,10,5,6};
bubbleSort(array);
System.out.println(Arrays.toString(array));
}
}
这是已经经过一次优化的代码,如果不优化,j <array.length-1不减i也可以实现此次排序要求。如果还想进行进一步的优化,我们可以考虑加一个标志位来判断数组是否在某一趟后已经有序,则后面的循环冒泡排序过程则可以不去进行。具体代码示例如下:
public class TestDemo {
public static void bubbleSort(int[] array){
boolean flg = false;
//i表示趟数
for (int i = 0; i <array.length-1 ; i++) {
flg = false;
for (int j = 0; j <array.length-1-i ; j++) {
if(array[j] > array[j+1]){
int tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
flg = true;
}
}
if(flg == false){
return;
}
}
}
public static void main(String[] args) {
int[] array = {1,2,3,10,5,6};
bubbleSort(array);
System.out.println(Arrays.toString(array));
}
}
例11:数组逆序。
给定一个数组, 将里面的元素逆序排列。
算法思路: 设定两个下标, 分别指向第一个元素和最后一个元素,交换两个位置的元素,然后让前一个下标自增, 后一个下标自减, 循环继续即可,当前一个下标和后一个下标相遇时,则循环结束,逆序排列实现。
具体代码示例如下:
public class TestDemo {
//就地逆置
public static void reverse(int[] array){
if(array == null){
return;
}
int i = 0;
int j = array.length-1;
while(i < j){
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
i++;
j--;
}
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6};
reverse(array);
System.out.println(Arrays.toString(array));
}
}
例12:数组数字排列。
给定一个整型数组, 将所有的偶数放在前半部分, 将所有的奇数放在数组后半部分。例如:{1, 2, 3, 4},调整后得到{4, 2, 3, 1}。
基本思路: 设定两个下标分别指向第一个元素和最后一个元素,用前一个下标从左往右找到第一个奇数, 用后一个下标从右往左找到第一个偶数, 然后交换两个位置的元素,依次循环即可。当前一个下标和后一个下标相遇时,则循环结束,数组数字排列实现。
具体代码示例如下:
public class TestDemo {
public static void func4(int[] array){
int i = 0;
int j = array.length-1;
while(i < j){
while(i < j && array[i] % 2 == 0){
i++;
}
if(i >= j){
return;
}else{
while(i < j && array[j] % 2 != 0){
j--;
}
if(i >= j){
return;
}else{
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
}
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6,7};
func4(array);
System.out.println(Arrays.toString(array));
}
}
上面这段代码写的太繁琐了,下面我们将这段代码进行一个简化,如果 i < j 了,则 代码里的 i >= j 也进不来,所以可以将 两个if(i >= j) { return; }这段代码都删掉。具体代码示例如下:
public class TestDemo {
public static void func4(int[] array){
if(array == null){
return;
}
int i = 0;
int j = array.length-1;
while(i < j){
while(i < j && array[i] % 2 == 0){
i++;
}
while(i < j && array[j] % 2 != 0){
j--;
}
int tmp = array[i];
array[i] = array[j];
array[j] = tmp;
}
}
public static void main(String[] args) {
int[] array = {1,2,3,4,5,6,7};
func4(array);
System.out.println(Arrays.toString(array));
}
}
3 二维数组
二维数组: 本质上也就是一维数组, 只不过每个元素又是一个一维数组。可借助下图进行理解:
3.1 定义二维数组
如何用程序来定义一个二维数组?
定义二维数组有四种定义方式,具体写法如下所示:
1. 数据类型[ ] [ ] 数组名称 = { {初始化数据},{ },{ } };
//定义了一个n行n列的数组
2. 数据类型[ ] [ ] 数组名称 = new 数据类型 [行数] [列数];
//这里没有初始化,默认为0
3. 数据类型[ ] [ ] 数组名称 = new 数据类型 [行数] [ ];
//列是可以省略的,行不可以省略,叫做不规则的二维数组
4. 数据类型[ ] [ ] 数组名称 = new 数据类型 [ ] [ ] { {初始化数据},{ },{ } };
//这里的列和行都得省略,里面不能有数字
具体代码示例如下:
public static void main(String[] args) {
int[][] array = {{1,2,3},{4,5,6},{7,8,9}};//定义了一个三行三列的数组
int[][] array2 = new int[2][3];//这里没有初始化,默认为0
int[][] array3 = new int[2][];//列是可以省略的,行不可以省略 叫做不规则的二维数组
int[][] array4 = new int[][]{{1,2,3},{4,5,6},{7,8,9}};//这里的列和行都得省略,里面不能有数字
}
3.2 打印二维数组
如何对二维数组进行打印?
对二维数组进行打印有三种打印方式,具体写法如下所示:
1. 第一种是采用for()循环中的fori()的方式进行打印数组;
for (int i = 0; i < 数组名.length ; i++) {
for (int j = 0; j < 数组名 [i].length ; j++) {
System.out.print(array[ i ][ j ] +" "); }
System.out.println(); }
2. 第二种则是采用for()循环中的foreach()的方式进行打印数组;
for (int[ ] arr : 二维数组名) {
for (int x:arr) {
System.out.println(x); }
System.out.println(); }
3. 第三种是利用Arrays.deepToString();方式进行打印数组。
Arrays是一个工具类,专门用来操作数组的工具类。
String 变量 = Arrays.deepToString( 数组名);
System.out.println(变量);
具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int[][] array = {{1,2,3},{4,5,6},{7,8,9}};
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();
}
System.out.println("===================================");
for (int[] arr:array) {//array中的每一个引用是一个数组arr
for (int x:arr) {//arr数组中的引用是一个元素
System.out.println(x);
}
System.out.println();
}
System.out.println("===================================");
String ret = Arrays.deepToString(array);
System.out.println(ret);
}
}
如何采用for()循环中的fori()的方式对不规则二维数组进行打印?
当我们采用for()循环中的fori()的方式进行打印数组时,如果采用以下代码进行打印,则会抛出空指针异常,因为此时我们只能确定出数组的行有几行,而不能确定出数组的列有几列,所以代码在运行到 for (int j = 0; j <array[i].length ; j++)这一行时,会判断不出array[i]的长度,所以就会进行报错。具体代码示例如下:
public class TestDemo {
public static void main(String[] args) {
int[][] array = new int[2][];
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();
}
}
}
对于这个数组的理解,可借助下图进行理解。
代码运行结果如下图所示:
所以我们可以通过下面两种方法对这个问题进行解决,方法一如下面的代码所示:
public class TestDemo {
public static void main(String[] args) {
int[][] array = new int[2][];
array[0] = new int[10];
array[1] = new int[3];
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();
}
}
}
代码运行结果如下图所示:
方法二如下面的代码所示:
public class TestDemo {
public static void main(String[] args) {
int[][] array = new int[2][];
array[0] = new int[]{1,2,3,4,5};
array[1] = new int[]{7,8,9};
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();
}
}
}
代码运行结果如下图所示: