遞歸的思想
以此類推是遞歸的基本思想。
具體來講就是把規模大的問題轉化爲規模小的相似的子問題來解決。在函數實現時,因爲解決大問題的方法和解決小問題的方法往往是同一個方法,所以就產生了函數調用它自身的情況。另外這個解決問題的函數必須有明顯的結束條件,這樣就不會產生無限遞歸的情況了。
遞歸的兩個條件:
自身調用:可以通過遞歸調用來縮小問題規模,且新問題與原問題有着相同的形式
遞歸出口:存在一種簡單情境,可以使遞歸在簡單情境下退出。
怎麼更好地理解遞歸算法
遞歸:你打開面前這扇門,看到屋裏面還有一扇門(這門可能跟前面打開的門一樣大小(靜),也可能門小了些(動)),你走過去,發現手中的鑰匙還可以打開它,你推開門,發現裏面還有一扇門,你繼續打開…… 若干次之後,你打開面前一扇門,發現只有一間屋子,沒有門了。 你開始原路返回,每走回一間屋子,你數一次,走到入口的時候,你可以回答出你到底用這鑰匙開了幾扇門。遞歸就是有去(遞去)有回(歸來)。
循環:你打開面前這扇門,看到屋裏面還有一扇門,(這門可能跟前面打開的門一樣大小(靜),也可能門小了些(動)),你走過去,發現手中的鑰匙還可以打開它,你推開門,發現裏面還有一扇門,(前面門如果一樣,這門也是一樣,第二扇門如果相比第一扇門變小了,這扇門也比第二扇門變小了(動靜如一,要麼沒有變化,要麼同樣的變化)),你繼續打開這扇門……一直這樣走下去。 入口處的人始終等不到你回去告訴他答案。
應用場景
1.Fibonacci數列
斐波那契數列就是如下的數列:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …,
總之,就是第N(N > 2)個數等於第(N - 1)個數和(N - 2)個數的和。用遞歸程序流程如下圖:
2. 階乘
例如在求解6的階乘時,遞歸過程如下所示。
這個過程和棧的工作原理一致對,遞歸調用就是通過棧這種數據結構完成的。整個過程實際上就是一個棧的入棧和出棧問題。然而我們並不需要關心這個棧的實現,這個過程是由系統來完成的。
那麼遞歸中的“遞”就是入棧,遞進;“歸”就是出棧,迴歸。
3 .漢諾塔問題
漢諾塔問題是源於印度一個古老傳說的益智玩具。大梵天創造世界的時候做了三根金剛石柱子,在一根柱子上從下往上按照大小順序摞着64片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小順序重新擺放在另一根柱子上。並且規定,在小圓盤上不能放大圓盤,在三根柱子之間一次只能移動一個圓盤。
import java.util.*;
public class HanoiTower {
public static int yidongcishu=0;
public static void main(String[] args) {
while(true){
Scanner read=new Scanner(System.in);
int cishu=read.nextInt();
hanoiTower(cishu, 'A', 'B', 'C');
System.out.println("移動的總次數爲:"+yidongcishu+"次。");
yidongcishu=0;
}}
private static void hanoiTower(int num, char a, char b, char c) {
if (num == 1) { // 只有一個盤,直接解出
System.out.println("第1個盤從" + a + "->" + c);
yidongcishu++;
} else {
// 如果n>=2的情況
// 1.先把最上面的所有盤A->B,移動過程會使用C
hanoiTower(num - 1, a, c, b);
// 2.把最下邊的盤A->C
System.out.println("第" + num + "個盤從" + a + "->" + c);
yidongcishu++;
// 3.把B塔所有盤從B->C,移動過程使用到A
hanoiTower(num - 1, b, a, c);
}
}
}
程序運行結果如下圖所示:
4. 排列組合
對於一個長度爲n的串或者n個字符(數字、節點)組成的字符串數組,它的全排列共有A(n, n)=n!種。這個問題也是一個遞歸的問題。如1,2,3,全排列可得到:{123,132,213,231,312,321},輸出任意個數字母、數字的全排列 。
5.歸併排序
歸併排序也是遞歸的典型應用,其思想:將序列分爲若干有序序列(開始爲單個記錄),兩個相鄰有序的序列合併成一個有序的序列,以此類推,直到整個序列有序。
歸併排序(Merge)是將兩個(或兩個以上)有序表合併成一個新的有序表,即把待排序序列分爲若干個子序列,每個子序列是有序的。然後再把有序子序列合併爲整體有序序列。
歸併排序是建立在歸併操作上的一種有效的排序算法。該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。 將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲2-路歸併。
歸併排序算法穩定,數組需要O(n)的額外空間,鏈表需要O(log(n))的額外空間,時間複雜度爲O(nlog(n)),算法不是自適應的,不需要對數據的隨機讀取。
工作原理:
1.申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列;
2.設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置;
3.比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置;
4.重複步驟3直到某一指針達到序列尾;
5.將另一序列剩下的所有元素直接複製到合併序列尾。
6 .趣味問題——年齡。
有5個人坐在一起,問第五個人多少歲?他說比第4個人大2歲。問第4個人歲數,他說比第3個人大2歲。問第三個人,又說比第2人大兩歲。問第2個人,說比第一個人大兩歲。最後問第一個人,他說是10歲。請問第五個人多大?
7.和數分解
把一個數分解成任意幾個數的和,把所有的可能性列出來。
import java.util.*;
public class HeShiFenJie {
/*
*@author Chao
*/
static int sum = 0;//和
static int pos = -1;//指針
static int number = 0;//輸入的數
static int[] reslut;
static int count = 0;//解的次數
public static void main(String[] args) {
Scanner read = new Scanner(System.in);
while(read.hasNext()){
number = read.nextInt();
reslut = new int[number];
DFS(1);
System.out.println("您好:根據要求,共有"+count+"數據和爲"+"number");
count = 0;
}
}
private static void DFS(int x) {
if (sum == number) {// 得到一組解
count++;
System.out.print(number + "=");
for (int i = 0; i < pos; i++) {// 輸出前pos-1個元素
System.out.print(reslut[i] + "+");
}
System.out.println(reslut[pos]+";");//輸出第pos個元素,換行
return;// 返回上一層
}
if (sum > number) {//未滿足要求
return;
}
for (int i = x; i < number + 1; i++) {//把i的初值賦爲x,目的就是保持序列自增
reslut[++pos] = i;// 要先自增再賦值,也會減少中間變量,節省空間
sum += i;
DFS(i);// 遞歸
pos--;// 指針回到前一位置
sum -= i;//相當於抵消上一次求和
}
}
}
程序運行結果如下圖所示: