写在前面:
- Focus
- Keep your mind clean
- Beginners mind.
- No Ego.
- There is no career goal.
- Shut up.
- Mindfulness. Care. Awareness
- There is no Boss
- Do something else
- There is nothing special.
上一篇文章:JavaSE学习笔记1
文章目录
6 方法
6.0 定义
方法的就是将一段具有独立功能的代码给提炼出来
语法格式
访问权限修饰符 方法类型 返回值类型 方法名(数据类型 数据1, 数据类型 数据2) {
代码...
return 值;
}
方法名与参数组成了方法签名
- 访问权限修饰符
修饰词 本类 同一个包的类 继承类 其他类 private
√ × × × 空
(默认)√ √ × × protected
√ √ √ × public
√ √ √ √ - 方法类型
native
:本地方法static
:静态方法synchronized
:同步方法
- 返回类型
调用方法后返回一个值的类型,也可能为void(空)
- 方法名
调用此方法所使用的名称 - 参数列表
调用此方法需要传入的数据,调用时传入的参数称之为实参,方法定于来用于接收的参数称之为形参 - 方法体
方法中的代码
方法根据参数与返回值来说,可以分为以下几类:
- 无参数无返回值
- 有参数有返回值
- 有参数无返回值
- 无参数有返回值
6.1 方法运行原理
Java中方法是基于 栈(LIFO) 运行的
进入栈中的每一个方法又被称之为栈帧,栈帧包含了方法的定义与方法签名
方法在运行开始时将会被加载到栈的顶部 - 入栈
方法在运行结束时将会从栈的顶部给移出 - 出栈
当一个方法在运行时如果有另一个方法被调用就会在栈顶加入新的栈,而停止当前方法,直至新方法运行结束,出栈,然后继续运行当前方法
6.2 方法的重载
同一个类中可以用多个同名的方法,这被称之为重载(Overload)
只要是参数不一样的两个同名方法都可以重载,参数不一样指参数在同顺序上类型与数量不同
举个栗子
void show(int a, float b, char c) {} //方法1
void show(float a, int a, char c) {} //该方法重载了方法1
void show(int a, int b, int c) {} //该方法重载了方法1
void show(int a, int b) {} //该方法重载了方法1
void show() {} //该方法重载了方法1
int show(int b, float a, char c) {} //该方法未重载了方法1
调用一个具有重载的方法时的流程:
- 寻找与给定参数类型完全匹配的方法,如果存在就调用,不存在进行下一步
- 依次寻找参数与对应重载方法接收参数兼容的方法,如果存在唯一一个就调用,如果存在多个可能会报错(引用不明确)
6.3 递归
方法自身调用自身
这句话很经典:人用迭代,神用递归
- 迭代可以解决的问题,都可以用递归解决,反过来可能就行不通了
- 递归会反复调用自身,可能会加大内存的开销
- 同样的问题,递归可能比迭代代码量少
递归一定需要一个结束条件,作为递归的边界来结束递归,否则就会一直递归无法退出
6.4 递归的两个经典问题
6.4.0 斐波拉契数列
斐波拉契数列指第1、2项是1,之后的每一项是前两项的和
分析:
设第n
项为f(n)
当求f(n)
,意味求f(n - 1) + f(n - 2)
求f(n - 1)
,意味求f(n - 2) + f(n - 3)
…
直到求f(1)、f(2)
时等于1
public class Sample {
public static void main(String[] args) {
System.out.println(fibo(35));
System.out.println(fibo2(35));
}
public static int fibo(int n) {
if (n == 1 || n == 2) {
return 1;
} else {
return fibo(x - 1) + fibo(x - 2);
}
}
public static int fibo2(int n) {
if (n == 1 || n == 2) {
return 1;
}
int a = 1;
int b = 1;
int c = 0;
for (int i = 3; i <= n; i++) {
c = a + b;
a = b;
b = c;
}
return c;
}
}
总结:
使用递归与迭代相比减少了代码量,使用了更多的内存与时间,随着n
的增加,使用的内存与时间更会显著体现,因此不是任何情况使用递归逗比迭代好,但是,接下来的一个问题就基本只能用递归来实现
6.4.1 汉诺塔问题
有三根杆子A,B,C。A杆上有 N 个 (N>1) 穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至 C 杆:
- 每次只能移动一个圆盘;
- 大盘不能叠在小盘上面。
问:如何移?
分析:
先来看1个圆盘(在A上)的情况,只需要将这个圆盘从A直接移动到C
再来看2个圆盘的情况,先将A上面圆盘从A移到B,然后将A上的圆盘(此时A只有一个圆盘)移到C,最后将B上的圆盘移到C
之后再来看看3个圆盘的情况,这时我们从另一个角度考虑,将A上的圆盘是从上往下从小到大的,最后到C上的时候,最下方的圆盘也应该是最大的,我们就先考虑如果将A最下面最大的圆盘移动到C上面。
此时,我们需要将A上面除最大圆盘外的2个圆盘移动到B,然后就可以将最大的圆盘从A移到C上了,那上面两个圆盘如何移动到B呢?这就是我们上面所说的2个圆盘的情况,只是我们的目标从C变成了B。
这时,B上有两个圆盘,C上有最大的一个圆盘,这时候我们就可以借助A将B上的圆盘移动到C。
现在来归纳和推广下上面的情况,
假设现在在A上面有n个圆盘,我们想要移动最下面那个圆盘,就需要将上面n-1个圆盘借助C全部移动到B上面,然后就可以将那个最大的圆盘移动到C上面了,接着我们需要将现在在B上面n-1个的圆盘借助A移动到C上面,那现在如果将B上面n-1个圆盘借助A移动到C上面呢?我们需要先将n-2个圆盘通过C移到A圆盘,然后就可以重复上面的操作了
下面是程序示例:
public class Demo81 {
public static void main(String[] args) {
String begin = "A";
String mid = "B";
String end = "C";
hano(3, begin, mid, end);
}
public static void hano(int level, String begin, String mid, String end) {
if (level == 1) {
System.out.println(begin + " -> " + end);
} else {
hano(level - 1, begin, end, mid);
System.out.println(begin + " -> " + end);
hano(level - 1, mid, begin, end);
}
}
}
7 数组
7.0 数组的概念
数组是Java中一种最简单的数据结构,数组用于存储已知长度且类型相同元素集合,一个数组就是一个对象
内存中的情况
栈:主要用于存储运行中的方法
堆:主要用于存储数据的对象
数组本质上就是在堆内存中开辟一系列地址连续且空间大小相等的存储空间,每一个存储空间用来存储数据(基本,引用)
数组是存储在堆内存中,并且在堆内存中存储的数据都有默认初始化的行为。所以数组创建之初,每一个存储空间里面都会被JVM初始化该数据类型对应的零值。
数组的地址是连续的,所以通过公式:An=A1+(n-1)*d
可以快速访问到其他的元素,所以对于数组而言查找元素比较快的。将元素的真实物理地址转换成对应的角标获取元素。
如何来调用数组呢?通过一个变量存储该数组在堆内存当中的首元素的地址。
当数组一旦定义出来,其长度不可变,存储空间的内容是可变的所以我们在定义数组的时候,要么把长度固定,要么直接输入相关的元素。
7.1 数组的定义
三种方法
//创建一个指定长度且指定数据类型的一维数组,名称为数组名,虽然没有指定元素,但是会有默认值
数据类型[] 数组名 = new 数据类型[长度];
//创建一个指定元素且指定数据类型的一维数组,名称为数组名,虽然有指定元素,还是有默认初始化这个步骤的!
数据类型[] 数组名 = new 数据类型[]{数据1,数据2,...,数据n};
数据类型[] 数组名 = {数据1,数据2,...,数据n};
数组的索引是从0开始的,因此一定要注意索引的使用,索引等于或大于数组长度就会引发索引越界
7.2 数组的遍历
两种方法
- 依靠索引的for循环,可以改变原数组的内容
- 使用foreach循环,只是简单的读取数组的内容
public class Sample {
public static void main(String[] args) {
int[] arr = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
for (int i = 0; i < arr.length; i++) {
arr[i] = arr[i] * 10;
System.out.println(arr[i]);
}
for (int num : arr) {
num = num / 10;
System.out.println(num);
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
}
注意:在数组中获取长度用数组名.length
,在字符串获取长度用字符串名.length()
7.3 数组的最值
public class Sample {
public static void main(String[] args) {
int[] arr = new int[]{3, 6, 8, 2, 9, 4, 5, 1, 7};
int min = arr[0];
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] < min) {
min = arr[i];
}
if (arr[i] > max) {
max = arr[i];
}
}
System.out.println(max);
System.out.println(min);
}
}
7.4 数组的扩容
注意:数组长度在创建后就无法更改,因此,此处的扩容是伪扩容
public class Sample {
public static void main(String[] args) {
int[] arr = new int[]{1, 2, 3, 4, 5};
arr = add(arr, 6);
arr = add(arr, 6);
arr = add(arr, 6);
arr = add(arr, 6);
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
//在指定的数组arr中添加元素element
public static int[] add(int[] arr, int element) {
int[] newArr = new int[arr.length + 1];
for (int i = 0; i < arr.length; i++) {
newArr[i] = arr[i];
}
newArr[newArr.length - 1] = element;
return newArr;
}
}
7.5 数组元素的排序
7.5.0 选择排序算法
从数组的以第一个元素为基准开始,依次比较除该元素外剩余的元素,如果有比它小的数,就交换,直到最后一个元素;然后以第二个元素为基准进行比较,一直到倒数第二个元素为基准比较最后一个元素结束得出结果。
public static void selectionSort(int[] arr){
//按照上述分析,会以第一个为基准起到倒数第二个为止,因此有 i < arr.length-1
for (int i = 0; i < arr.length - 1; i++) {
int min = i;
//每一次比较都会从基准的下一个元素开始,直至最后一个元素
for (int j = i + 1; j < arr.length; j++) {
//这里换了一种思想,遇到较小的数保留,直至遇到最小的数
if (arr[min] > arr[j]) {
min = j;
}
}
//将最小的数交换到基准数的位置
if (i != min) {
int tmp = arr[i];
arr[i] = arr[min];
arr[min] = tmp;
}
}
}
7.5.1 冒泡排序算法
从第一个数开始,逐步比较两个数,让小数在前,大数在后。每一轮比较完毕之后,都会将本轮最大的数放置到数组末尾,下次就可以只比较从第一个数到上一次比较的末尾减1这个数(因为上一次比较的最大的数一定大于这一轮所有的数),直至到最左边两个最小的数的比较结束为止。
public static void bubbleSort(int[] arr) {
//每一次确定最后一个数,需要确定数组总数-1个数
for (int i = 0; i < arr.length - 1; i++) {
//从索引为0开始,依次往后两两比较,让小数在前,大数在后,直到上一次比较数减一
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr [j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
7.5.2 插入排序算法
从第二个数开始,取出来对比它前面所有的数,如果存在一个位置,左边的数比它小,右边的数比它大,那么这个数就应该在这个位置,特别的,如果遇到一个数比较到左边已经没有比它小的数的时候,这个数就将放置到最左边,而从这个位置开始的所有数都将往后位移一位,直到最后一个数放置完成。
public static void insertionSort(int[] arr) {
//从第二个数开始比较
for (int i = 1; i < arr.length; i++) {
//保留比较的数值
int cur = arr[i];
int j = i;
//比较它左边值的和当前值的大小,如果大就将左边值往有移动
while (j > 0 && arr[j - 1] > cur) {
arr[j] = arr[j - 1];
j--;
}
//直到它左边值比当前值小,那就让这个位置等于我们当前值
//因为所有比它大的值都往右移了一位,所以当前位置就符合左边的数比它小,右边的数比它大
arr[j] = cur;
}
}
7.5.3 计数排序算法
计数排序属于桶排序的一种,只针对整数。将最小数与最大数作为边界,然后构建一个新的表示从最小值开始到最大值依次递增1的数值出现次数的数组,例如有待排序数组{2, 5, 8, 11, 6, 7, 13, 1, 5, 4},最小值为1,最大值为13,就将构建一个拥有(13-1+1)元素的数组,使用索引为0的元素来表示最小值1,使用索引为12的元素来表示最大值13,如果要存放数值5就使用(5-1)+0找到对应索引号,然后加一,然后将各个整数在待排序数组中出现的次数统计到新的数组中,就排序完成了。
这种排序方式不直接使用比较去排序,而是依靠数组的索引,使用大量的空间去换取时间来进行排序
public static void countingSort(int[] arr) {
//找出最大与最小值
int min = arr[0];
int max = arr[0];
for (int i : arr) {
if (i < min) {
min = i;
}
if (i > max) {
max = i;
}
}
//构建一个用于存放数值出现次数的临时数组
int[] tmp = new int[max - min + 1];
//将所有数按照其值推算出索引,并将其索引对应的值加一
for (int value : arr) {
tmp[value - min]++;
}
//将所有数覆盖原数组
int k = 0;
for (int i = 0; i < tmp.length; i++) {
for (int j = 0; j < tmp[i]; j++) {
arr[k++] = min + i;
}
}
}
7.5.4 基数排序算法
基数排序也是属于桶排序的一种,只针对整数。首先找出待排数组中最大值找到,判断其位数,然后将所有元素的个位按照0~9的顺序分别存放到对应的队列(FIFO) 中,然后依次按照0~9的顺序从队列里弹出元素(以下示意图中,从下往上),并按序保存到原数组中,然后根据十位归位,直到比较到最大值的位数为止。
public static void radixSort(int[] arr) {
//找出最大值的位数
int max = arr[0];
for (int i : arr) {
if (i > max) {
max = i;
}
}
int maxLength = (max + "").length();
//创建一个队列,长度为10来存放某位为0~9的数
LinkedList<Integer>[] bucket = new LinkedList[10];
for (int i = 0; i < bucket.length; i++) {
bucket[i] = new LinkedList<Integer>();
}
//从个位开始直到最高位结束,依次归位
for (int count = 0; count < maxLength; count++) {
//依次将数组中的元素分类
for (int i = 0; i < arr.length; i++) {
//arr[i] / (int) Math.pow(10, count) % 10 可以判断当前数在当前判断位下位的数值
//比如说15,判断十位数:15 / Math.pow(10, 1) % 10 = 15 / 10 % 10 = 1
//就将其存放至索引为1的队列中去
bucket[arr[i] / (int) Math.pow(10, count) % 10].add(arr[i]);
}
//将整理好的队列依次弹出每一个元素,顺序的放回到原数组中
int j = 0;
for (int i = 0; i < bucket.length; i++) {
while (!bucket[i].isEmpty()) {
arr[j++] = bucket[i].poll();
}
}
}
}
7.6 数组元素的查找
7.6.0 二分查找算法
前提:需要查找的数组元素是有序的
如果我们要查找一元素,先看数组中间的值V和所需查找数据的大小关系,分三种情况:
- 等于所要查找的数据,直接找到
- 若小于 V,在小于 V 部分分组继续查询
- 若大于 V,在大于 V 部分分组继续查询
public static int binarySearch(int[] arr, int find) {
//首先确定开始索引、结束索引和二分(中间)索引
int min = 0;
int max = arr.length;
int cur = (min + max) / 2;
//判断二分索引所指向的值是不是需要查询的值
//如果不是继续寻找
while (arr[cur] != find) {
//找到开始索引和结束索引为相同值,二分索引指向的都不是需要寻找的值时,
//那寻找的值在该数组中就不存在
if (min == max) {
return -1;
}
//如果寻找值大于当前二分索引值,说明需要寻找的值在当前二分索引值的右边
//所以开始索引调整至当前二分索引加一
//如果寻找值小于当前二分索引值,说明需要寻找的值在当前二分索引值的左边
//所以结束索引调整至当前二分索引减一
if (find > arr[cur]) {
min = cur + 1;
} else {
max = cur - 1;
}
//将其当前索引调整至当前开始索引与结束索引的中间
cur = (min + max) / 2;
}
return cur;
}
7.7 可变长参数列表
//可变长参数列表写法
变量类型名... 参数名
//调用时可将可变长参数列表作为数组使用
参数名[索引]
//例如sort方法:
void sort(int... num) {};
//可以使用如下语句调用
sort();
sort(1);
sort(1, 2);
sort(new int[]{1, 2});
//注意:
//使用可变长参数列表可向其传递数组,但是使用数组不能向其传递多个参数
7.8 二维数组
二维数组,本质上就是一个一维数组,只不过该一维数组里面的元素是另一个一维数组
定义
//第一种
数据类型[][] 矩阵名 = new 数据类型[row][col];
//第二种
数据类型[][] 矩阵名 = new 数据类型[][] {
{...},
{...},
{...}
};
//第三种
数据类型[][] 矩阵名 = {
{...},
{...},
{...}
};
更多内容: JavaSE学习笔记1 | JavaSE学习笔记3