一、Day6部分复习(重中之重)
1. 方法递归(程序结构):函数自身调用自身。在递归函数的内部,有调用了函数本身的操作,称之为递归函数
2. 使用递归的条件
(1)原问题可以拆分为多个子问题的解
(2)拆分后的子问题与原问题之间除了数据规模不同外,其他解决思路完全相同
(3)存在递归的终止条件(不可无限拆分问题。)
注意:终止条件:无须借助其他方法或语句,当前场景下就能立即得出问题的解,该条件就是递归终止条件。例如下列例子
3. 写出正确递归代码的技巧:抓住函数的语义(语义:这个函数是干什么用的),假设这个函数已经实现完成,在当前场景下只需要利用好这个函数辅助解决问题即可
二、斐波那契数列和汉诺塔(递归实现)
1. 斐波那契数列
(1)思路:
(2)代码实现
2. 上述代码运行时间会随着求解项数的增加而极具上升原因如下
原理:上图中,每一个节点代表着函数的一次执行,而运行时间和函数的执行次数成正比,因此这个问题转化成了上述二叉树节点个数的求解。标号为N的二叉树中最多有2^N-1个节点,因此时间复杂度为(2^N) 。因此当N变化很小的时候,运行时间会指数级增长
3. 解决方法
(1)解决方法一:记忆化搜索(将递归过程中,每个计算后的数字保存起来)
A. 原理:如上图所示,F(2)/F(3)被重复计算拖慢了整个函数的运行速度
B. 记忆化搜索解决问题:当发现F(num)已经被计算过了,不再递归,直接返回计算后的结果,减少了很多次无用的递归展开
C. 代码实现
(2)解决方法二:动态规划解决(利用循环解决,不使用递归)
注意:动态规划的速度又得到了进一步提升
三、汉诺塔问题(递归解决)
1. 汉诺塔问题(最少步数):
(1)规则
(2)规则图示(以三个盘子为例子)
(3)移动过程(以三个盘子为例子,从上到下依次给盘子编号1,2,3)
(4)思路:
要素1:可拆分如上图所示可以分为三个部分:
//A. 将A上的编号为N-1的盘子从A->B ,C作为辅助,暂存比较小的盘子 //B. 将A上的编号最大(N)的盘子从A -> C //C. 将B上的编号N-1的盘子移动到C,A作为辅助,暂存比较小的盘子
要素2://递归的终止条件:如果只有一个盘子,直接从A->C(只有一个盘子的时候可以移动,且从起始柱移动到目标柱)
理解:要想移动3个盘子,就得移动上2个盘子,要想移动2个盘子就得移动上1个盘子,直到1个盘子的时候才可以移动,且从起始柱移动到目标柱
(5)实现代码
结果
四、数组的定义与使用
1. 数组定义:(1)相同数据类型的集合(2)数组在内存空间中连续存储(3)java中数组是引用数据类型,在堆中存储
2. 数组特点:
(1)数组中存放的元素类型相同
(2)数组空间连续,一个元素紧挨另一个元素
(3)每个数组元素都有下标,第一个数组元素的下标从0开始
3. 数组的创建与初始化(注意:java中数组的长度一旦定义之后就是固定的,也就是定长数组)
(1)静态初始化:定义数组时,随机将每个元素的值也初始化好
A. 语法:
数据类型[] 数组名 = {元素值1,元素值2....,元素值N};
B. 代码实例:
C. 注意:数组长度编译器会根据元素的个数来自动计算并定长
(2)动态初始化:定义数组时,不给元素具体赋值,定义之后再赋值,数组中每个元素都是其数据类型的默认值
A. 语法:
数据类型[] 数组名 = new 数据类型[数组长度];
注意:前面的数据类型和后面的一致
B. 代码实例:
(3)注意:静态初始化实际上是一个语法糖,从源代码上看会加上new int[]
4. 数组的基本使用
(1) 数组中元素的访问
A. 原理:
a. 由于数组在内存中是一块连续的内存空间,且每个数组元素都有下标,第一个元素的下标为0,程序只需要记录第一个元素的地址(数组的首地址)即可。
b. 下标为 i 的数组元素(第i+1个元素)的地址 = 首地址 + 数据类型的大小 * i
c. 数组的下标称为数组的偏移量:该数组元素相对于第一个元素的偏移量(即相对于第一个元素的位置(首地址)差了几个单位的数据类型大小)
因此数组元素的下标为[0,N-1],N为数组的长度。(第一个元素距离自己的偏移量为0,因此最后一个元素的偏移量为N-1)
d.基于上述的原理,得到了数组的随机访问特性:可以立即取得数组中的任何一个元素
B. 语法:
数组名称[数组下标];
注意1:元素个数 = 下标 + 1
注意2: 下标范围是[0, N-1],越界访问会报错
C. 实例:
(2)得到数组长度的方法
A. 语法
数组名称.length
B. 实例
(3) 遍历数组的每一个元素
A. 通过普通的for循环,数组长度来遍历数组的每一个元素(可打印,可修改)
B. 只需要取得数组元素,并不修改,可以用for-each循环来遍历
a. 语法:
for(int i : 数组名){
//循环体
}
b. 原理
c. 代码实例
五、什么是引用
1. 引用数据类型:数组,类,接口都属于引用数据类型
所有引用数据类型的变量,保存的都是一块堆内存的地址(对象的地址),不是具体数值,所谓的引用只不过是给对象起了个别名而已
2. java的栈区和堆区
(1)栈区:保存的是函数的栈帧,每个函数的调用和结束,就对应一个栈帧的入栈和出栈过程。方法中的所有局部变量都保存在栈帧中
(2)堆区:java中的new是为了产生对象的,new产生的对象都保存在堆区中。
3. 引用类型的变量就是保存对象的地址(相对的,基本数据类型都保存的是具体是数值)
举例:new int [5]相当于在内存中开辟了一块新的空间,数组是连续空间。将数组的首地址赋值给这个引用类型的变量 int[] num
4. swap函数数组实现
(1)原理:
(2)代码实现
5. NPE异常
(1)原理:当引用数据类型保存的值为null,表示该引用不指向任何堆内存空间,无法通过该引用进行任何具体的操作(读写操作),一旦操作则发出NPE异常
(2)例子:
6. 数组作为函数的返回值
例子:定义一个方法,传入一个整数N,返回从第一项到第N项的所有斐波那契数(此处应该返回一个整型数组)