遞歸
遞歸是指函數/過程/子程序在運行過程中直接或間接調用自身而產生的重入現象。
運用遞歸的條件:
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]);
}
}
}
運行結果: