1. 程式人生 > >深入淺出遞迴思想

深入淺出遞迴思想

引言

作為一名程式設計師,演算法是不可或缺的。每個人對演算法的敏感度不一樣,就想每個人對數學的敏感度一樣,有些人邏輯能力就是很強。有一說一,博主個人覺得自己的數學學的不咋滴,但是像我這樣笨笨的人,我可以多複習,多推演嘛。今天我想寫的是關於遞迴的理解和使用,我會介紹一下遞迴的規則,然後用簡單和比較難的例子進行學習。

技術點:
1、遞迴是函式對自身的呼叫,呼叫分為直接呼叫間接呼叫,直接呼叫是指在函式體中呼叫自身,間接呼叫是呼叫別的函式,而這些別的函式又呼叫函式本身。它主要是把大問題變成小問題,使得程式碼更加簡潔。理解遞迴需要有一定的抽象能力。

何時使用遞迴

在什麼情況下使用遞迴呢?博主在判斷某個需求是否要用遞迴實現主要分為以下幾個步驟:

a、根據需求畫圖,把流程用圖形的形式展現出來,會幫助你理解
b、覺得它是一個遞迴,那就嘗試建立它的數學函式模型
c、用程式碼進行實現數學函式模型
d、程式碼推導,與需求是否一致

在知道步驟之後,我們必須要知道遞迴的幾個原則,在這之前,我們有必要知道遞迴在程式碼中的骨架是如何的:


    method(){
        if(條件滿足) 
            return//結束遞迴(建立在基準情況之上)
        else
            method()//繼續遞迴(不斷推進)
    }

a、基準情況:函數出口,也就是說此時的函式的值可以直接算出,不需要通過遞迴求得。或者理解為最後一次遞迴操作。
b、不斷推進

:意思就是說每次進行一次遞迴,那麼下次需要的遞迴會逐漸靠近基準情況,注意這個逐漸靠近是建立在能到達基準情況的,比如說下面這段遞迴程式碼:

package com.bw;

/**
 * @author brickworker
 * 關於類RecursiveTest的描述:遞迴展示類
 */
public class RecursiveTest {

    public static int run(int k){
        if(k == 0)
            return 0;
        else
            return run(k/3+1) +k-1;
    }

}

我們通過上述程式碼理解一下遞迴規則,首先因為if(k == 0)結束函式呼叫,說明它具備基準情況。然後我們看下它的不斷推進,發現當run(1)執行後又要去求得run(1),使得程式碼變成了無限迴圈。難倒你說不靠近基準情況嘛?其實是靠近的,比如說run(10)就要先求的run(4),但是它沒有建立在對於run(0)可達的基礎之上,所以說這個遞迴是不合法的。

斐波那契數列

斐波那契數列應該算是遞迴的經典了,我們就從這個經典作為第一個簡單的例子幫大家如何處理遞迴,寫出遞迴程式碼:

斐波那契數列是指1,1,2,3,5,8,13,21…的一個數列,它的規律就是某一個數就是前面兩個數的只和,博主對於這個數列畫圖如下:
初步畫圖

仔細觀察上面的圖,我覺得它們之間存在一種遞迴的關係,嘗試建立它的數學函式模型,假設21為f(n),那麼13就是f(n-1),8就是f(n-2)。那麼就可以總結出它們的規律就是f(n) = f(n-1)+f(n-2)在n>=2的情況下。

那麼我們馬上就可以寫出它的程式碼形式了:

package com.bw;

/**
 * @author brickworker
 * 關於類RecursiveTest的描述:遞迴展示類
 */
public class RecursiveTest {

    public static int fbnq(int n){
        if( n== 2 || n==1)
            return 1;
        else
            return fbnq(n-1)+fbnq(n-2);

    }

    public static void main(String[] args) {
        System.out.println(fbnq(8));
    }
}
//輸出結果:21

最後,我們需要用寫出的程式碼進行驗證需求,如果滿足需求,遞迴就完成了從思考,畫圖,抽象,編寫的步驟。其實最後的驗證顯得非常的不專業和不標準,雖然說特殊不能代表一般,但是我們在現實生活中相信大家很多時候都是用幾組資料測試一下吧。

全排列問題

如果說斐波那契數列是遞迴入門,那麼給定一個字串,輸出它的全排列就顯得比較困難了。

全排列:
假如輸入字串為qwe,那麼輸出就是要qwe,qew,wqe,weq,eqw,ewq六種情況。拿到這個問題,之後我們要開始尋找規律了,總共有3個坑位,有3個字母來佔坑。那麼我們可以抓住坑位來進行分析,畫出的圖如下:
這裡寫圖片描述

分析題目我們就知道輸出的字元長度和輸入肯定是一樣的,接著從圖中可以看出,對所有的字元進行了3次分配,那麼我們先考慮第一個坑位,在第一個坑位分配的時候,需要從給出的字串中任意選擇一個字元填入,既然是全排列,所以坑位對於字元的要求肯定是都要有的,所以在這一步可以考慮用for迴圈新增字元。接下來我們考慮第二個坑位,這個坑位要填入的字元是除卻第一個坑位已經用掉的字元所剩下的字元陣列中選擇一個填入,因為是要求全排列,所以也必然用for迴圈解決。那麼基準情形就是最後一次執行就是還剩下一個字元的時候,直接就放在最後一位。那麼,分析到這裡我們大致可以推算出這個數學函式模型應該是這樣的:

f(char[], int),其中char[]陣列表示組合的結果,int值表示處理第幾個坑位,同時我們還知道肯定存在一個for迴圈去處理這個char[]陣列,而且需要去除已經使用的字元。不知道博主如此解釋,能看懂不?

下面就是博主寫的程式碼,每一句我都寫了註釋:

package com.bw;

/**
 * @author brickworker
 * 關於類RecursiveTest的描述:遞迴展示類
 */
public class RecursiveTest {

    public static void permutation(char[] ch, int n){//ch表示重新排列後的字元陣列,n表示處理第幾個坑位
        if(n == ch.length)//基準情形
            System.out.println(String.valueOf(ch));
        else{
            for (int i = n; i < ch.length; i++) {//把n賦值給i用於前面說到的除卻已使用的字元
                //以下操作其實是進行了字元交換
                char temp = ch[n];//把要處理的位置的資料拿出來,防止覆蓋
                ch[n] = ch[i]; //對第n個坑位進行賦值
                ch[i] = temp;//賦值結束之後需要把前面儲存的資料賦值給替換的
                permutation(ch, n+1);//繼續下個坑位賦值]
                //因為上面的交換導致ch陣列順序出錯,遞迴以後需要矯正,不然遍歷會出錯
                temp = ch[i];
                ch[i] = ch[n];
                ch[n] = temp;
            }
        }
    }

    public static void main(String[] args) {
        permutation("abc".toCharArray(), 0);
    }

//  執行結果:
//  acb
//  bac
//  bca
//  cba
//  cab
}

對於上面的函式程式碼,已經是非常簡便的了,可能中間那塊交換的程式碼比較不好理解,我這裡再解釋一下,為什麼要交換?因為char[]就是你輸入的字串,也是要輸出的字串,所以當對某一個坑位進行賦值的時候,不能覆蓋這個坑位原有的字元,所以要把這個要處理的坑位原本的值用temp儲存起來。再者為什麼迭代後又需要交換回來呢?也是由於因為char[]即是輸入也是輸出,再迴圈的過程中,你改變了迴圈物件本身,如果不改變回來,那麼後面的迴圈就是錯誤的,操作的不再是原來的陣列了。

再者,為什麼說上述程式碼是最簡版了,博主考慮了2種處理方法:
1、建立一個char[] temp來接收入參的char[]陣列。但是這個方式要注意,即使你char[] temp = ch是不能解決問題的,你只是改變了引用罷了,修改了陣列順序ch也是會變動的。你必須要new一個出來才行,同時代碼量很多。

2、處理函式入參,改成f(char[] ch, String result, int n),多一個result來儲存結果也可以解決這個問題。大家可以試一試,但是程式碼就顯得一點都不優雅,你覺得呢?

總結

遞迴其實是非常奇妙的,有的時候壓根感覺不到能用遞迴處理,但是你通過畫圖分析之後,發現存在一個基準情況,而且通過一步步的推到會靠近這個基準情況。找到這種規律之後,我們需要抽象出一個數學函式模型,然後用程式碼來實現這個函式模型,編寫結束之後,再去驗證程式碼的執行結果是否滿足需求。
好啦,今天就寫到這裡了。如果大家有什麼問題可以聯絡我:
這裡寫圖片描述

相關推薦

深入淺出思想

引言 作為一名程式設計師,演算法是不可或缺的。每個人對演算法的敏感度不一樣,就想每個人對數學的敏感度一樣,有些人邏輯能力就是很強。有一說一,博主個人覺得自己的數學學的不咋滴,但是像我這樣笨笨的人,我可以多複習,多推演嘛。今天我想寫的是關於遞迴的理解和使用,我會

思想和例項

