摘要:那么,有了循環(huán),為什么還要用遞歸呢在某些情況下費(fèi)波納切數(shù)列,漢諾塔,使用遞歸會(huì)比循環(huán)簡(jiǎn)單很多很多話說(shuō)多了也無(wú)益,讓我們來(lái)感受一下遞歸吧。
遞歸介紹
本來(lái)預(yù)算此章節(jié)是繼續(xù)寫(xiě)快速排序的,然而編寫(xiě)快速排序往往是遞歸來(lái)寫(xiě)的,并且遞歸可能不是那么好理解,于是就有了這篇文章。
在上面提到了遞歸這么一個(gè)詞,遞歸在程序語(yǔ)言中簡(jiǎn)單的理解是:方法自己調(diào)用自己
遞歸其實(shí)和循環(huán)是非常像的,循環(huán)都可以改寫(xiě)成遞歸,遞歸未必能改寫(xiě)成循環(huán),這是一個(gè)充分不必要的條件。
那么,有了循環(huán),為什么還要用遞歸呢??在某些情況下(費(fèi)波納切數(shù)列,漢諾塔),使用遞歸會(huì)比循環(huán)簡(jiǎn)單很多很多
話說(shuō)多了也無(wú)益,讓我們來(lái)感受一下遞歸吧。
我們初學(xué)編程的時(shí)候肯定會(huì)做過(guò)類(lèi)似的練習(xí):
1+2+3+4+....+100(n)求和
給出一個(gè)數(shù)組,求該數(shù)組內(nèi)部的最大值
我們要記住的是,想要用遞歸必須知道兩個(gè)條件:
遞歸出口(終止遞歸的條件)
遞歸表達(dá)式(規(guī)律)
技巧:在遞歸中常常是將問(wèn)題切割成兩個(gè)部分(1和整體的思想),這能夠讓我們快速找到遞歸表達(dá)式(規(guī)律)
一、求和如果我們使用for循環(huán)來(lái)進(jìn)行求和1+2+3+4+....+100,那是很簡(jiǎn)單的:
int sum = 0; for (int i = 1; i <= 100; i++) { sum = sum + i; } System.out.println("公眾號(hào):Java3y:" + sum);
前面我說(shuō)了,for循環(huán)都可以使用遞歸來(lái)進(jìn)行改寫(xiě),而使用遞歸必須要知道兩個(gè)條件:1、遞歸出口,2、遞歸表達(dá)式(規(guī)律)
首先,我們來(lái)找出它的規(guī)律:1+2+3+...+n,這是一個(gè)求和的運(yùn)算,那么我們可以假設(shè)X=1+2+3+...+n,可以將1+2+3+...+(n-1)看成是一個(gè)整體。而這個(gè)整體做的事又和我們的初始目的(求和)相同。以我們的高中數(shù)學(xué)知識(shí)我們又可以將上面的式子看成X=sum(n-1)+n
好了,我們找到我們的遞歸表達(dá)式(規(guī)律),它就是sum(n-1)+n,那遞歸出口呢,這個(gè)題目的遞歸出口就有很多了,我列舉一下:
如果n=1時(shí),那么就返回1
如果n=2時(shí),那么就返回3(1+2)
如果n=3時(shí),那么就返回6(1+2+3)
當(dāng)然了,我肯定是使用一個(gè)最簡(jiǎn)單的遞歸出口了:if(n=1) return 1
遞歸表達(dá)式和遞歸出口我們都找到了,下面就代碼演示:
遞歸出口為1:
public static void main(String[] args) { System.out.println("公眾號(hào):Java3y:" + sum(100)); } /** * * @param n 要加到的數(shù)字,比如題目的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("公眾號(hào):Java3y:" + sum(100)); } /** * * @param n 要加到的數(shù)字,比如題目的100 * @return */ public static int sum(int n) { //如果遞歸出口為4,(1+2+3+4) if (n == 4) { return 10; } else { return sum(n - 1) + n; } }
結(jié)果都是一樣的。
二、數(shù)組內(nèi)部的最大值如果使用的是循環(huán),那么我們通常這樣實(shí)現(xiàn):
int[] arrays = {2, 3, 4, 5, 1, 5, 2, 9, 5, 6, 8, 3, 2}; //將數(shù)組的第一個(gè)假設(shè)是最大值 int max = arrays[0]; for (int i = 1; i < arrays.length; i++) { if (arrays[i] > max) { max = arrays[i]; } } System.out.println("公眾號(hào):Java3y:" + max);
那如果我們用遞歸的話,那怎么用弄呢?首先還是先要找到遞歸表達(dá)式(規(guī)律)和遞歸出口
我們又可以運(yùn)用1和整體的思想來(lái)找到規(guī)律
將數(shù)組第一個(gè)數(shù)->2與數(shù)組后面的數(shù)->{3, 4, 5, 1, 5, 2, 9, 5, 6, 8, 3, 2}進(jìn)行切割,將數(shù)組后面的數(shù)看成是一個(gè)整體X={3, 4, 5, 1, 5, 2, 9, 5, 6, 8, 3, 2},那么我們就可以看成是第一個(gè)數(shù)和一個(gè)整體進(jìn)行比較if(2>X) return 2 else(2
而我們要做的就是找出這個(gè)整體的最大值與2進(jìn)行比較。找出整體的最大值又是和我們的初始目的(找出最大值)是一樣的
也就可以寫(xiě)成if( 2>findMax() )return 2 else return findMax()
遞歸出口,如果數(shù)組只有1個(gè)元素時(shí),那么這個(gè)數(shù)組最大值就是它了。
使用到數(shù)組的時(shí)候,我們通常為數(shù)組設(shè)定左邊界和右邊界,這樣比較好地進(jìn)行切割
L表示左邊界,往往表示的是數(shù)組第一個(gè)元素,也就會(huì)賦值為0(角標(biāo)為0是數(shù)組的第一個(gè)元素)
R表示右邊界,往往表示的是數(shù)組的長(zhǎng)度,也就會(huì)賦值為arrays.length-1(長(zhǎng)度-1在角標(biāo)中才是代表最后一個(gè)元素)
那么可以看看我們遞歸的寫(xiě)法了:
public static void main(String[] args) { int[] arrays = {2, 3, 4, 5, 1, 5, 2, 9, 5, 6, 8, 3, 1}; System.out.println("公眾號(hào):Java3y:" + findMax(arrays, 0, arrays.length - 1)); } /** * 遞歸,找出數(shù)組最大的值 * @param arrays 數(shù)組 * @param L 左邊界,第一個(gè)數(shù) * @param R 右邊界,數(shù)組的長(zhǎng)度 * @return */ public static int findMax(int[] arrays, int L, int R) { //如果該數(shù)組只有一個(gè)數(shù),那么最大的就是該數(shù)組第一個(gè)值了 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; } } }三、冒泡排序遞歸寫(xiě)法
在冒泡排序章節(jié)中給出了C語(yǔ)言的遞歸實(shí)現(xiàn)冒泡排序,那么現(xiàn)在我們已經(jīng)使用遞歸的基本思路了,我們使用Java來(lái)重寫(xiě)一下看看:
冒泡排序:倆倆交換,在第一趟排序中能夠?qū)⒆畲笾蹬诺阶詈竺?,外層循環(huán)控制排序趟數(shù),內(nèi)層循環(huán)控制比較次數(shù)
以遞歸的思想來(lái)進(jìn)行改造:
當(dāng)?shù)谝惶伺判蚝?,我們可以將?shù)組最后一位(R)和數(shù)組前面的數(shù)(L,R-1)進(jìn)行切割,數(shù)組前面的數(shù)(L,R-1)看成是一個(gè)整體,這個(gè)整體又是和我們的初始目的(找出最大值,與當(dāng)前趟數(shù)的末尾處交換)是一樣的
遞歸出口:當(dāng)只有一個(gè)元素時(shí),即不用比較了: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("公眾號(hào):Java3y:" + arrays); } public static void bubbleSort(int[] arrays, int L, int R) { int temp; //如果只有一個(gè)元素了,那什么都不用干 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; } } //第一趟排序后已經(jīng)將最大值放到數(shù)組最后面了 //接下來(lái)是排序"整體"的數(shù)據(jù)了 bubbleSort(arrays, L, R - 1); } }四、斐波那契數(shù)列
接觸過(guò)C語(yǔ)言的同學(xué)很可能就知道什么是費(fèi)波納切數(shù)列了,因?yàn)橥鼍毩?xí)題的時(shí)候它就會(huì)出現(xiàn),它也是遞歸的典型應(yīng)用。
菲波那切數(shù)列長(zhǎng)這個(gè)樣子:{1 1 2 3 5 8 13 21 34 55..... n }
數(shù)學(xué)好的同學(xué)可能很容易就找到規(guī)律了:前兩項(xiàng)之和等于第三項(xiàng)
例如:
1 + 1 = 2 2 + 3 = 5 13 + 21 = 34
如果讓我們求出第n項(xiàng)是多少,那么我們就可以很簡(jiǎn)單寫(xiě)出對(duì)應(yīng)的遞歸表達(dá)式了:Z = (n-2) + (n-1)
遞歸出口在本題目是需要有兩個(gè)的,因?yàn)?strong>它是前兩項(xiàng)加起來(lái)才得出第三項(xiàng)的值
同樣地,那么我們的遞歸出口可以寫(xiě)成這樣:if(n==1) retrun 1 if(n==2) return 2
下面就來(lái)看一下完整的代碼吧:
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("公眾號(hào):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)); } }五、漢諾塔算法
圖片來(lái)源百度百科:
玩漢諾塔的規(guī)則很簡(jiǎn)單:
有三根柱子,原始裝滿大小不一的盤(pán)子的柱子我們稱(chēng)為A,還有兩根空的柱子,我們分別稱(chēng)為B和C(任選)
最終的目的就是將A柱子的盤(pán)子全部移到C柱子中
移動(dòng)的時(shí)候有個(gè)規(guī)則:一次只能移動(dòng)一個(gè)盤(pán)子,小的盤(pán)子不能在大的盤(pán)子上面(反過(guò)來(lái):大的盤(pán)子不能在小的盤(pán)子上面)
我們下面就來(lái)玩一下:
只有一個(gè)盤(pán)子:
將A柱子的盤(pán)子直接移動(dòng)到C柱子中
完成游戲
只有兩個(gè)盤(pán)子:
將A柱子上的小盤(pán)子移動(dòng)到B柱子中
將A柱子上的大盤(pán)子移動(dòng)到C柱子中
最后將在B柱子的小盤(pán)子移動(dòng)到C柱子大盤(pán)子中
完成游戲
只有三個(gè)盤(pán)子:
將A柱子小的盤(pán)子移動(dòng)到C柱子中
將A柱子上的中盤(pán)子移動(dòng)到B柱子中
將C柱子小盤(pán)子移動(dòng)到B柱子中盤(pán)子中
將A柱子的大盤(pán)子移動(dòng)到C柱子中
將B柱子的小盤(pán)子移動(dòng)到A柱子中
將B柱子的中盤(pán)子移動(dòng)到C柱子中
最后將A柱子的小盤(pán)子移動(dòng)到C柱子中
完成游戲
.......................
從前三次玩法中我們就可以發(fā)現(xiàn)的規(guī)律:
想要將最大的盤(pán)子移動(dòng)到C柱子,就必須將其余的盤(pán)子移到B柱子處(借助B柱子將最大盤(pán)子移動(dòng)到C柱子中[除了最大盤(pán)子,將所有盤(pán)子移動(dòng)到B柱子中])[遞歸表達(dá)式]
當(dāng)C柱子有了最大盤(pán)子時(shí),所有的盤(pán)子在B柱子。現(xiàn)在的目的就是借助A柱子將B柱子的盤(pán)子都放到C柱子中(和上面是一樣的,已經(jīng)發(fā)生遞歸了)
當(dāng)只有一個(gè)盤(pán)子時(shí),就可以直接移動(dòng)到C柱子了(遞歸出口)
A柱子稱(chēng)之為起始柱子,B柱子稱(chēng)之為中轉(zhuǎn)柱子,C柱子稱(chēng)之為目標(biāo)柱子
從上面的描述我們可以發(fā)現(xiàn),起始柱子、中轉(zhuǎn)柱子它們的角色是會(huì)變的(A柱子開(kāi)始是起始柱子,第二輪后就變成了中轉(zhuǎn)柱子了。B柱子開(kāi)始是目標(biāo)柱子,第二輪后就變成了起始柱子??傊?strong>起始柱子、中轉(zhuǎn)柱子的角色是不停切換的)
簡(jiǎn)單來(lái)說(shuō)就分成三步:
把 n-1 號(hào)盤(pán)子移動(dòng)到中轉(zhuǎn)柱子
把最大盤(pán)子從起點(diǎn)移到目標(biāo)柱子
把中轉(zhuǎn)柱子的n-1號(hào)盤(pán)子也移到目標(biāo)柱子
那么就可以寫(xiě)代碼測(cè)試一下了(回看上面玩的過(guò)程):
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("公眾號(hào):Java3y" ); } /** * 漢諾塔 * @param n n個(gè)盤(pán)子 * @param start 起始柱子 * @param transfer 中轉(zhuǎn)柱子 * @param target 目標(biāo)柱子 */ public static void hanoi(int n, char start, char transfer, char target) { //只有一個(gè)盤(pán)子,直接搬到目標(biāo)柱子 if (n == 1) { System.out.println(start + "---->" + target); } else { //起始柱子借助目標(biāo)柱子將盤(pán)子都移動(dòng)到中轉(zhuǎn)柱子中(除了最大的盤(pán)子) hanoi(n - 1, start, target, transfer); System.out.println(start + "---->" + target); //中轉(zhuǎn)柱子借助起始柱子將盤(pán)子都移動(dòng)到目標(biāo)柱子中 hanoi(n - 1, transfer, start, target); } }
我們來(lái)測(cè)試一下看寫(xiě)得對(duì)不對(duì):
參考資料:
https://www.zhihu.com/question/24385418
六、總結(jié)遞歸的確是一個(gè)比較難理解的東西,好幾次都把我繞進(jìn)去了....
要使用遞歸首先要知道兩件事:
遞歸出口(終止遞歸的條件)
遞歸表達(dá)式(規(guī)律)
在遞歸中常常用”整體“的思想,在漢諾塔例子中也不例外:將最大盤(pán)的盤(pán)子看成1,上面的盤(pán)子看成一個(gè)整體。那么我們?cè)谘菟愕臅r(shí)候就很清晰了:將”整體“搬到B柱子,將最大的盤(pán)子搬到C柱子,最后將B柱子的盤(pán)子搬到C柱子中
因?yàn)槲覀內(nèi)四X無(wú)法演算那么多的步驟,遞歸是用計(jì)算機(jī)來(lái)干的,只要我們找到了遞歸表達(dá)式和遞歸出口就要相信計(jì)算機(jī)能幫我們搞掂。
在編程語(yǔ)言中,遞歸的本質(zhì)是方法自己調(diào)用自己,只是參數(shù)不一樣罷了。
最后,我們來(lái)看一下如果是5個(gè)盤(pán)子,要運(yùn)多少次才能運(yùn)完:
PS:如果有更好的理解方法,或者我理解錯(cuò)的地方大家可以在評(píng)論下留言一起交流哦!
如果文章有錯(cuò)的地方歡迎指正,大家互相交流。習(xí)慣在微信看技術(shù)文章,想要獲取更多的Java資源的同學(xué),可以關(guān)注微信公眾號(hào):Java3y
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://hztianpu.com/yun/68863.html
摘要:簡(jiǎn)而言之,遞歸就是自己調(diào)用自己,但是這個(gè)調(diào)用它是有一定條件的,比如子問(wèn)題須與原始問(wèn)題為同樣的事,且更為簡(jiǎn)單。當(dāng)時(shí),這層遞歸返回,也就是返回到的這層遞歸。這時(shí),至此,遞歸結(jié)束,返回結(jié)果給調(diào)用方。 showImg(https://segmentfault.com/img/bVbvZRe?w=1242&h=735); 什么是遞歸? 維基百科給出了如下定義: 程序調(diào)用自身的編程技巧稱(chēng)為遞歸.遞...
摘要:快速排序的介紹來(lái)源百度百科快速排序由在年提出??焖倥判蚴敲嬖嚦霈F(xiàn)的可能性比較高的,也是經(jīng)常會(huì)用到的一種排序,應(yīng)該重點(diǎn)掌握。前面一個(gè)章節(jié)已經(jīng)講了遞歸了,那么現(xiàn)在來(lái)看快速排序就非常簡(jiǎn)單了。 快速排序就這么簡(jiǎn)單 從前面已經(jīng)講解了冒泡排序、選擇排序、插入排序了,本章主要講解的是快速排序,希望大家看完能夠理解并手寫(xiě)出快速排序的代碼,然后就通過(guò)面試了!如果我寫(xiě)得有錯(cuò)誤的地方也請(qǐng)大家在評(píng)論下指出。 ...
摘要:今天我們繼續(xù)使用的擼我們的實(shí)戰(zhàn)項(xiàng)目,只有在實(shí)戰(zhàn)中我們才會(huì)領(lǐng)悟更多,光紙上談兵然并卵,繼上篇我們的一個(gè)案例引發(fā)的動(dòng)態(tài)組件與全局事件綁定總結(jié)之后,今天來(lái)聊一聊我們?nèi)绾卧陧?xiàng)目中使用遞歸組件。 今天我們繼續(xù)使用 Vue 的擼我們的實(shí)戰(zhàn)項(xiàng)目,只有在實(shí)戰(zhàn)中我們才會(huì)領(lǐng)悟更多,光紙上談兵然并卵,繼上篇我們的《Vue一個(gè)案例引發(fā)的動(dòng)態(tài)組件與全局事件綁定總結(jié)》 之后,今天來(lái)聊一聊我們?nèi)绾卧陧?xiàng)目中使用遞歸組...
摘要:如果存在這么個(gè)函數(shù),那么我們就可以通過(guò)求解的不動(dòng)點(diǎn)來(lái)求出了。尋找轉(zhuǎn)換函數(shù)的不動(dòng)點(diǎn)找到了轉(zhuǎn)換函數(shù)后,下一步就是確定其不動(dòng)點(diǎn)了,而這個(gè)不動(dòng)點(diǎn)就是我們最終想要的。 本文原發(fā)于個(gè)人博客 遞歸 作為計(jì)算機(jī)科學(xué)中很重要的一個(gè)概念,應(yīng)用范圍非常廣泛。比較重要的數(shù)據(jù)結(jié)構(gòu),像樹(shù)、圖,本身就是遞歸定義的。比較常見(jiàn)的遞歸算法有階乘、斐波那契數(shù)等,它們都是在定義函數(shù)的同時(shí)又引用本身,對(duì)于初學(xué)者來(lái)說(shuō)也比較好理解...
摘要:不斷執(zhí)行這個(gè)操作代碼實(shí)現(xiàn)快速排序用遞歸比較好寫(xiě)如果不太熟悉遞歸的同學(xué)可到遞歸就這么簡(jiǎn)單。 前言 大概花了一周的時(shí)間把八大基礎(chǔ)排序過(guò)了一遍,這篇博文主要是用來(lái)回顧一下八大基礎(chǔ)排序的要點(diǎn)和一些總結(jié)~ 回顧: 冒泡排序就這么簡(jiǎn)單 選擇排序就這么簡(jiǎn)單 插入排序就這么簡(jiǎn)單 快速排序就這么簡(jiǎn)單 歸并排序就這么簡(jiǎn)單 堆排序就這么簡(jiǎn)單 希爾排序就這么簡(jiǎn)單 基數(shù)排序就這么簡(jiǎn)單 總的來(lái)說(shuō):快速排序是用...
閱讀 3623·2023-04-25 20:37
閱讀 3580·2021-09-07 09:59
閱讀 1804·2019-08-29 12:43
閱讀 1271·2019-08-28 18:27
閱讀 552·2019-08-26 13:50
閱讀 2277·2019-08-26 10:33
閱讀 3682·2019-08-23 18:39
閱讀 2485·2019-08-23 18:09