Java 面向对象(基础)
public class Object01{
//编写一个mian方法
public static void mian(String[] args){
//两只猫 - 单独变量表示猫信息 => 不利于数据的>管理
String cat1Name = "小白";
String cat2Name = "小花";
int cat1Age = 3;
int cat2Age = 100;
String cat1Color = "白色";
String cat2Color = "花色";
//数组表示 ==> 数据类型体现不出
//获取信息要下标,造成变量名字和内容的对应关系不明确
//不能体现猫的行为
String[] cat1 = {"小白","3","白色"};
String[] cat2 = {"小花","100","白色"};
}
}
-
定义两只猫的对象信息
-
单独的定义变量解决
-
使用数组解决
-
-
现有技术解决的缺点分析
- 不利于数据的管理
- 效率低下
-
java 设计者引入类与对象 ( OOP ),根本原因就是现有的技术,不能完美的解决新的新的需求。
1. 类与对象的概述
一个程序就是一个世界,有很多事物(对象[属性,行为])
创建一个对象 实例化一个对象 把类实例化
java最大的特点就是面向对象
public class Object02{
public static void main(String[] args){
//使用OOP面向对象解决
//实例化一只猫【创建一只猫对象】
//把创建的猫赋值给 cat1
//cat1 就是一个对象
Cat cat1 = new Cat();
cat1.name = "小白";
cat1.age = 3;
cat1.color = "白色";
//创建第二只猫,赋给 cat2
//cat2 就是一个对象(猫对象)
Cat cat2 = new Cat();
cat2.name = "小花";
cat2.age = 100;
cat2.color = "花色";
//怎么访问对象的属性
System.out.println("第一只猫信息" + cat1.name + " " + cat1.age + " " + cat1.color);
System.out.println("第二只猫信息" + cat2.name + " " + cat2.age + " " + cat2.color);
}
}
//定义一个猫类 Cat -> 自定义的数据类型
class Cat {
//属性
String name;
int age;
String color;
}
类与对象的区别和联系
- 类是抽象的,概念的,代表一类事物,比如人类、猫类,即它是数据类型
- 对象是具体的,实际的,代表一个具体事物,即是实例
- 类是对象的模板,对象是类的一个个体,对应一个实例
对象内存布局
Cat cat1 = new Cat();
cat1.name = "小白";
cat1.age = 3;
cat1.color = "白色";
属性概念
属性/成员变量基本介绍
- 从概念或叫法上看:成员变量 = 属性 = field字段(即 成员变量 是用来表示属性的,授课中,统一叫 属性)
class Car{
String name; //属性,成员变量,字段,filed
double price;
String color;
String[] master; //属性可以是基本数据类型,也可以是引用类型(对象,数组)
}
- 属性是类的一个组成部分,一般是基本数据类型,也可是引用类型(对象,数组)。比如前面定义猫类的 int age 就是属性。
注意细节
- 属性的定义语法同变量,示例:访问修饰符 属性 类型 属性名;
- 访问修饰符:控制属性的访问范围
- 有四种访问修饰符 public,proctected,默认,private
-
属性的定义类型可以为任意类型,包含基本类型或引用类型。
-
属性如果不赋值,有默认值,规则和数组一致。
2. 对象的创建
创建对象访问属性
如何创建对象
- 先声明再创建
Cat cat;
cat = new Cat();
- 直接创建
Cat cat = new Cat();
如何访问属性
对象名.属性名;
cat.name;
cat.age;
cat.color;
类和对象的内存分配机制(重要)
Java内存的结构分析
栈:一般存放基本数据类型(局部变量)
堆:存放对象(Cat cat,数组等)
方法区:常量池(常量,比如字符串),类加载信息
public class Object03{
public static void main(String[] args){
Person p1 = new Person();
p1.age = 10;
p1.name = "小明";
Person p2 = p1; //把 p1 赋值给 p2,让 p2 指向 p1
System.out.println(p2.age);
//p2.age 是什么?
//10
}
}
class Person {
String name;
int age;
}
内存图
对象创建过程
Person p = new Person();
p.name = "Jack";
p.age = 10;
- 先加载Person类信息(属性和方法信息,只会加载一次)
- 在堆中分配空间,进行默认初始化(看规则)
- 把地址赋给,p就指向对象
- 进行指定初始化,比如 p.name = “Jack”;p.age = 10;
对象机制练习
public class Object04{
public static void main(String[] args){
Person a = new Person();
a.age = 10;
a.name = "小明";
Person b;
b = a;
System.out.println(b.name); //小明
b.age = 200;
b = null;
System.out.println(a.age); //200
System.out.println(b.age); //发生异常
}
}
class Person {
String name;
int age;
}
3. 方法的调用
成员方法
在某些情况下,我们要需要定义成员方法(简称方法)。
比如人类:除了有一些属性外(年龄,姓名 … )。
我们人类还有一些行为比如:可以说话、跑步 … ,通过学习,还可以做算术题。
这时就要用成员方法才能完成。
public class Method01 {
public static void main(String[] args) {
//方法使用
//方法写好,如果不调用就不会输出
//先创建对象,然后调用方法即可
Person p1 = new Person();
p1.speak();
p1.cal01();
p1.cal02(5); //1加到5
p1.cal02(10); //1加到10
int returnRes = p1.getSum(10,20); //调用getSum 方法,同时 num1 = 10,num2 = 20
System.out.println("getSum方法返回的是:" + returnRes);
}
}
class Person {
String name;
int age;
//方法(成员方法)
//添加 speak 成员方法
//public 表示方法是公开的
//void 表示方法没有返回值
//speak() speak是方法名,()形参列表
//{ } 方法体,可以写我们要执行的代码
public void speak() {
System.out.println("我是一个好人");
}
//添加cal01 成员方法,可以计算从 1 + ... + 1000 的结果
public void cal01() {
//循环
int res = 0;
for(int i = 1; i <= 1000; i++){
res += i;
}
System.out.println("计算结果 = " + res);
}
//添加cal02 成员方法,该方法可以接收一个数 n,计算从 1... + n 的结果
//(int n) 形参列表,表示当前有一个形参 n,可以接受用户输入
public void cal02(int n){
int res = 0;
for(int i = 1;i <= n;i++){
res += i;
}
System.out.println("计算结果 = " + res);
}
//添加getSum 成员方法,可以计算两个数的和
//int 表示方法执行后,返回一个 int 值
//形参列表,可以写多个,接受用户传入的数
//return res; 表示把 res 的值,返回
public int getSum(int num1,int num2){
int res = num1 + num2;
return res;
}
}
方法调用机制
Person p1 = new Person();
int returnRes = p1.getSum(10,20);
System.out.println(returnRes);
public int getSum(int num1,int num2){
int res = num1 + num2;
return res;
}
方法调用小结
- 当程序执行到方法时,就会开辟一个独立的空间(栈空间)
- 当方法执行完毕。或者执行到return语句时,就会返回,
- 返回到调用方法的地方
- 返回后,维续执行方法后面的代码
- 当 main 方法(栈)执行完毕,整个程序退出
方法的使用
public class Method02 {
public static void main(String[] args) {
//遍历数组,输出数组的各个元素值
int [][] map = { {0,0,1} , {1,1,1} , {1,1,3} };
//传统方法:直接遍历数组
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
//要求再次遍历 - 麻烦,代码重复
//使用方法完成输出
MyTools tool = new MyTools();
tool.printArr(map);
}
}
//把输出的功能,写到类方法
class MyTools {
public void printArr(int[][] map) {
//对传入的 map 数组进行遍历输出
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
}
成员方法的好处
-
提高代码的复用性
-
可以将实现的细节封装起来,然后供其他用户来调用即可。
方法的定义
成员方法的定义
访问修饰符 返回数据类型 方法名 (形参列表) {
方法体语句;
return 返回值;
}
//1. 形参列表:表示成员方法输入 cal(int n), getSum(int num1, int num2)
//2. 返回数据类型:表示成员方法输出,void表示没有返回值
//3. 方法主体:表示为了实现某一功能代码块
//4. return语句不是必须的
方法使用注意点和使用细节
访问修饰符
作用是控制方法使用的范围
如果不写默认访问,[ 有四种:public、protected、默认、private ]
返回数据类型
- 一个方法最多有一个返回值 [ 思考,如何返回多个结果返回数组 ]
- 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
- 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值; 而且要求返回值类型必须和return的值类型一致或兼容
- 如果方法是 void,则方法体中可以没有 return 语句,或者只写 return;
- 方法名遵循驼峰命名法,最好见名知义,表达出该功能的意思即可,比如得到两个数的和getSum,开发中按照规范
public class Method03 {
public static void main(String[] args) {
AA a = new AA(); //错误: 不兼容的类型: int[]无法转换为int
//int res = a.getSumAndSub(1,4);
int[] res = a.getSumAndSub(1,4);
System.out.println("和=" + res[0]);
System.out.println("差=" + res[1]);
}
}
class AA {
//1.一个方法最多有一个返回值 [如果返回多个结果?>返回数组]
//2.返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
public int[] getSumAndSub(int n1, int n2) {
int[] resArr = new int[2]; //创建一个数组
resArr[0] = n1 + n2;
resArr[1] = n1 - n2;
return resArr;
}
//3.如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值;
//而且要求返回值类型必须和return的值类型一致或兼容
public double f1() {
double d1 = 1.1 * 3;
int n = 100;
return n; //可以的,兼容性,int 转类型-> double
}
// public int f2() {
// double d1 = 1.1 * 3;
// //错误: 不兼容的类型: 从double转换到int可能会有损失
// return d1; //不可以,double -> int
// }
//4.如果方法是 void,则方法体中可以没有 return 语句,或者只写 return;
public void f3() {
System.out.println("hello");
int n = 10;
return; //可以写
//return n; //不可以写,错误: 不兼容的类型: 意外的返回值
}
}
-
一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开。比如 getSum(int n1,int n2)
-
参数类型可以为任意类型,包含基本类型或引用类型,比如 printArr(int map)
-
调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数 【getSum】
-
方法定义时的参数称为形式参数,简称形参;方法调用时的传入参数称为实际参数,简称实参,实参和形参的类型要一致或兼容、个数、顺序必须一致
-
方法体:里面写完成功能的具体的语句,可以为输入、输出、变量、运算、分支、循环、方法调用,但里面不能再定义方法!即:方法不能嵌套定义。
public class Method03 {
public static void main(String[] args) {
AA a = new AA(); //错误: 不兼容的类型: int[]无法转换为int
//int res = a.getSumAndSub(1,4);
int[] res = a.getSumAndSub(1,4);
System.out.println("和=" + res[0]);
System.out.println("差=" + res[1]);
//细节:调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数
byte b1 = 1;
byte b2 = 2;
a.getSumAndSub(b1,b2); //可以,低精度转高精度,byte -> int
//a.getSumAndSub(1.1,1.8); //不可以,从double转换到int可能会有损失
//实参和形参的类型要一直或兼容、个数、顺序必须一致
//a.getSumAndSub(100); //个数不一致,原因: 实际参数列表和形式参数列表长度不同
a.f4("tom",10); //可以
//a.f4(10,"jack"); //不可以,实际参数和形式参数顺序不对,原因: 实际参数列表和形式参数列表长度不同
}
}
class AA {
public int[] getSumAndSub(int n1, int n2) {
int[] resArr = new int[2]; //创建一个数组
resArr[0] = n1 + n2;
resArr[1] = n1 - n2;
return resArr;
}
public void f4(String str,int n) {
}
}
方法调用细节说明
-
同一个类中的方法调用:直接调用即可。比如print(参数)
-
跨类中的方法A类调用B类方法:需要通过对象名调用。比如对象名.方法名(参数);
-
跨类的方法调用和方法的访问修饰符相关
public class Method04 {
public static void main(String[] args) {
A a = new A();
a.sayOk();
a.m1();
}
}
class A {
//同一个类,直接调用
public void print(int n) {
System.out.println("print()方法被调用 n=" + n);
}
public void sayOk() { //sayOk调用 print(直接调用)
print(10);
System.out.println("继续执行sayOk");
}
//跨类中的方法A类调用B类方法:需要通过对象名调用。比如对象名.方法名(参数);
public void m1() {
System.out.println("m1()方法被调用");
//先创建对象
B b = new B();
//再调用方法
b.hi();
System.out.println("m1()继续执行");
}
}
class B {
public void hi() {
System.out.println("B类中的hi()被执行");
}
}
练习
1.
public class Method05 {
public static void main(String[] args) {
AA a = new AA();
if(a.isOdd(1)) {
System.out.println("是奇数");
} else {
System.out.println("是偶数");
}
}
}
//判断一个数是技术还是偶数,返回Boolean
class AA {
//思路
//方法的返回类型boolean
//方法的名字isodd
//方法的形参(int num)
//方法体,判断
public boolean isOdd(int num) {
// if(num % 2 != 0) {
// return true;
// } else {
// return false;
// }
//简化
//return num % 2 != 0 ? true : false;
//简化
return num % 2 != 0;
}
}
2.
public class Method06 {
public static void main(String[] args) {
AA a = new AA();
a.print(5,5,'0');
}
}
class AA {
//根据行、列、字符打印对应行数和列数的字符,
//比如:行:4,列:4,字符#,则打印相应的效果
/*
* ####
* ####
* ####
* ####
*
*/
//方法的返回类型 void
//方法的名字 print
//方法的形参(int row, int col, char c)
//方法体,判断
public void print(int row, int col, char c) {
for(int i = 0; i < row; i++) {
for(int j = 0; j < col; j++) {
System.out.print(c);
}
System.out.println(); //换行
}
}
}
4. 方法传参机制
基本数据类型的传参机制
public class Method07 {
public static void main(String[] args) {
int a = 10;
int b = 20;
//创建AA对象 obj
AA obj = new AA();
obj.swap(a,b); //调用swap方法
System.out.println();
System.out.println("main方法 a=" + a + " b=" + b);
}
}
class AA {
public void swap(int a,int b) {
System.out.println("\na和b交换前的值\na=" + a + "\tb=" +b);
//完成 a 和 b 的交换
int tmp = a;
a = b;
b = tmp;
System.out.println("\na和b交换后的值\na=" + a + "\tb=" + b);
}
}
内存流程图
结论:基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参!
引用数据类型的传参机制
1
-
B类中编写一个方法test100,可以接收一个数组,在方法中修改该数组,看看原来的数组是否变化?
-
会变化
public class Method08 {
public static void main(String[] args) {
//测试
B b = new B();
int[] arr = {1,2,3};
b.test100(arr); //调用方法
System.out.println("main 的 arr 数组");
//遍历数组
for(int i = 0; i < arr.length; i++) {
System.out.println(arr[i] + "\t");
}
System.out.println();
}
}
class B {
//B类可以接收一个数组,在方法中修改该数组,看看原来的数组是否变化
public void test100(int[] arr) {
arr[0] = 200; //修改元素
//遍历数组
System.out.println("test100 的 arr 数组");
for(int i = 0; i< arr.length; i++){
System.out.println(arr[i] + "\t");
}
}
}
内存流程图
2
- B类中编写一个方法test200,可以接收一个Person(age,sal)对象,在方法中修改该对象属性,看看原来的对象是否变化?
- 会变化.
public class Method09 {
public static void main(String[] args) {
//测试
B b = new B();
Person p = new Person();
p.name = "jack";
p.age = 10;
b.test200(p);
System.out.println("main 的 p.age = " + p.age); // 1000
}
}
class Person {
String name;
int age;
}
class B {
public void test200(Person p) {
p.age = 1000; //修改对象属性
}
}
内存流程图
结论
引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参
练习1
public class Method10 {
public static void main(String[] args) {
//测试
B b = new B();
Person p = new Person();
p.name = "jack";
p.age = 10;
b.test200(p);
//如果test200 执行的是 p = null,测试的结果是?
System.out.println("main 的 p.age = " + p.age); // 10
//测试的结果是:10
//p=null,意味着test的p不再指向0x11,指向空,test的age=0,name=null
//最后main输出p=10,是因为main的p指向0x11的地址不与test的冲
突
}
}
class Person {
String name;
int age;
}
class B {
public void test200(Person p) {
//p.age = 1000; //修改对象属性
//思考 - 执行以下语句会怎么样?
p = null;
}
}
内存流程图
练习2
public class Method11 {
public static void main(String[] args) {
//测试
B b = new B();
Person p = new Person();
p.name = "jack";
p.age = 10;
b.test200(p);
//test200 执行的是 p = new Person(); 结果是?
System.out.println("main 的 p.age = " + p.age); //10
//结果是:10
//因为 test200 创建新的对象,就是指向新的对象
//更改对象不会对 main 的对象冲突,就不会发生变化
//所以还是打印 main 指向的对象 p 的 age = 10
}
}
class Person {
String name;
int age;
}
class B {
public void test200(Person p) {
//p.age = 1000; //修改对象属性
p = new Person();
p.name = "tom";
p.age = 99;
}
}
内存流程图
注意:test 的 新对象在test方法结束后会被当作垃圾回收
练习3:克隆对象
编写一个方法copyPerson,可以复制一个Person对象,返回复制的对象。
克隆对象,注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同。
public class Method12 {
public static void main(String[] args) {
Person p = new Person();
p.name = "milan";
p.age = 100;
//创建tools
MyTools tools = new MyTools();
Person p2 = tools.copyPerson(p);
//到此P和P2是Person对象,但是是两个独立的对象,属性相同
System.out.println("p的属性 age = " + p.age + " 名字 = " + p.name);
System.out.println("p2的属性 age = " + p2.age + " 名字 = " + p2.name);
//可以同 对象比较 看看对象是否是同一个对象
System.out.println(p == p2); //false
System.out.println(p == p); //true
}
}
class Person {
String name;
int age;
}
class MyTools {
//编写一个方法copyPerson,可以复制一个Person对象,返回复制的对象。克隆对象,
//注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同
//
//1.方法的返回类型 Person
//2.方法的名字 copyPerson
//3.方法的形参 (Person P)
//4.方法体,创建一个新对象,并复制属性,返回即可
public Person copyPerson(Person p) {
//创建一个新的对象
Person p2 = new Person();
p2.name = p.name; //把原来对象的名字赋给p2.name
p2.age = p.age; //把原来对象的年龄赋给p2.age
return p2;
}
}
内存流程图
5. 递归
递归能解决什么问题?
- 各种数学问题如:8皇后问题,汉诺塔,阶乘问题,迷宫问题,球和篮子的问题(Google编程大赛)
- 各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等
- 将用栈解决的问题 --> 递归代码比较简洁
递归的机制
1
public class Recursion01 {
public static void main(String[] args) {
T t1 = new T();
t1.test(4);
}
}
class T {
public void test(int n) {
if (n > 2) {
test(n -1);
}
System.out.println("n=" + n);
}
}
2
public class Recursion02 {
public static void main(String[] args) {
T t1 = new T();
t1.test(4);
}
}
class T {
public void test(int n) {
if (n > 2) {
test(n -1);
} else {
System.out.println("n=" + n);
}
}
}
3
public class Recursion03 {
public static void main(String[] args) {
T t1 = new T();
int res = t1.factorial(5);
System.out.println("res=" + res);
}
}
class T {
//阶乘
public int factorial(int n) {
if (n == 1) {
return 1;
} else {
return factorial(n + 1) * n;
}
}
}
递归重要规则
- 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的,不会相互影响,比如 n 变量
- 如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据
- 递归必须向退出递归的条件逼近,否则就是无限递归,出现(StackOverflowError,栈内存溢出)
- 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕
练习
求出斐波那契数
- 请使用递归的方式求出斐波那契数1,1,2,3,5,8,13 … 给你一个整数n,求出它的值是多少?
思路分析
- 当 n = 1 斐波那契数是 1
- 当 n = 2 斐波那契数是1
- 当 n >= 3 斐波那契数是前两个数的和
- 这就是一个递归的思路
public class Recursion04 {
public static void main(String[] args) {
T t1 = new T();
System.out.println("当 n = 7 对应的斐波那契数 = " + t1.fibonacci(7));
}
}
class T {
/** 用递归的方式求出斐波那契数 */
public int fibonacci(int n) {
if (n >= 1) {
if (n == 1 || n == 2) {
return 1;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
} else {
System.out.println("要求输入 n >= 1 的整数");
return -1;
}
}
}
猴子吃桃子问题:
有一堆桃子。猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,
然后再多吃一个。当到第10天时,想再吃时(即还没吃),发现只有1个桃子了。
问题:最初共多少个桃子?
public class Recursion05 {
public static void main(String[] args) {
T t1 = new T();
int day = 1;
int peachNum = t1.peach(day);
if (peachNum != -1) {
System.out.println("第1天有"+ peachNum + "个桃子");
}
}
}
class T {
//猴子吃桃问题
//规律就是 前一天的桃子 = (后一天的桃子 + 1) * 2
public int peach(int day) {
if (day == 10) { //第十天只有一个套
return 1;
} else if (day >= 1 && day <= 9) {
return (peach(day + 1) + 1) * 2;
} else {
System.out.println("day在 1 - 10");
return -1;
}
}
}
递归调用应用实例 - 迷宫问题
- 小球得到的路径,和程序员设置的找路策略有关即:找路的上下左右的顺序相关
- 再得到小球路径时,可以先使用( 下 右 上 左),再改成( 上 右 下 左 ),看看路径是不是有变化
- 测试回溯现象
画出地图
public class MiGong {
public static void main(String[] args) {
//老鼠出迷宫
//思路
//1.先创建迷宫,用二维数组表示int[][] map = new int[8][7];
//2.先规定map数组的元素值:0 表示可以走;1 表示障碍物
int[][] map = new int[8][7];
//3.将最上面的一行和最下面的一行,全部设置为1
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
//4.将最右面的的一列和最左面的一列,全部设置为1
for (int i = 0; i< 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
//输出当前的地图
System.out.println("=====当前地图情况=====");
for (int i = 0; i< map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " "); //输>出一行
}
System.out.println();
}
}
}
完整代码
public class MiGong {
public static void main(String[] args) {
//老鼠出迷宫
//思路
//1.先创建迷宫,用二维数组表示int[][] map = new int[8][7];
//2.先规定map数组的元素值:0 表示可以走;1 表示障碍物
int[][] map = new int[8][7];
//3.将最上面的一行和最下面的一行,全部设置为1
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
//4.将最右面的的一列和最左面的一列,全部设置为1
for (int i = 0; i< 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
map[3][1] = 1;
map[3][2] = 1;
//输出当前的地图
System.out.println("=====当前地图情况=====");
for (int i = 0; i< map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " "); //输出一行
}
System.out.println();
}
//使用findWay 给老鼠找路
T t1 = new T();
t1.findWay(map, 1, 1);
System.out.println("\n=====找路的情况如下=====");
for (int i = 0; i< map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " "); //输出一行
}
System.out.println();
}
}
}
class T {
//使用递归回溯的思想来解决老鼠出迷宫
//1. findway方法就是专内来找出迷宫的路径
//2.如果找到,就返回 true,否则返回false
//3. map就是二维数组,即表示迷宫
//4. i,j就是老鼠的位置,初始化的位置为(1,1)
//5. 因为我们是递归的找路,所以先规定 map数组的各个值的含义
// 0表示可以走 1表示障碍物 2表示可以走 3表示走过,但是走不通时死路
//6. 当 map[6][5] = 2 就说明找到通路,就可以结束,否则就继续找
//7. 先确定老鼠找路策略 下->右->上->左
public boolean findWay(int[][] map, int i, int j) {
if (map[6][5] == 2) { //说明已经找到
return true;
} else {
if (map[i][j] == 0) { //当前这个位置0,说明表示可以走
//假定可以走通
map[i][j] = 2;
//使用找路策略,来确定该位置是否真的可以走通
//下->右->上->左
if (findWay(map, i + 1, j)) { //先走下
return true;
} else if (findWay(map, i, j + 1)) { //向走右
return true;
} else if (findWay(map, i - 1, j)) { //向上走
return true;
} else if (findWay(map, i, j -1)) {
return true;
} else {
map[i][j] = 3;
return false;
}
} else {
return false;
}
}
}
}
修改找路策略
public class MiGong1 {
public static void main(String[] args) {
//老鼠出迷宫
//思路
//1.先创建迷宫,用二维数组表示int[][] map = new int[8][7];
//2.先规定map数组的元素值:0 表示可以走;1 表示障碍物
int[][] map = new int[8][7];
//3.将最上面的一行和最下面的一行,全部设置为1
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
//4.将最右面的的一列和最左面的一列,全部设置为1
for (int i = 0; i< 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
map[3][1] = 1;
map[3][2] = 1;
//输出当前的地图
System.out.println("=====当前地图情况=====");
for (int i = 0; i< map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " "); //输出一行
}
System.out.println();
}
//使用findWay 给老鼠找路
T t1 = new T();
t1.findWay(map, 1, 1);
System.out.println("\n=====找路的情况如下=====");
for (int i = 0; i< map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " "); //输出一行
}
System.out.println();
}
}
}
class T {
//使用递归回溯的思想来解决老鼠出迷宫
//1. findway方法就是专内来找出迷宫的路径
//2.如果找到,就返回 true,否则返回false
//3. map就是二维数组,即表示迷宫
//4. i,j就是老鼠的位置,初始化的位置为(1,1)
//5. 因为我们是递归的找路,所以先规定 map数组的各个值的含义
// 0表示可以走 1表示障碍物 2表示可以走 3表示走过,但是走不通时死路
//6. 当 map[6][5] = 2 就说明找到通路,就可以结束,否则就继续找
//7. 先确定老鼠找路策略
//8.修改找路策略,看看路径是否有变化
// 下->右->上->左 => 上->右->下->左
public boolean findWay(int[][] map, int i, int j) {
if (map[6][5] == 2) { //说明已经找到
return true;
} else {
if (map[i][j] == 0) { //当前这个位置0,说明表示可以走
//假定可以走通
map[i][j] = 2;
//使用找路策略,来确定该位置是否真的可以走通
//上->右->下->左
if (findWay(map, i - 1, j)) { //先走上
return true;
} else if (findWay(map, i, j + 1)) { //向走右
return true;
} else if (findWay(map, i + 1, j)) { //向下走
return true;
} else if (findWay(map, i, j -1)) {
return true;
} else {
map[i][j] = 3;
return false;
}
} else {
return false;
}
}
}
}
测试回溯现象
public class MiGong2 {
public static void main(String[] args) {
//老鼠出迷宫
//思路
//1.先创建迷宫,用二维数组表示int[][] map = new int[8][7];
//2.先规定map数组的元素值:0 表示可以走;1 表示障碍物
int[][] map = new int[8][7];
//3.将最上面的一行和最下面的一行,全部设置为1
for (int i = 0; i < 7; i++) {
map[0][i] = 1;
map[7][i] = 1;
}
//4.将最右面的的一列和最左面的一列,全部设置为1
for (int i = 0; i< 8; i++) {
map[i][0] = 1;
map[i][6] = 1;
}
map[3][1] = 1;
map[3][2] = 1;
//更改点
map[2][2] = 1; //测试回溯现象
//输出当前的地图
System.out.println("=====当前地图情况=====");
for (int i = 0; i< map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " "); //输出一行
}
System.out.println();
}
//使用findWay 给老鼠找路
T t1 = new T();
t1.findWay(map, 1, 1);
System.out.println("\n=====找路的情况如下=====");
for (int i = 0; i< map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j] + " "); //输出一行
}
System.out.println();
}
}
}
class T {
//使用递归回溯的思想来解决老鼠出迷宫
//1. findway方法就是专内来找出迷宫的路径
//2.如果找到,就返回 true,否则返回false
//3. map就是二维数组,即表示迷宫
//4. i,j就是老鼠的位置,初始化的位置为(1,1)
//5. 因为我们是递归的找路,所以先规定 map数组的各个值的含义
// 0表示可以走 1表示障碍物 2表示可以走 3表示走过,但是走不通时死路
//6. 当 map[6][5] = 2 就说明找到通路,就可以结束,否则就继续找
//7. 先确定老鼠找路策略 下->右->上->左
public boolean findWay(int[][] map, int i, int j) {
if (map[6][5] == 2) { //说明已经找到
return true;
} else {
if (map[i][j] == 0) { //当前这个位置0,说明表示可以走
//假定可以走通
map[i][j] = 2;
//使用招录策略,来确定该位置是否真的可以走通
//下->右->上->左
if (findWay(map, i + 1, j)) { //先走下
return true;
} else if (findWay(map, i, j + 1)) { //向走右
return true;
} else if (findWay(map, i - 1, j)) { //向上走
return true;
} else if (findWay(map, i, j -1)) {
return true;
} else {
map[i][j] = 3;
return false;
}
} else {
return false;
}
}
}
}
递归调用实例 - 汉诺塔
public class HanoiTower {
public static void main(String[] args) {
System.out.println("当一个盘时:");
Tower tower = new Tower();
tower.move(1,'A', 'B', 'C');
System.out.println("当两个盘时:");
tower.move(2, 'A', 'B', 'C');
System.out.println("当三个盘时:");
tower.move(3, 'A', 'B', 'C');
}
}
class Tower {
//方法
//num 表示要移动的个数,a,b,c 分别表示A塔,B塔,C塔
public void move(int num, char a, char b, char c) {
//如果只有一个盘 num = 1
if (num == 1) {
System.out.println(a + "->" + c);
} else {
//如果有多个盘,可以看成两个,最下面的和上面的所有盘(num-1)
//(1)先移动上面所有的盘到b,借助c
move(num -1, a, c, b);
//(2)最下面的这个盘,移动到 c
System.out.println(a + "->" + c);
//(3)再把 b塔的所有盘,移动到 c ,借助 a
move(num - 1, b, a, c);
}
}
}