遞歸就這么簡單

遞歸介紹

本來預算此章節是繼續寫快速排序的,然而編寫快速排序往往是遞歸來寫的,并且遞歸可能不是那么好理解,于是就有了這篇文章。

在上面提到了遞歸這么一個詞,遞歸在程序語言中簡單的理解是:方法自己調用自己

遞歸其實和循環是非常像的,循環可以改寫成遞歸,遞歸未必能改寫成循環,這是一個充分不必要的條件。

  • 那么,有了循環,為什么還要用遞歸呢??在某些情況下(費波納切數列,漢諾塔),使用遞歸會比循環簡單很多很多
  • 話說多了也無益,讓我們來感受一下遞歸吧。

我們初學編程的時候肯定會做過類似的練習:

  • 1+2+3+4+....+100(n)求和
  • 給出一個數組,求該數組內部的最大值

我們要記住的是,想要用遞歸必須知道兩個條件:

  • 遞歸出口(終止遞歸的條件)
  • 遞歸表達式(規律)

技巧:在遞歸中常常是將問題切割成兩個部分(1和整體的思想),這能夠讓我們快速找到遞歸表達式(規律)

一、求和

如果我們使用for循環來進行求和1+2+3+4+....+100,那是很簡單的:

int sum = 0;for (int i = 1; i <= 100; i++) {sum = sum + i;}System.out.println("公眾號:Java3y:" + sum);

前面我說了,for循環都可以使用遞歸來進行改寫,而使用遞歸必須要知道兩個條件:1、遞歸出口,2、遞歸表達式(規律)

首先,我們來找出它的規律:1+2+3+...+n,這是一個求和的運算,那么我們可以假設X=1+2+3+...+n,可以將1+2+3+...+(n-1)看成是一個整體。而這個整體做的事又和我們的**初始目的(求和)**相同。以我們的高中數學知識我們又可以將上面的式子看成X=sum(n-1)+n

好了,我們找到我們的遞歸表達式(規律),它就是sum(n-1)+n,那遞歸出口呢,這個題目的遞歸出口就有很多了,我列舉一下:

  • 如果n=1時,那么就返回1
  • 如果n=2時,那么就返回3(1+2)
  • 如果n=3時,那么就返回6(1+2+3)

當然了,我肯定是使用一個最簡單的遞歸出口了:if(n=1) return 1

遞歸表達式和遞歸出口我們都找到了,下面就代碼演示:

遞歸出口為1:

public static void main(String[] args) {System.out.println("公眾號:Java3y:" + sum(100));}/**** @param n 要加到的數字,比如題目的100* @return*/public static int sum(int n) {if (n == 1) {return 1;} else {return sum(n - 1) + n;}}

遞歸出口為4:

public static void main(String[] args) {System.out.println("公眾號:Java3y:" + sum(100));}/**** @param n 要加到的數字,比如題目的100* @return*/public static int sum(int n) {//如果遞歸出口為4,(1+2+3+4)if (n == 4) {return 10;} else {return sum(n - 1) + n;}}

結果都是一樣的。

二、數組內部的最大值

如果使用的是循環,那么我們通常這樣實現:

int[] arrays = {2, 3, 4, 5, 1, 5, 2, 9, 5, 6, 8, 3, 2};//將數組的第一個假設是最大值int max = arrays[0];for (int i = 1; i < arrays.length; i++) {if (arrays[i] > max) {max = arrays[i];}}System.out.println("公眾號:Java3y:" + max);

那如果我們用遞歸的話,那怎么用弄呢?首先還是先要找到遞歸表達式(規律)和遞歸出口

  • 我們又可以運用1和整體的思想來找到規律
    • 將數組第一個數->2與數組后面的數->{3, 4, 5, 1, 5, 2, 9, 5, 6, 8, 3, 2}進行切割,將數組后面的數看成是一個整體X={3, 4, 5, 1, 5, 2, 9, 5, 6, 8, 3, 2},那么我們就可以看成是第一個數和一個整體進行比較if(2>X) return 2 else(2<X) return X
    • 而我們要做的就是找出這個整體的最大值與2進行比較。找出整體的最大值又是和我們的**初始目的(找出最大值)**是一樣的
    • 也就可以寫成if( 2>findMax() )return 2 else return findMax()
  • 遞歸出口,如果數組只有1個元素時,那么這個數組最大值就是它了。

使用到數組的時候,我們通常為數組設定左邊界和右邊界,這樣比較好地進行切割

  • L表示左邊界,往往表示的是數組第一個元素,也就會賦值為0(角標為0是數組的第一個元素)
  • R表示右邊界,往往表示的是數組的長度,也就會賦值為arrays.length-1(長度-1在角標中才是代表最后一個元素)

那么可以看看我們遞歸的寫法了:

public static void main(String[] args) {int[] arrays = {2, 3, 4, 5, 1, 5, 2, 9, 5, 6, 8, 3, 1};System.out.println("公眾號:Java3y:" + findMax(arrays, 0, arrays.length - 1));}/*** 遞歸,找出數組最大的值* @param arrays 數組* @param L      左邊界,第一個數* @param R      右邊界,數組的長度* @return*/public static int findMax(int[] arrays, int L, int R) {//如果該數組只有一個數,那么最大的就是該數組第一個值了if (L == R) {return arrays[L];} else {int a = arrays[L];int b = findMax(arrays, L + 1, R);//找出整體的最大值if (a > b) {return a;} else {return b;}}}

三、冒泡排序遞歸寫法

在冒泡排序章節中給出了C語言的遞歸實現冒泡排序,那么現在我們已經使用遞歸的基本思路了,我們使用Java來重寫一下看看:

冒泡排序:倆倆交換,在第一趟排序中能夠將最大值排到最后面,外層循環控制排序趟數,內層循環控制比較次數

以遞歸的思想來進行改造:

  • 當第一趟排序后,我們可以將數組最后一位(R)和數組前面的數(L,R-1)進行切割,數組前面的數(L,R-1)看成是一個整體,這個整體又是和我們的**初始目的(找出最大值,與當前趟數的末尾處交換)**是一樣的
  • 遞歸出口:當只有一個元素時,即不用比較了:L==R
public static void main(String[] args) {int[] arrays = {2, 3, 4, 5, 1, 5, 2, 9, 5, 6, 8, 3, 1};bubbleSort(arrays, 0, arrays.length - 1);System.out.println("公眾號:Java3y:" + arrays);}public static void bubbleSort(int[] arrays, int L, int R) {int temp;//如果只有一個元素了,那什么都不用干if (L == R) ;else {for (int i = L; i < R; i++) {if (arrays[i] > arrays[i + 1]) {temp = arrays[i];arrays[i] = arrays[i + 1];arrays[i + 1] = temp;}}//第一趟排序后已經將最大值放到數組最后面了//接下來是排序"整體"的數據了bubbleSort(arrays, L, R - 1);}}

四、斐波那契數列

接觸過C語言的同學很可能就知道什么是費波納切數列了,因為往往做練習題的時候它就會出現,它也是遞歸的典型應用。

菲波那切數列長這個樣子:{1 1 2 3 5 8 13 21 34 55..... n }

數學好的同學可能很容易就找到規律了:前兩項之和等于第三項

例如:

 	1 + 1 = 22 + 3 = 513 + 21 = 34

如果讓我們求出第n項是多少,那么我們就可以很簡單寫出對應的遞歸表達式了:Z = (n-2) + (n-1)

遞歸出口在本題目是需要有兩個的,因為它是前兩項加起來才得出第三項的值

同樣地,那么我們的遞歸出口可以寫成這樣:if(n==1) retrun 1 if(n==2) return 2

下面就來看一下完整的代碼吧:

public static void main(String[] args) {int[] arrays = {1, 1, 2, 3, 5, 8, 13, 21};//bubbleSort(arrays, 0, arrays.length - 1);int fibonacci = fibonacci(10);System.out.println("公眾號:Java3y:" + fibonacci);}public static int fibonacci(int n) {if (n == 1) {return 1;} else if (n == 2) {return 1;} else {return (fibonacci(n - 1) + fibonacci(n - 2));}}

五、漢諾塔算法

圖片來源百度百科:

玩漢諾塔的規則很簡單:

  • 有三根柱子,原始裝滿大小不一的盤子的柱子我們稱為A,還有兩根空的柱子,我們分別稱為B和C(任選)
  • 最終的目的就是將A柱子的盤子全部移到C柱子中
    • 移動的時候有個規則:一次只能移動一個盤子,小的盤子不能在大的盤子上面(反過來:大的盤子不能在小的盤子上面)

我們下面就來玩一下:

  • 只有一個盤子:
    • A柱子的盤子直接移動到C柱子中
    • 完成游戲
  • 只有兩個盤子:
    • 將A柱子上的盤子移動到B柱子中
    • 將A柱子上的盤子移動到C柱子中
    • 最后將在B柱子的盤子移動到C柱子盤子中
    • 完成游戲
  • 只有三個盤子:
    • 將A柱子的盤子移動到C柱子中
    • 將A柱子上的盤子移動到B柱子中
    • 將C柱子盤子移動到B柱子盤子中
    • 將A柱子的盤子移動到C柱子中
    • 將B柱子的盤子移動到A柱子中
    • 將B柱子的盤子移動到C柱子中
    • 最后將A柱子的盤子移動到C柱子中
    • 完成游戲

.......................

從前三次玩法中我們就可以發現的規律:

  • 想要將最大的盤子移動到C柱子,就必須將其余的盤子移到B柱子處(借助B柱子將最大盤子移動到C柱子中[除了最大盤子,將所有盤子移動到B柱子中])[遞歸表達式]
  • 當C柱子有了最大盤子時,所有的盤子在B柱子。現在的目的就是借助A柱子將B柱子的盤子都放到C柱子中(和上面是一樣的,已經發生遞歸了)
  • 當只有一個盤子時,就可以直接移動到C柱子了(遞歸出口)
    • A柱子稱之為起始柱子,B柱子稱之為中轉柱子,C柱子稱之為目標柱子
    • 從上面的描述我們可以發現,起始柱子、中轉柱子它們的角色是會變的(A柱子開始是起始柱子,第二輪后就變成了中轉柱子了。B柱子開始是目標柱子,第二輪后就變成了起始柱子。總之,起始柱子、中轉柱子的角色是不停切換的)

簡單來說就分成三步:

  1. 把 n-1 號盤子移動到中轉柱子
  2. 把最大盤子從起點移到目標柱子
  3. 把中轉柱子的n-1號盤子也移到目標柱子

那么就可以寫代碼測試一下了(回看上面玩的過程):

public static void main(String[] args) {int[] arrays = {1, 1, 2, 3, 5, 8, 13, 21};//bubbleSort(arrays, 0, arrays.length - 1);//int fibonacci = fibonacci(10);hanoi(3, 'A', 'B', 'C');System.out.println("公眾號:Java3y" );}/*** 漢諾塔* @param n n個盤子* @param start 起始柱子* @param transfer 中轉柱子* @param target 目標柱子*/public static void hanoi(int n, char start, char transfer, char target) {//只有一個盤子,直接搬到目標柱子if (n == 1) {System.out.println(start + "---->" + target);} else {//起始柱子借助目標柱子將盤子都移動到中轉柱子中(除了最大的盤子)hanoi(n - 1, start, target, transfer);System.out.println(start + "---->" + target);//中轉柱子借助起始柱子將盤子都移動到目標柱子中hanoi(n - 1, transfer, start, target);}}

我們來測試一下看寫得對不對:

參考資料:

  • https://www.zhihu.com/question/24385418

六、總結

遞歸的確是一個比較難理解的東西,好幾次都把我繞進去了....

要使用遞歸首先要知道兩件事:

  • 遞歸出口(終止遞歸的條件)
  • 遞歸表達式(規律)

在遞歸中常常用”整體“的思想,在漢諾塔例子中也不例外:將最大盤的盤子看成1,上面的盤子看成一個整體。那么我們在演算的時候就很清晰了:將”整體“搬到B柱子,將最大的盤子搬到C柱子,最后將B柱子的盤子搬到C柱子中

因為我們人腦無法演算那么多的步驟,遞歸是用計算機來干的,只要我們找到了遞歸表達式和遞歸出口就要相信計算機能幫我們搞掂。

在編程語言中,遞歸的本質是方法自己調用自己,只是參數不一樣罷了。

最后,我們來看一下如果是5個盤子,要運多少次才能運完:

PS:如果有更好的理解方法,或者我理解錯的地方大家可以在評論下留言一起交流哦!

如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y

轉載于:https://www.cnblogs.com/Java3y/p/8610134.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/252157.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/252157.shtml
英文地址,請注明出處:http://en.pswp.cn/news/252157.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

阿里云RPA(機器人流程自動化)干貨系列之二:認識RPA(下)

2019獨角獸企業重金招聘Python工程師標準>>> 導讀&#xff1a;本文是阿里云RPA&#xff08;機器人流程自動化&#xff09;干貨系列之二&#xff0c;主要介紹了RPA的發展齊納經和主要使用場景有哪些&#xff0c;目前國內外主流的RPA廠商以及RPA的未來在哪。 一、RPA的…

C# 圖片的讀取

//圖片轉成二進制public byte[] GetPictureData(string imagepath){/**/根據圖片文件的路徑使用文件流打開&#xff0c;并保存為byte[] FileStream FileStream new FileStream(imagepath, FileMode.Open);byte[] byData new byte[FileStream.Length];FileStream.Read(byData,…

SDOI2010 地精部落

題目描述 傳說很久以前&#xff0c;大地上居住著一種神秘的生物&#xff1a;地精。 地精喜歡住在連綿不絕的山脈中。具體地說&#xff0c;一座長度為N的山脈H可分為從左到右的N段&#xff0c;每段有一個[b][u]獨一無二[/u][/b]的高度Hi&#xff0c;其中Hi是1到N之間的正整數。 …

Codechef Yet another cute girl

題意大概就是讓你求一下[L,R]中的約數個數是素數的數的個數。 其中1<L<R<1e12,R-L<1e6. 然后我寫了兩種做法&#xff0c;第一種是可以直接搞出來L-R的約數個數&#xff0c;然后直接統計一下就好了。 這個的復雜度大致是 O((R-L) * log(R-L)) 第二種就是需要先發現滿…

簡單弄一個-個人主頁

--- 整理一下已經發表的文章 JAVA基礎 java基礎數據結構之-紅黑樹(插入)java基礎數據結構之-紅黑樹(刪除)了解一下jdk動態代理的本質了解一下cglib動態代理的本質SpringBoot源碼解析 前言&#xff1a;閱讀springboot源碼之前&#xff0c;最好對spring源碼有一定的了解&#xff…

Halocn OCR識別入門學習

一、建立OCR庫 dev_close_window() read_image(Image,OCR) get_image_size(Image,Width,Hight) dev_open_window(0,0,Width,Hight,black,Window) dev_display(Image)*字符處理 rgb1_to_gray(Image,ImageGray) *鼠標畫你要找的roi區域 draw_rectangle1(Window,Row1,Column1,Row…

ctsc2009 移民站選址

分析&#xff1a;非常非常好的一道題&#xff01; 首先需要對問題進行轉化&#xff1a; 行列無關&#xff0c;對于行單獨處理&#xff0c;對于列單獨處理必然存在一個最優方案使得每一個新站與舊站重合.轉化1很顯然&#xff0c;對于轉化2&#xff0c;是一類非常經典的“中位數問…

Jenkins 安裝與使用--實例

參考了博客Jenkins master在windows上安裝 Jenkins的主要功能是監視反復工作的運行&#xff0c;比如軟件project的構建詳細地&#xff1a; *軟件的持續構建和測試 本質上提供了一個易于使用的持續集成系統。使得開發者更easy地將改變集成到project中。使得用戶更easy獲得一個…

后端項目搭建技術棧

Koa2&#xff1a;koa-bodyparser koa-router koa-session koa-corsTypeScript數據庫&#xff1a;Mysql &#xff08;庫&#xff1a;Sequelize&#xff09;表單驗證庫&#xff1a;Joi

C# 實體類幾種深拷貝的方法——解決關于對象賦值,A=B,A改變,B也改變問題

幾種常見的深拷貝方式 1、利用反射實現 public static T DeepCopyByReflection<T>(T obj) {   if (obj is string || obj.GetType().IsValueType)   return obj; object retval Activator.CreateInstance(obj.GetType());   FieldInfo[] fields obj.GetType().…

Hadoop學習之路(九)HDFS深入理解

HDFS的優點和缺點 HDFS的優點 1、可構建在廉價機器上 通過多副本提高可靠性&#xff0c;提供了容錯和恢復機制 服務器節點的宕機是常態 必須理性對象 2、高容錯性 數據自動保存多個副本&#xff0c;副本丟失后&#xff0c;自動恢復 HDFS的核心設計思想&#xff1a; 分散均勻…

關于Unity中的聲音管理模塊(專題七)

聲音的要素 1: 音頻文件AudioClip2: 音源AudioSource;3: 耳朵AudioListener;//全局只能有一個4: 2D/3D音頻;//2D只是簡單地播放聲音&#xff0c;3D可以根據距離衰減音量 怎樣聽到聲音&#xff1a; 創建一個節點&#xff0c;掛載AudioSource組件&#xff0c;AudioSource組件關聯…

重啟唯一的窗體實例,以及調用系統重啟函數失敗解決辦法

1、修改Program.cs內的程序啟動函數 static class Program{public static System.Threading.Mutex Instance;/// <summary>/// 應用程序的主入口點。/// </summary>[STAThread]static void Main(){Application.EnableVisualStyles();Application.SetCompatibleTe…

ThreadLocal可能引起的內存泄露

threadlocal里面使用了一個存在弱引用的map,當釋放掉threadlocal的強引用以后,map里面的value卻沒有被回收.而這塊value永遠不會被訪問到了. 所以存在著內存泄露. 最好的做法是將調用threadlocal的remove方法. 在threadlocal的生命周期中,都存在這些引用. 看下圖: 實線代表強引…

codevs 1576 最長嚴格上升子序列

題目鏈接&#xff1a;http://codevs.cn/problem/1576/題目描述 Description給一個數組a1, a2 ... an&#xff0c;找到最長的上升降子序列ab1<ab2< .. <abk&#xff0c;其中b1<b2<..bk。 輸出長度即可。 輸入描述 Input Description第一行&#xff0c;一個整數N。…

nginx服務器開啟緩存、反向代理

一、反向代理配置 1、反向代理服務器配置如下 反向代理就是需要這一行proxy_pass來完成。當我們要訪問后端web服務器的時候&#xff0c;我們只需要訪問代理服務器就可以了&#xff0c;此時代理服務器就充當后端web服務器的角色。proxy_pass依賴的模塊是&#xff1a; 至于后兩行…

Halcon:區域特征:select_shape(Regions : SelectedRegions : Features, Operation, Min, Max : )

Region特征一覽&#xff1a; 特征 英 譯 備注 area Area of the object 對象的面積 row Row index of the center 中心點的行坐標 column Column index of the center 中心點的列坐標 width Width of the region 區域的寬度 height Height of the…

Web應用主動偵測工具Skipfish

Web應用主動偵測工具SkipfishSkipfish是Kali Linux附帶的一個主動Web應用偵測工具。該工具會首先盡可能獲取所有網站路徑&#xff0c;進行訪問&#xff0c;然后根據返回的內容&#xff0c;檢測是否存在漏洞。該工具采用字典爆破和網頁爬行兩種方式獲取網站。一旦獲取網頁內容&a…

7步讓你get首個數據科學實習

由于數據科學的龐大和復雜&#xff0c;如果你沒有相關的實習經歷的話&#xff0c;成為數據科學家的道路將會更加艱巨和困難。即使是經驗豐富的人&#xff0c;實習也是轉型進入數據科學領域的一種有效方式。 那么&#xff0c;尋找數據科學實習有哪些技巧&#xff1f;本文總結了數…

Halcon:Image、region、xld常用的處理

一、讀取文件夾中的所有圖片 list_files (C:/Users/fuping.liu/Desktop/檳榔有無頭/有頭, [files,follow_links], ImageFiles) tuple_regexp_select (ImageFiles, [\(tif|tiff|gif|bmp|jpg|jpeg|jp2|png|pcx|pgm|ppm|pbm|xwd|ima|hobj)$,ignore_case], ImageFiles)for Index :…