流程控制语句
每一个程序都会有一种执行流程,而每一种编程语言大多都分为了两种流程控制语句:分支结构和循环结构。分支结构包含了多个条件语句,可以选择性的进行执行相应的结构。循环结构主要是为了程序有的时候需要重复的执行某一段的代码。
键盘录入:
java.util.Scanner
键盘扫描器用于从键盘接收数据到程序中。
import java.util.Scanner;//导包
public class Demo{
public static void main(String[] args){
//1.创建一个键盘扫描器
Scanner in = new Scanner(System.in);
//2.提示信息
System.out.println("请输入数据:");
//3.接收数据
int num = in.nextInt();//接收一个整数
double d = in.nextDouble();//接收一个小数
boolean b = in.nextBoolean();//接收布尔值
//接收字符串
String s = in.next();//遇到空白字符结束
char c = in.next().charAt(0);//获取一个字符
String line = in.nextLine();//接收一行字符串,遇到回车键结束,注意如果之前有其他接收方式,会接收之前的回车键
}
}
流程控制语句分类:
-
顺序结构:代码自上而下顺序执行
-
分支结构,选择结构:根据条件执行某些代码或不执行
-
循环结构:重复执行某些代码
分支结构语句if
- if语句格式一
if(条件表达式){ 语句块; } //执行流程:如果小括号内结果为true才会执行其后大括号的代码,否则不执行
执行流程:
①判断条件表达式,(结果:true 或 false)
②结果为true进入 if 语句体
③结果为false跳过语句体
-
if-else语句格式
if(条件表达式){ 语句块1; }else{ 语句块2; } //执行流程:如果小括号内结果为true就执行其后大括号内的代码块1,否则执行代码块2
执行流程:
①条件语句判断(结果:true 或 false)
②结果为true进入语句块1
③结果为false进入语句块2
-
if-else if-else语句格式
if (判断条件1) { 执行语句1; }else if (判断条件2) { 执行语句2; } ... }else if (判断条件n) { 执行语句n; } else { 执行语句n+1; } //执行流程:如果小括号内结果为true就会执行其后大括号内的代码,然后结束整个语句,如果都不为true则执行else代码。
执行流程:
①判断表达式(结果: true或false)
②结果为true进入执行语句1
③结果为false进入第二个判断表达式(结果:true或false)
④第二个结果为true进入语句块2
⑤结果为false进入下个判断条件
⑥…
⑦全部的判断表达式都不满足进入最后的else语句块
-
if语句的嵌套使用
在if语句、else if语句或者else语句块中嵌套使用条件表达式(单分支、双分支、多分支)特点:满足外边的条件表达式才会进入执行嵌套的条件表达式
四、分支结构语句switch语句
- switch语句格式
【】在这里表示可写也可不写
switch(表达式){
case 常量值1:
语句块1;
【break;】
case 常量值2:
语句块2;
【break;】
...
default:
语句块n+1;
【break;】
}
//执行流程:小括号内的结果与case后的常量值相等则执行这个case后的语句块,遇到break结束switch语句。
执行流程:
- 入口(switch)
-
- 从switch进入语句,switch后面括号里的表达式语句会和流程结构中的每一个case后面的常量进行比较。
- 表达式和case后面的语句进行匹配,如果相等就从该case后面的语句开始执行,一直执行到switch最后的语句(遇见break除外),遇见break跳出switch语句
- 如果一个case都不匹配的话会执行最后一个default语句。
- 没有写default直接跳出switch
注意:
(1)switch(表达式)的值的类型只能是(byte,short,int,char,引用:enum(JDK1.5),String(JDK1.7))类型
(2)case后面必须是常量值,而且不能够重复
(3)default默认分支,当所有case值都不匹配时会执行default分支。
(4)break关键字,用于跳出Switch语句,如果case后没有break会继续执行下一个case中的代码,导致switch穿透。
实例:
public class SwitchDemo {
public static void main(String[] args) {
//定义指定的星期
int weekday = 5;
//switch语句实现选择
switch(weekday) {
case 1:
System.out.println("星期一");
break;
case 2:
System.out.println("星期二");
break;
case 3:
System.out.println("星期三");
break;
case 4:
System.out.println("星期四");
break;
case 5:
System.out.println("星期五");
break;
case 6:
System.out.println("星期六");
break;
case 7:
System.out.println("星期日");
break;
default:
System.out.println("你的数字有误");
break;
}
}
}
再例如:
/*
* 需求:指定一个月份,输出该月份对应的季节。
* 一年有四季
* 3,4,5 春季
* 6,7,8 夏季
* 9,10,11 秋季
* 12,1,2 冬季
*/
public class SwitchTest {
public static void main(String[] args) {
//指定一个月份
int month = 5;
switch(month) {
case 1:
case 2:
case 12:
System.out.println("冬季");
break;
case 3:
case 4:
case 5:
System.out.println("春季");
break;
case 6:
case 7:
case 8:
System.out.println("夏季");
break;
case 9:
case 10:
case 11:
System.out.println("秋季");
break;
default:
System.out.println("你输入的月份有误");
break;
}
}
}
-
Switch语句与if语句比较
- if语句的条件是一个布尔类型值,通常根据判断结果进入分支,使用范围更广。
- switch语句的条件是一个常量值(byte,short,int,char,枚举,String),一般条件是几个固定的常量值时使用switch语句。
- 如果根据进入分支的条件是几个固定的常量值,这时使用if和switch都可以,如果条件选项比较多时,适合使用switch语句(效率高)。
循环结构语句
当我们需要进行重复执行某一段代码时,那我们可以使用循环结构语句,避免代码的冗余。
目下学习需要知道的循环前提:
①需要有一个判断进行入循环的条件
②再循环体里面进行代码编写实现我们要做的功能
③要有一个迭代(不断改变的)变量,方便后面出循环
【此外也可以有一个初始化进入循环的变量】
1、for循环语句
一个初始化进入循环的变量、判断循环进行的条件和迭代语句都写在循环结构里面的循环
for循环语句格式:
for(初始化语句①;循环结束的条件②;迭代语句④){
循环体语句③;
}
//执行流程:①②③④ ②③④ ②③④...②
//死循环
for(;;){
//循环体
}
执行流程:
- 执行初始化语句①,通常是给迭代语句的初始化
- 执行判断语句②,判断循环是否继续进行
- 执行循环体③,也就是我们想要重复执行的代码
- 迭代语句④,主要对于执行循环进行的变量的增减
- 最后会执行②再出来
代码示例:
for(int i=0;i<5;i++){
System.out.println(i);
}
//01234
2、while循环语句
一个在循环结构体上只有条件判断表达式的循环体
while循环语句格式:
while(条件表达式①){
循环体语句②
}
//执行流程:①② ①② ①② ...①
执行流程:
- 条件表达式①,判断是否满足循环的条件
- 满足条件进入循环体②,执行我们想要重复执行的代码。
- 【迭代语句】
- 接着执行条件表达式①,不满足了出循环
代码示例:
int i=0;
while(i<5){
System.out.println(i);
i++;
}
//01234
3、do…while循环语句
一个类似于while循环结构表达式的循环语句,和while的主要不同就是do…while是先进行循环在进行条件的判断。
do…while循环结构体语句格式:
do{
//循环体语句①
}while(条件表达式②);
//执行流程:①② ①② ①② ... ②
执行流程:
- 先执行循环结构体①
- 【迭代语句】
- 进入条件判断表达式②,满足条件继续执行①,不满足条件出循环
示例代码:
int i = 0;
do{
System.out.println(i);
i++;
}while(i<5);
//01234
4、循环语句的比较
- 从循环次数角度分析
-
do…while循环至少执行一次循环体语句
-
for和while循环先判断条件,在执行循环,至少执行0次
-
如何选择
-
遍历有明显的循环次数(范围)的需求,选择for循环
-
遍历没有明显的循环次数(范围)的需求,循环while循环
-
如果循环体语句块至少执行一次,可以考虑使用do…while循环
-
本质上:三种循环之间是可以互相转换的,都能实现循环的功能
-
-
三种循环结构都应具有的要素:
-
(1)循环条件
-
(2)循环变量的修改的迭代表达式
-
(3)循环体语句块
- 死循环比较
-
for(;😉{循环体} ,除循环体外不需要执行其他语句,性能略高
-
while(true){ 循环体},除循环体外还需要执行小括号里的表达式
5、循环语句的嵌套使用
在我们平常写代码时经常会遇到一个循环解决不了的情况,这时我们或许可以再循环中在嵌套写一个循环。
- 循环的嵌套:在一个循环体里面在嵌套写一个循环,可以写相同的循环,也可以写其他的循环结构语句。for、while、do…while这三种循环结构语句就可以相互嵌套。
- 比如:
for(初始化语句①; 循环条件语句②; 迭代语句⑦) {
for(初始化语句③; 循环条件语句④; 迭代语句⑥) {
循环体语句⑤;
}
}
例子:
public static void main(String[] args){
for (int i = 0; i < 5; i++) {
for (int j = 0; j <= i; j++) {
System.out.print("*");
}
System.out.println();
}
}
/*
*
**
***
****
*****
*/
break和continue关键字
1、break关键字的应用场景与作用
使用场景:
-
用在switch语句中,用于跳出switch语句
-
用在循环语句中,用于跳出当前循环
for(int i=0;i<5;i++){ if(i==3){ break;//跳出循环 } System.out.println(i); } //012
2、continue关键字的应用场景与作用
使用场景:
-
用在循环语句中,用于结束本次循环,不再执行本次循环余下语句
for(int i=0;i<5;i++){ if(i==3){ continue;//跳出循环 } System.out.println(i); } //0124
数组
一、数组的概念理解
-
数组是什么?:数组就是一个存储(保存)相同类型的数据的一个数据容器,并给这个容器进行命名。这些数据是按照一定的顺序存储在内存当中,给每一个数据都进行编号。
-
数组都包含:
-
数组名:标识符,通过数组名来使用数组
-
元素:数组中存储的每个数据
-
长度:数组可以存储的元素个数
-
索引:下标:从0开始的编号,用于访问数组中的元素
-
-
数组的特点:
- 数组创建之后长度不可改变
- 数组是在内存中一块连续的内存空间
- 数组有索引,通过索引快速访问元素
- 数组可以存储基本数据类型和引用数据类型元素。
二、 数组声明与初始化
没有声明创建的数组无法使用,无论是什么都必须要有才可以使用吧!数组必须先声明并初始化才能使用
1、数组声明
声明格式:
数据类型[] 数组名;
int[] nums;//声明一个元素为int类型的数组
String[] names;//声明了一个元素为String类型的数组
//不推荐
int age[];
2、数组的初始化
数组的初始化方式分为两种:静态数组的初始化和动态数组的初始化。
- 静态初始化:在定义数组的同时为数组分配内存空间,并且赋值。程序员可以给出元素初始值,长度由系统决定
初始化格式:
- 数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3,…};
- 数据类型[] 数组名;
数组名 = new 数据类型[]{元素1,元素2,元素3,…}; - 数据类型[] 数组名 = {元素1,元素2,元素3,…};
例如:
int[] nums;
nums = new int[]{11,22,33};
int[] arr = new int[]{11,22,33};
//简化写法
int[] arr = {11,22,33};
- 动态初始化:前面先创建一个数组,并指定数组的长度,后面在向数组里面传入值(但是一开始系统会给出默认值)。
初始化格式:
- 数据类型[] 数组名 = new 数据类型[长度];
- ①数据类型[] 数组名;
- ②数组名 = new 数据类型[长度];
例如:
String[] names;
names = new String[3];
double[] arr = new double[4];
三、数组元素的访问与数组遍历
将数据存入数组中后我们需要将数据取出来,这时我们需要遍历获取数组中的每一个元素。往往可以使用循环语句进行遍历。
1、 数组元素的访问
数组的访问需要先获取需要访问的索引位置。
- 索引:数组存取数据的下标(编号)。数组存数据时,每存一个元素都会为数据添加一个编号,从0开始,而这个编号就是数组的索引,可以通过这个索引获取值。
- 索引的取值范围:[0,数组长度-1]
- 格式:数组名[索引] 【= 值】
访问数组元素:
- 访问数组指定索引元素:数组名[索引下标];
- 为数组指定索引赋值:数组名[索引] = 值;
例如:
int[] arr = {11,22,33};
int num = arr[0];//获取元素值
arr[1] = 100;//给元素赋值
2、数组的长度
数组名.length
3、 数组的遍历
通常数组的遍历都是采用循环结构进行遍历。
例如:
int[] arr = {11,22,33};
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
四、数组的默认初始值
当动态初始化时,数组元素有系统给出默认初始值,不同元素类型的数组,默认初始值不同:
五、数组的内存分析
内存概述
每台计算机都会有内存,内存是计算机的重要组件之一,它也是和Cpu进行沟通的桥梁。内存的作用是用来暂时存放CPU中的运算数据,以及与硬盘等外部存储器等进行交换的数据。当计算机运行中,CPU就会把需要进行运算的数据调到内存中进行运算,运算后CPU把结果传送出来。我们写的程序就是在内存中运行的,运行结束后在清空内存。
JVM内存分析
Java虚拟机要运行程序,必须要对得到的内存进行空间的分配和管理。
Java虚拟机为了更高效的运算,对得到的内存空间进行了划分,其中的每一块区域都有特定的处理数据方式和内存管理方式。
内存模块分析:
区域名称 | 作用 |
---|---|
程序计数器 | 程序计数器是CPU中的寄存器,它包含每一个线程下一条要执行的指令的地址 |
本地方法栈 | 当程序中调用了native的本地方法时,本地方法执行期间的内存区域 |
方法区 | 存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 |
堆内存 | 存储对象(包括数组对象),new来创建的,都存储在堆内存。 |
虚拟机栈 | 用于存储正在执行的每个Java方法的局部变量表等。 局部变量表存放了编译期可知长度的各种基本数据类型、对象引用,方法执行完,自动释放。 |
数组内存分析
一个数组内存图
public static void main(String[] args) {
int[] arr = new int[3];
System.out.println(arr);//[I@5f150435
}
思考:打印arr为什么是[I@5f150435,它是数组的地址吗?
答:它不是数组的地址。
问?不是说arr中存储的是数组对象的首地址吗?
答:arr中存储的是数组的首地址,但是因为数组是引用数据类型,打印arr时,会自动调用arr数组对象的toString()方法,默认该方法实现的是对象类型名@该对象的hashCode()值的十六进制值。
问?对象的hashCode值是否就是对象内存地址?
答:不一定,因为这个和不同品牌的JVM产品的具体实现有关。例如:Oracle的OpenJDK中给出了5种实现,其中有一种是直接返回对象的内存地址,但是OpenJDK默认没有选择这种方式。
六、数组的相关算法
- 统计相关算法:比如累加求和,求平均值,求偶数个数等
例如:
int[] arr = {4,5,6,1,9};
//求总和、均值
int sum = 0;//因为0加上任何数都不影响结果
for(int i=0; i<arr.length; i++){
sum += arr[i];
}
//均值
double avg = (double)sum/arr.length;
- 查找最值及位置:查找数组中值最大或最小的元素及其位置
例如:
int[] arr = {4,5,6,1,9};
//找最大值
int max = arr[0];
for(int i=1; i<arr.length; i++){
if(arr[i] > max){
max = arr[i];
}
}
System.out.println(max); //最大值
-
查找指定元素及位置:
- 顺序查找
- 二分查找(前提是排序的)
顺序查找就是按照数组元素的顺序进行查找,这个找不到,下标加一(减一)去找
例如:
//查找value第一次在数组中出现的index
public static void main(String[] args){
int[] arr = {4,5,6,1,9};
int value = 1;
int index = -1;
for(int i=0; i<arr.length; i++){
if(arr[i] == value){
index = i;
break;
}
}
if(index==-1){
System.out.println(value + "不存在");
}else{
System.out.println(value + "的下标是" + index);
}
}
//找最大值和最大值所在的位置
int[] arr = {4,9,5,6,1,9};
//找最大值
int max = arr[0];
for(int i=1; i<arr.length; i++){
if(arr[i] > max){
max = arr[i];
}
}
//遍历数组,看哪些元素和最大值是一样的
for(int i=0; i<arr.length; i++){
if(max == arr[i]){
System.out.print(i+"\t");
}
}
二分查找,也称为折半查找,前提条件是有序的数组
二分查找就是在每次都是先拿中间索引的那个数和要查找的那个数进行对比(排好序),要找的数如果大于中间的数就找后一半,小于找前一半,再次和中间四分之一的数进行比较,以此类推直到找到。
示例:
class Exam2{
public static void main(String[] args){
int[] arr = {2,5,7,8,10,15,18,20,22,25,28};//数组是有序的
//二分查找
int num = 18;//目标值
int index = -1;//目标的位置默认为-1表示不存在
int left = 0;//左边位置默认为最小索引位置
int right = arr.length - 1;//右边位置默认为最大索引位置
int mid;//中间位置
//循环查找,每次查找left与right之间的数据,并比较中间位置元素与目标元素的大小
do {
//每次计算中间位置
mid = (left + right) / 2;
if (num == arr[mid]) {//目标元素等于中间位置元素时,找到
index = mid;//保存找到的元素位置
break;//跳出循环
}
if (num > arr[mid])//目标元素比中间位置元素大时
left = mid + 1;//修改新的左边位置
if (num < arr[mid])//目标元素比中间位置元素小时
right = mid - 1;//修改新的右边位置
} while (left <= right);//当左右位置重合时说明已经找遍了整个数组。
if(index==-1){
System.out.println(value + "不存在");
}else{
System.out.println(value + "的下标是" + index);
}
}
}
- 数组的反转:把数组的元素前后倒置
数组反转可以有两种方式实现,①是借助另一个和元素相同类型的变量,临时存储其中一个元素,来进行前后交换。②直接创建一个新的数组,然后往新数组中反向赋值。
-
数组的排序:
实际开发过程中经常会遇到对数组元素进行排序的情况,由此有很多的排序算法,在这里只进行了其中两种进行演示。-
冒泡排序:相邻的元素两两比较,大的交换到后面,每轮确定一个最大元素到最好位置,依次进行多轮比较。
int[] arr = {44,12,36,7,9,1}; for(int i=0;i<arr.length-1;i++){ //总共需要比较n-1轮 for(int j=0;j<arr.length-1-i;i++){ //每轮比较数组长度减i次 if(arr[j]>arr[j+1]){ int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } }
-
冒泡排序分析图:
- 直接选择排序:先假设第一个是最小的元素,每轮拿下一个位置元素与本轮最小的元素交换位置,即每轮确定一个最小元素在第一个位置,依次比较多轮
//原数组 int[] arr = {31, 20, 19, 6, 29, 10, 99, 2, 77}; //外循环控制比较轮数 for (int j = 0; j < arr.length - 1; j++) { //比较n-1轮 //第一轮比较 int minIndex = j;//假定本轮第一个元素时最小元素位置 for (int i = j + 1; i < arr.length; i++) { //剩下的每一个元素都与最小的数进行比较 if (arr[i] < arr[minIndex]) { minIndex = i;//记录本轮最小元素位置 } } if (j != minIndex) {//表示本轮第一个元素不是最小元素 //交换本轮第一个元素与最小元素位置 int temp = arr[j]; arr[j] = arr[minIndex]; arr[minIndex] = temp; } }
直接选择排序分析图:
- 数组的扩容
有的时候我们会想要增加更多的元素,而原来的数组里面的容量已经最大了,数组的容量又不能改变。那么怎么存入更多的数据呢?创建新的数组又不太利于维护。那么在这时就可以使用数组的扩容。
数组的扩容:简单来说就是新建一个数组,新数组的长度达到我们需要的要求,我们再把原来数组的值一个个赋值给新的数组(注意:创建了新的数组,新数组的内存地址与原来数组的地址不同)。
示例:当原来的数组长度不够了需要扩容,例如需要新增位置来存储10
int[] arr = {1,2,3,4,5,6,7,8,9};
//如果要把arr数组扩容,增加1个位置
//(1)先创建一个新数组,它的长度 = 旧数组的长度+1,或者也可以扩大为原来数组长度的1.5倍,2倍等
int[] newArr = new int[arr.length + 1];
//(2)复制元素
//注意:i<arr.length 因位arr比newArr短,避免下标越界
for(int i=0; i<arr.length; i++){
newArr[i] = arr[i];
}
//(3)把新元素添加到newArr的最后
newArr[newArr.length-1] = 10;
//(4)如果下面继续使用arr,可以让arr指向新数组
arr = newArr;
//(4)遍历显示
for(int i=0; i<arr.length; i++){
System.out.println(arr[i]);
}
(1)至于新数组的长度定义多少合适,看实际情况,如果新增的元素个数确定,那么可以增加指定长度,如果新增元素个数不确定,那么可以扩容为原来的1.5倍、2倍等
(2)数组扩容太多会造成浪费,太少会导致频繁扩容,效率低下
- 数组元素的插入
数据的插入就是在指定索引的位置上插入一个元素,如果数组还有空位置,这时我们可以将数组后面的元素都往后移一位,再把这个元素插入指定的索引处。如果数组已经没有空位置了我们需要新建一个数组,再将元素插入进去。
例如:
//在索引index位置插入一个整数5
int index=1;
int[] arr={1,2,3,4};
//创建新数组扩容
int[] newArr=new int[5];
//复制数组
for (int i = 0; i < arr.length; i++) {
newArr[i]=arr[i];
}
//向后移动插入位置之后的每个元素
for (int i = newArr.length - 1; i >= 0; i--) {
if(i>index){
newArr[i]=newArr[i-1];
}
}
//插入新元素
newArr[index]=5;
arr=newArr;
//遍历显示
for(int i=0; i<arr.length; i++){
System.out.println(arr[i]);
}
七、数组工具类
Java提供了现成的工具类,方便实现数组的相关操作。
java.util.Arrays数组工具类,提供了很多静态方法来对数组进行操作,而且如下每一个方法都有各种重载形式,以下只列出int[]类型的,其他类型的数组类推:
-
static int binarySearch(int[] a, int key) :要求数组有序,在数组中查找key是否存在,如果存在返回第一次找到的下标,不存在返回负数
-
static int[] copyOf(int[] original, int newLength) :根据original原数组复制一个长度为newLength的新数组,并返回新数组
-
static int[] copyOfRange(int[] original, int from, int to) :复制original原数组的[from,to)构成新数组,并返回新数组
-
static boolean equals(int[] a, int[] a2) :比较两个数组的长度、元素是否完全相同
-
static void fill(int[] a, int val) :用val填充整个a数组
-
static void fill(int[] a, int fromIndex, int toIndex, int val):将a数组[fromIndex,toIndex)部分填充为val
-
static void sort(int[] a) :将a数组按照从小到大进行排序
-
static void sort(int[] a, int fromIndex, int toIndex) :将a数组的[fromIndex, toIndex)部分按照升序排列
-
static String toString(int[] a) :把a数组的元素,拼接为一个字符串,形式为:[元素1,元素2,元素3。。。]
示例代码:
import java.util.Arrays;
import java.util.Random;
public class Test{
public static void main(String[] args){
int[] arr = new int[5];
// 打印数组,输出地址值
System.out.println(arr); // [I@2ac1fdc4
// 数组内容转为字符串
System.out.println("arr数组初始状态:"+ Arrays.toString(arr));
Arrays.fill(arr, 3);
System.out.println("arr数组现在状态:"+ Arrays.toString(arr));
Random rand = new Random();
for (int i = 0; i < arr.length; i++) {
arr[i] = rand.nextInt(100);//赋值为100以内的随机整数
}
System.out.println("arr数组现在状态:"+ Arrays.toString(arr));
int[] arr2 = Arrays.copyOf(arr, 10);
System.out.println("新数组:" + Arrays.toString(arr2));
System.out.println("两个数组的比较结果:" + Arrays.equals(arr, arr2));
Arrays.sort(arr);
System.out.println("arr数组现在状态:"+ Arrays.toString(arr));
}
}
八、二维数组(理解)
- 二维数组:本质上还是一个一维数组,只不过每个元素上都是一个一维数组
- 知道行和列
元素的数据类型[][] 二维数组名 = new 元素的数据类型[m][n];
m:表示这个二维数组有多少个一维数组。或者说一共二维表有几行
n:表示每一个一维数组的元素有多少个。或者说每一行共有一个单元格
- 给二维数组赋值
二维数组名[行下标][列下标] = 值;
二维数组的声明与初始化:
-
声明格式:
数据类型[][] 数组名;
-
初始化格式:
-
静态初始化
数据类型[][] 数组名 = new 数据类型[][]{{元素1,元素2,..},{元素1,元素2,..},...}; //简化写法 数据类型[][] 数组名 = {{元素1,元素2,..},{元素1,元素2,..},...};
//声明并初始化二维数组 int[][] arr = {{1},{2,3},{4,5,6}};
-
动态初始化
数据类型[][] 数组名 = new 数据类型[M][N];//M表示二维数组的长度,N表示一维数组长度(可选) 数据类型[][] 数组名 = new 数据类型[M][];//表示二维数组的长度为M,其中每个一维数组未初始化
int[][] arr = new int[3][2];//三行两列 int[][] arr1 = new int[3][];//三行,每行列数未确定 arr1[0] = new int[]{1,2,3}; arr1[1] = new int[]{4,5}; arr1[2] = new int[]{6};
-
二维数组元素访问与遍历:
int[][] arr = new int[3][2];
//给元素赋值
arr[0][0] = 1;
arr[0][1] = 2;
arr[1][0] = 3;
arr[1][1] = 4;
arr[2][0] = 5;
arr[2][1] = 6;
//遍历二维数组
//获取每个一维数组
for(int i=0;i<arr.length;i++){
for(int j=0;j<arr[i].lenght;j++){
System.out.print(arr[i][j]+" ");
}
System.out.println();
}
4、二维数组的内存分析
附加:
折半插入排序:
例如:数组{12,2,6,1,5}
第一次:在[0,1)之间找插入2的位置==>left = [0] ==> {2,12,6,1,5}
第二次:在[0,2)之间找插入6的位置==>left = [1] ==> {2,6,12,1,5}
第三次:在[0,3)之间找插入1的位置==>left = [0] ==>{1,2,6,12,5}
第四次:在[0,4)之间找插入5的位置==>left = [2] ==>{1,2,5,6,12}
@Test
public void test(){
int[] arr = {12,2,6,1,5};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public void sort(int[] arr){
for (int i=1; i<arr.length; i++){
//找到[0,i)之间插入arr[i]的位置
//使用二分查找法
int left = 0;
int right=i-1;
while (left<=right){
int mid = (left + right)/2;
if(arr[i]<=arr[mid]){
right = mid - 1;
}else{
left = mid + 1;
}
}
//在[0,i)插入arr[i]
if(left < i){
int temp = arr[i];
System.arraycopy(arr,left,arr,left+1,i-left);
arr[left] = temp;
}
}
}
快速排序
例如:数组{5, 2, 6, 12, 1,7,9}
@Test
public void test(){
int[] arr = {5, 2, 6, 12, 1,7,9};
sort(arr,0, arr.length-1);
System.out.println(Arrays.toString(arr));
}
//将[start+1,end]之间的元素分为两拨,左边的所有元素比arr[start]小,右边的所有元素比arr[start]大
public void sort(int[] arr, int start, int end) {
if (start >= end) //无需排序
return;
int left = start + 1;
int right = end;
int pivot = arr[start];//基准元素
while (left <= right) {
//从左往右,从[start+1]开始找比基准元素大的数arr[left],让它与arr[right]交换
//当arr[left]大于arr[start]就停止循环,因为此时找到了比基准元素大的数arr[left]
while (left <= right && arr[left] <= pivot) {
//必须先判断left<=right,否则可能数组索引越界异常。
left++;
}
//从右往左,从[end]开始找比基准元素小的数arr[right],让它与arr[left]交换
//当arr[right]小于基准元素就停止循环,因为此时找到了比基准元素小的数arr[right]
while (right >= left && arr[right] >= pivot) {
right--;
}
//交换左右的两边的元素,然后left++ ,right--,即从下一个位置继续查找
if (left < right) {
int temp = arr[left];
arr[left++] = arr[right];
arr[right--] = temp;
}
}
//循环结束,left与right已经重合,甚至交错,这时交换基准元素与right位置元素
arr[start] = arr[right];
arr[right] = pivot;
//此时[start,right-1]之间都是比arr[start]小的数据了,但是它们还未排序
//此时[right+1,end]之间都是比arr[start]大的数据了,但是它们还未排序
//所以需要分别对[start,right-1]、[right+1,end]之间元素重复上面的操作继续排序
sort(arr, start, right - 1);
sort(arr, right + 1, end);
}
快速排序分析图: