Java的遞歸算法詳解
一、介紹
1、介紹
遞歸:遞歸就是方法自己調(diào)用自己,每次調(diào)用時(shí)傳入不同的變量。遞歸有助于編程者解決復(fù)雜的問(wèn)題,同時(shí)可以讓代碼變得簡(jiǎn)潔。
迭代和遞歸區(qū)別:迭代使用的是循環(huán)結(jié)構(gòu),遞歸使用的選擇結(jié)構(gòu)。使用遞歸能使程序的結(jié)構(gòu)更清晰、更簡(jiǎn)潔、更容易讓人理解,從而減少讀懂代碼的時(shí)間。其時(shí)間復(fù)雜度就是遞歸的次數(shù)。
但大量的遞歸調(diào)用會(huì)建立函數(shù)的副本,會(huì)消耗大量的時(shí)間和內(nèi)存,而迭代則不需要此種付出。
遞歸函數(shù)分為調(diào)用和回退階段,遞歸的回退順序是它調(diào)用順序的逆序。
分治:當(dāng)一個(gè)問(wèn)題規(guī)模較大且不易求解的時(shí)候,就可以考慮將問(wèn)題分成幾個(gè)小的模塊,逐一解決。
2、案例
- 兔子繁殖的問(wèn)題。(斐波那契數(shù)列)。
- 計(jì)算 n! 。
- 任意長(zhǎng)度的字符串反向輸出。
- 折半查找算法的遞歸實(shí)現(xiàn)。
- 漢諾塔問(wèn)題
- 八皇后問(wèn)題
二、迷宮問(wèn)題
問(wèn)題:尋找一條從起始點(diǎn)到達(dá)終點(diǎn)的有效路徑。

代碼示例:迷宮
public class MiGong {
/**
* 0:該點(diǎn)沒有走過(guò), 1:表示墻, 2:可以走, 3:該點(diǎn)已經(jīng)走過(guò),但是走不通\
* 策略: 下->右->上->左, 如果該點(diǎn)走不通,再回溯
*/
private int[][] map;
private int desX;
private int desY;
/**
* 構(gòu)建 row*col的迷宮
*
* @param row 行
* @param col 列
*/
public MiGong(int row, int col) {
if (row <= 0 || col <= 0) {
return;
}
map = new int[row][col];
// 默認(rèn) 上下左右 全部為墻
for (int i = 0; i < col; i++) {
map[0][i] = 1;
map[row - 1][i] = 1;
}
for (int i = 0; i < row; i++) {
map[i][0] = 1;
map[i][col - 1] = 1;
}
}
/**
* 在迷宮內(nèi)部添加擋板
*
* @param i 橫坐標(biāo)
* @param j 縱坐標(biāo)
*/
public void addBaffle(int i, int j) {
if (map == null) {
return;
}
// 外面一周都是墻
if (i > 0 && i < map.length - 1 && j > 0 && j < map[0].length - 1) {
map[i][j] = 1;
}
}
/**
* 設(shè)置迷宮的終點(diǎn)位置
*
* @param desX 橫坐標(biāo)
* @param desY 縱坐標(biāo)
*/
public void setDes(int desX, int desY) {
this.desX = desX;
this.desY = desY;
}
public boolean setWay(int i, int j) {
// 通路已經(jīng)找到
if (map[desX][desY] == 2) {
return true;
} else {
if (map[i][j] != 0) {
return false;
}
// map[i][j] == 0 按照策略 下->右->上->左 遞歸
// 假定該點(diǎn)是可以走通.
map[i][j] = 2;
if (setWay(i + 1, j)) {
return true;
} else if (setWay(i, j + 1)) {
return true;
} else if (setWay(i - 1, j)) {
return true;
} else if (setWay(i, j - 1)) {
return true;
} else {
// 說(shuō)明該點(diǎn)是走不通,是死路
map[i][j] = 3;
return false;
}
}
}
// 顯示地圖
public void show() {
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[0].length; j++) {
System.out.print(map[i][j] + " ");
}
System.out.println();
}
}
}
代碼示例:測(cè)試類
// 測(cè)試類
public class Main {
public static void main(String[] args) {
MiGong miGong = new MiGong(8, 7);
miGong.addBaffle(3, 1);
miGong.addBaffle(3, 2);
miGong.setDes(6, 5); // 設(shè)置目的地
System.out.println("初始地圖的情況");
miGong.show();
miGong.setWay(1, 1); // 設(shè)置起始位置
System.out.println("小球走過(guò)的路徑,地圖的情況");
miGong.show();
}
}
// 結(jié)果
初始地圖的情況
1 1 1 1 1 1 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 0 0 0 0 0 1
1 1 1 1 1 1 1
小球走過(guò)的路徑,地圖的情況
1 1 1 1 1 1 1
1 2 0 0 0 0 1
1 2 2 2 0 0 1
1 1 1 2 0 0 1
1 0 0 2 0 0 1
1 0 0 2 0 0 1
1 0 0 2 2 2 1
1 1 1 1 1 1 1
三、八皇后問(wèn)題
問(wèn)題:在8×8格的國(guó)際象棋上擺放八個(gè)皇后,使其不能互相攻擊,即:任意兩個(gè)皇后都不能處于同一行、同一列或同一斜線上,問(wèn)有多少種擺法。

代碼示例:八皇后
public class Queue8 {
private static final int MAX = 8;
// 保存皇后放置的位置,比如 arr = {0, 4, 7, 5, 2, 6, 1, 3}
private final int[] array = new int[MAX];
public static int count = 0;
public static int judgeCount = 0;
public void check() {
this.check(0);
}
// check 是每一次遞歸時(shí),進(jìn)入到check中都有 for(int i = 0; i < max; i++),因此會(huì)有回溯
private void check(int n) {
// n = 8, 表示8個(gè)皇后就已經(jīng)放好
if (n == MAX) {
print();
return;
}
for (int i = 0; i < MAX; i++) {
array[n] = i;
// 判斷當(dāng)放置第n個(gè)皇后到i列時(shí),是否沖突
// 不沖突
if (!judge(n)) {
// 接著放n+1個(gè)皇后,即開始遞歸
check(n + 1);
}
}
}
private boolean judge(int n) {
judgeCount++;
for (int i = 0; i < n; i++) {
// 同一列 或 同一斜線
if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])) {
return true;
}
}
return false;
}
private void print() {
count++;
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
}
}
代碼示例:測(cè)試類
// 測(cè)試類
public class Main {
public static void main(String[] args) {
Queue8 queue8 = new Queue8();
queue8.check();
System.out.printf("一共有%d解法", Queue8.count);
System.out.printf("一共判斷沖突的次數(shù)%d次", Queue8.judgeCount); // 1.5w
}
}
四、漢諾塔問(wèn)題
1、問(wèn)題

2、思想
如果 n = 1,A -> C
如果 n >= 2,總是看做是兩個(gè)盤,①最下邊的盤。②上面所有的盤。則,步驟:
(1)先把上面所有的盤 A->B
(2)把最下邊的盤 A->C
(3)把 B 塔的所有盤 從 B->C
3、代碼
代碼示例:漢諾塔問(wèn)題
// 漢諾塔
public class Hanoitower {
// 使用分治算法
public static void move(int num, char a, char b, char c) {
// 如果只有一個(gè)盤
if (num == 1) {
System.out.println("第1個(gè)盤從 " + a + "->" + c);
} else {
// n >= 2,總是看做是兩個(gè)盤,①最下邊的盤。②上面所有的盤。則,步驟:
// 1.先把上面所有的盤 A->B.移動(dòng)過(guò)程會(huì)使用到 c
move(num - 1, a, c, b);
// 2.把最下邊的盤 A->C
System.out.println("第" + num + "個(gè)盤從 " + a + "->" + c);
// 3.把 B 塔的所有盤 從 B->C.移動(dòng)過(guò)程會(huì)使用到 a
move(num - 1, b, a, c);
}
}
}
代碼示例:測(cè)試類
// 測(cè)試類
public class Main {
public static void main(String[] args) {
Hanoitower.move(3, 'A', 'B', 'C');
}
}
// 結(jié)果
第1個(gè)盤從 A->C
第2個(gè)盤從 A->B
第1個(gè)盤從 C->B
第3個(gè)盤從 A->C
第1個(gè)盤從 B->A
第2個(gè)盤從 B->C
第1個(gè)盤從 A->C
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
詳解如何使用Java流API構(gòu)建樹形結(jié)構(gòu)數(shù)據(jù)
在實(shí)際開發(fā)中,構(gòu)建樹狀層次結(jié)構(gòu)是常見需求,本文主要為大家詳細(xì)介紹了如何使用Java 8 Stream API將扁平化的菜單數(shù)據(jù)轉(zhuǎn)換為具有層級(jí)關(guān)系的樹形結(jié)構(gòu),需要的可以參考下2024-04-04
Spring Boot中如何使用Convert接口實(shí)現(xiàn)類型轉(zhuǎn)換器
這篇文章主要介紹了Spring Boot中使用Convert接口實(shí)現(xiàn)類型轉(zhuǎn)換器的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Java Swing 非常漂亮外觀Nimbus的使用方法實(shí)例
Java Swing 非常漂亮外觀Nimbus的使用方法實(shí)例,需要的朋友可以參考一下2013-02-02
Java面向?qū)ο蠡A(chǔ)之多態(tài)性,抽象類和接口
這篇文章主要介紹了Java面向?qū)ο蠡A(chǔ):多態(tài)性,抽象類和接口,文中代碼可以幫助各位更好的理解學(xué)習(xí),有需求的小伙伴可以參考下2020-05-05
Java實(shí)現(xiàn)簡(jiǎn)易生產(chǎn)者消費(fèi)者模型過(guò)程解析
這篇文章主要介紹了Java實(shí)現(xiàn)簡(jiǎn)易生產(chǎn)者消費(fèi)者模型過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
SpringBoot實(shí)戰(zhàn)之處理異常案例詳解
這篇文章主要介紹了SpringBoot實(shí)戰(zhàn)之處理異常案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09