先給一個簡單的階乘例子: public static int getDg(int x){ System.out.println(x); if (x==1) { return 1; } if (x<4) { return x * getDg(x-1);

深入理解思想

1、什麼是遞迴 本質上,將原來的問題轉換為更小的同一問題。問題規模可以不斷縮小,直到達到一個不能再縮小的基本問題,解決這個基本問題,就解決了整個問題。 例如,使用遞迴思想對自然數1、2、3…n-1 、n求和: sum(n) = n +sum(n-1); //sum(n-1)就是被

列印連結串列(學習思想)——牛客

題目描述 輸入一個連結串列,按連結串列值從尾到頭的順序返回一個ArrayList。 問題分析 注意從尾到頭,這個很符合棧的特性——FILO,考慮用棧。既然想到用棧的形式,可以聯想到遞迴方法,最終確定為遞迴解決本題。 程式碼實現 直接使用當前函式 clas

約瑟夫環思想

轉自:開啟連結 題目描述:30個遊客同乘一條船,因為嚴重超載, 加上風浪大作,危險萬分。因此船長告訴乘客,只有將全船 一半的旅客投入海中,其餘人才能倖免於難。無奈,大家只 得同意這種辦法,並議定30 個人圍成一圈,由第一個人數起,依次報數,數到第9人,便把他投入大海中,然後

Unity思想 階乘 1 1 2 3 5 8 13 和尋找子物體

遞迴的核心思想就是自己呼叫自己,只要能說出來,就能用程式碼寫出來   public int 階乘(int index)     {          &nbs

高維空間中的體積(包含思想的初步理解)

n維超球體的體積的變化的特點:當n<=7的時候,體積是增大的;當n>7的時候,體積是縮小的,可以小到0 因此可以從中推出,如果以固定的半徑進行取樣,這取到的樣本的數量是先增大,然後再縮小的。 遞迴思想的通俗理解:你打開面前這扇門,看到屋裡面還有一扇門。你走過去,發現手中的鑰匙還可以開啟它,你推

思想以及例項練習

思想分析:      1.對問題的細化成小問題。      2.自己呼叫比自己小一個規模的自己。      3.有結束條件。 滿足條件:      1.有遞迴公式。問題能夠分解為一個一個於自身類似的小問題。      2.有確切的邊界。能夠最後分解為一個有確定解

思想和迭代思想

遞迴思想(遞迴函式) 遞迴思想的一個基本形式是:在一個函式中,有至少一條語句,又會去呼叫該函式自身。 但是,從程式碼角度來說,如果單純是函式內部呼叫函式本,則會出現“出不來”的現象。 則我們就必須再來解決下一個問題: 怎麼終止(停止)這種呼叫——找到遞迴函式的出口。 遞推思想(迭代思想) 遞推思想本身並

C++快速排序,分治思想

 c++快排演算法,自己用來做筆記,時間原因寫的比較亂,可以參考一下。 int array[] = { 60, 68, 59, 52, 72, 28, 96, 33, 24 }; 0 1 2 3 4 5

生兔子問題(思想)

有一對兔子,從出生後第四個月起每個月都生一對兔子,小兔子長到第四個月後每個月又生一對兔子。假如兔子都不死,計算第十個月兔子的總數? 分析:F(N) = f(n-1)+ f(n-3)。可以運用遞迴來解決問題。 當出生後第三個月開始生兔子: F(N) = f(n-1)+ f(

HDU - 1995 奇妙的塔 (漢諾塔思想詳解)

用1,2,...,n表示n個盤子,稱為1號盤,2號盤,...。號數大盤子就大。經典的漢諾塔問  題經常作為一個遞迴的經典例題存在。可能有人並不知道漢諾塔問題的典故。漢諾塔來源於  印度傳說的一個故事,上帝創造世界時作了三根金剛石柱子,在一根柱子上從下往上按大小 

快速排序思想 n個元素中第m小的元素

利用快速排序的思想,編寫一個遞迴演算法,求出給定的n個元素中第m個最小的元素 思路:取陣列第m個元素也就是a[m-1]作為每一次迴圈的一個閾值,將大於a[m-1]的全部放在陣列的右邊,小於a[m-1]的全部放在陣列的左邊,如果一直這樣迴圈下去,那麼最後a[m-1]這個單元中

050day(思想在問題分解上的應用(爬樓梯))

172210704111-陳國佳總結《2017年11月29日》【連續050天】 標題:遞迴思想在問題分解上的應用(爬樓梯); 內容:用遞迴將問題分解為規模更小的子問題進行求解; 例題:爬樓梯,樹老師爬樓梯,他可以每次走1級或者2級,輸出樓梯的級數,求不同的走法數; 輸入包含

思想解決漢諾塔的問題

【解決思路】 以3個塔柱為例 鐵柱x  鐵柱y  鐵柱z 總共64個盤子 我們把所有的呃思路聚集為以下兩個問題: 問題1: 將X上的63個盤子藉助z移動到y上 問題2: 將Y上的63個盤子接住X移動到Z上 然後用這個方法遞迴---------------- 問題1的

Java思想倒置陣列

public class ReverseArry { public static void main(String[] args){ int[] arr = {1,2,3,4,5,6,7,8,9}; //System.out.println(arr.l

java中的思想及應用

遞迴就是自己調自己,最需要注意的就是結束條件,否則可能就是死迴圈,導致記憶體溢位 public T a(Object x,Object y) {   if(條件true) {   a(x1,y1); } else {   return f(x,y);

Java思想分析

先舉個例子:定義一個 sumByMax(int max)方法,求 1+…+max 的和 這個例子簡單的來講就是將1到max的所有整數都加起來,如果先不考慮用遞迴,那麼這就是一個個重複的累加步驟可以用迴圈來解決: public int sumByMax(int

jQuery中利用思想練習自定義動畫

技術QQ交流群:294088839 <!DOCTYPE html> <html> <head lang="en">     <meta charset="UTF-8">     <title>自定義動畫</t

通過java思想實現以樹形方式展現出該目錄中的所有子目錄和檔案

當初在開始接觸Java時  學習File部分的一個小練習  挺有意思 一開始是通過看 北京聖思園 張龍老師的視訊開始學校java的,必須強烈推薦,真很棒。 功能實現:主要實現以樹形方式展現出該目錄中的所有子目錄和檔案。 另外, 在展現的時候將目錄排在上面,檔案排在下面。每一層要加上縮排。 檔案是jre6資料