递归
递归是指函数/过程/子程序在运行过程中直接或间接调用自身而产生的重入现象。
运用递归的条件:
1. 子问题须与原始问题为同样的事,且更为简单;
2. 不能无限制地调用本身,须有个出口,化简为非递归状况处理。
就比如经典的汉诺塔问题:
共有3根柱子ABC,A柱上有若干个圆盘(从大到小依次摆放,最小的在最上方),大盘子不能放在小盘子的上面,只能依次移动盘子,问如何将A柱上的圆盘移动到C柱上(顺序与A柱相同)?
可以将过程分为3个步骤:
第一步:将除最大盘子以外的n-1个盘子移动到B。(可以理解为将n-1个盘子从A移动到B,然后进行递归计算)
第二歩:将最大的盘子移动到C。(可以直接完成)
第三步:将B柱上的n-1个盘子移动到C。(重复前两步,将除最大盘子以外的盘子移动到A,再将最大的盘子移动到C。)
public class 递归算法_汉诺塔问题 {
public static void main(String[] args) {
System.out.print("输入要移动的个数:");
Scanner scanner = newScanner(System.in);
int num = scanner.nextInt();
Hanoi h = new Hanoi();
h.hanoi(num,'A','B','C');
}
}
class Hanoi{
void move(char a,char b){
System.out.println(a+"----->"+b);
}
//n为所需要移动盘子的个数,abc表示柱子
void hanoi(int n, char a,char b,char c){
//出口
if (n == 1) {
move(a, c);
}else{
//移动除最下面以外所有盘子到b(c为中介)
hanoi(n - 1, a, c, b);
//移动最下面的盘子到c
move(a, c);
//移动剩下的盘子到c(在b上,a为中介)
hanoi(n - 1, b, a, c);
}
}
}
首先看一下运行结果:
还有一个经典例子,四皇后问题:(没有步骤,直接上代码)
这里是用一维数组存放的方法:
- 首先用一维数组来记录皇后放置位置。
- 数组内的每个数组元素可取1—4 4个值。数字就表示皇后所放置的位置。
- 从第一个元素开始,取值为1,然后进行递归,判断第二个元素的取值,如果可以进行取值,则再进行递归,否则返回并取下一个值。
- 判断条件,每个元素所取的值不能有相同的,并且2个元素之间所取值的大小不能等于数组下标的差值,即所取值不能在对角线上。
public class 递归算法_四皇后问题_一维数组 {
public static void main(String[] args) {
Squeue squeue = new Squeue();
squeue.fun(0);
}
}
class Squeue{
//存放结果的数组
int que[] = new int[4];
//打印最终的结果
void display(){
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
if (j == que[i] - 1) {
System.out.print(1+" ");
}else {
System.out.print(0+" ");
}
}
System.out.println();
}
System.out.println();
}
//出口,如果找到满足条件的4个皇后,输出
void fun(int a){
if (a == 4) {
display();
return;
}
//取值为1--4
for (int k = 1; k <= 4; k++) {
boolean ct = true;
que[a] = k;
//判断当前位置所放置的皇后是否满足条件
for (int i = 0; i < a; i++) {
if (Math.abs(que[a]- que[i]) == a- i ) {//在对角线上
ct = false;
}else if (que[a] == que[i]) {//同行
ct = false ;
}
}
//如果满足则进行递归,不满足则继续循环
if (ct) {
fun(a+1);
}
}
}
}
结果如下图:
回溯
回溯也称试探法,它的基本思想是:从问题的某一种状态(初始状态)出发,搜索从这种状态出发所能达到的所有“状态”,当一条路走到“尽头”的时候(不能再前进),再后退一步或若干步,从另一种可能“状态”出发,继续搜索,直到所有的“路径”(状态)都试探过。这种不断“前进”、不断“回溯”寻找解的方法,就称作“回溯法”。
用 Icossian 问题来进行说明:
找出所有能遍历所有的城市的路径,且每个城市只能经过一次。
由题可知有5个城市,则可设置一个5*5的数组用来存放路径关系。用0—1表示2个城市之间是否相连。
则图可转变为下列数组
将行看做是出口,列看做入口。
例如从A出发可达到B,则将A所在的行内的1变为2(关闭)。将B所在的列内的变为2(关闭)。然后将B所在的列变成行(入口变出口 )进行递归,找寻下一次满足条件的城市。如果存在则递归,反之跳回并将数据恢复到上一步。
当a[]数组的元素达到6个且A为结束点时就输出。
public class 回溯算法_Icossian问题 {
public static void main(String[] args) {
Country country = new Country();
country.setCountry();
country.visit(0, country.c);
}
}
class Country{
int c[][] = new int [5][5];//记录各点之间的关系
char a[] = new char[6];//记录可行路径
int count = 1;//路径放入的地方 a[count]
void setCountry(){ //输入各点之间之间的关系 a[0] = ‘A’;//从A开始
Scanner scanner = new Scanner(System.in);
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
c[i][j] = scanner.nextInt();
}
}
}
void visit(int h, int c[][]){
//出口 走过所有城市且最后到达A城
if (count == 6 && a[count - 1] == 'A') {
display(a);
System.out.println();
}
//寻找当前城市与其他城市之间的通路
for (int i = 0; i < a.length-1; i++) {
//如果存在通路
if (c[h][i] == 1) {
a[count] = (char)('A' + i);
count++;
for (int j = 0; j < a.length-1; j++) {
if (c[j][i] == 1) {
c[j][i] = 2;//将通路关闭(表示路已经走过了)
}
}
//进行递归
visit(i, c);
//以下进行回溯,当当前情况递归完毕后,要还原当前情况下的状态
count--;
for (int j = 0; j < a.length-1; j++) {
if (c[j][i] == 2) {
c[j][i] = 1;
}
}
}
}
}
void display(char a[]){
for (int i = 0; i < a.length; i++) {
System.out.print(a[i]);
}
}
}
运行结果: