C/C++ 递归
遞歸
當(dāng)一個(gè)函數(shù)調(diào)用它自己來定義時(shí)稱它為遞歸函數(shù)。(什么叫它自己調(diào)用它自己呢?)
1.1、引出遞歸
從一個(gè)簡單的問題考慮遞歸,求0,1,2, 3,4,5......n的和。
首先定義一個(gè)求和公式:sum(n);
顯然對于(n > 0): sum(n) = sum(n - 1) + n ;
? (n = 0 ) : sum(0) = 0;
? 成立。
將上述公式翻譯成C++函數(shù):
unsigned int sum(unsigned int n)
{
if(0 == n)
{
return 0; //基準(zhǔn)情況(遞歸的出口),sum不能一直調(diào)用它自己吧,總歸要有一個(gè)出口結(jié)束遞歸吧
}
else
{
return sum(n - 1) + n; //sum(unsigned int)調(diào)用了它自己
}
}
假設(shè) n = 5 分析一下計(jì)算過程:
sum(5) = sum(4) + 5;
sum(4) = sum(3) + 4;
sum(3) = sum(2) + 3;
sum(2) = sum(1) + 2;
sum(1) = sum(0) + 1;
sum(0) = 0; 當(dāng)sum(0)時(shí),sum()不再調(diào)用它自己,作為遞歸的出口結(jié)束遞歸。
假設(shè)沒有n = 0, sum(0) = 0 這個(gè)基準(zhǔn)情況作為遞歸的出口跳出遞歸,遞歸就會(huì)一直遞歸下去,沒完沒了直至崩潰。因此遞歸函數(shù)必須有一個(gè)基準(zhǔn)情況作為遞歸出口。
1.2、失敗的遞歸
給出一個(gè)所謂的遞歸函數(shù):
int bad(unsigned int n)
{
if(0 == n)
{
return 0;
}
else
{
return bad(n/3 + 1) + n - 1;
}
}
分析一下以上函數(shù),函數(shù)給出了 n = 0 的情況作為遞歸的出口,看似沒什么問題。
還是假設(shè)n = 5;
bad(5) : 調(diào)用bad(5/3 + 1), 即bad(2);
bad(2) : 調(diào)用bad(2/3 + 1), 即bad(1);
bad(1) : 調(diào)用bad(1/3 + 1), 即bad(1);
bad(1) : 調(diào)用bad(1/3 + 1), 即bad(1)..........
bad(1)一直調(diào)用bad(1), 一直調(diào)用到程序崩潰。很明顯bad()函數(shù)定義雖然給出了 n = 0 作為遞歸出口,但是bad()函數(shù)根本不會(huì)推進(jìn)到n = 0 的這種情況。因此遞歸調(diào)用必須總能夠朝著產(chǎn)生基準(zhǔn)情況(遞歸出口)的方向推進(jìn)。
1.3、遞歸和歸納
考慮一個(gè)問題:現(xiàn)在需要將一個(gè)正整數(shù) n 打印出來,但是I/O給出的函數(shù)接口(printDigit)只能處理單個(gè)數(shù)字(即n < 10)。
我們隨便假設(shè)一個(gè)n值:n = 2019,那么單個(gè)數(shù)字打印的順序就是2, 0, 1, 9。換句話說,9是最后一個(gè)打印的,在打印9之前要先打印201,即先打印“201”,再打印“9”;依次類推對于“201”先打印“20”,再打印“1”;對于“20”先打印“2”,再打印“0”;對于2已經(jīng)是單個(gè)數(shù)字,可以直接打印了, 不需要再劃分,再遞歸了,也就是說單個(gè)數(shù)字n < 10即為遞歸的出口。
我們按上述思路細(xì)致的分析一下:
對2019分成2部分: 201 = 2019 / 10; 9 = 2019 % 10;
對201分成2部分:20 = 201 / 10; 1 = 201 % 10;
對20分成2部分:2 = 20 / 10; 0 = 20 % 10;
對于 2 滿足 n < 10 的條件,不再遞歸,直接打印。
現(xiàn)在遞歸已經(jīng)很明顯了,嘗試編寫一下代碼:
//假設(shè)printDigit((unsigned int n)如下,
void printDigit(unsigned int n)
{
std::cout << n;
}
void print(unsigned int n)
{
if(n >= 10)
{
print(n / 10);
}
printDigit(n % 10);
}
代碼編寫好了,現(xiàn)在需要證明以下代碼是否正確:對于n >= 0,數(shù)的遞歸打印算法總是正確的。
證明:用k表示數(shù)字n的包含單個(gè)數(shù)字的個(gè)數(shù)。當(dāng)k = 1,即 n < 10 時(shí),很明顯程序是正確的,因?yàn)樗恍枰f歸,print()只調(diào)用一次printDigit(), 不調(diào)用它自己。然后假設(shè)print()對于所有k位數(shù)都能正常工作,任何k + 1位的數(shù)字n都可以通過它的前k位的數(shù)字和最低1位數(shù)字來表示。前k 位的數(shù)字恰好是[ n / 10], 歸納假設(shè)它能正常工作,而最低1位數(shù)字是[ n % 10],因此該程序能夠正確的打印出任意k + 1位。于是根據(jù)歸納法[1],所有數(shù)字都能被正確打印出來。
由以上實(shí)例總結(jié)可以出一條遞歸的設(shè)計(jì)法則:假設(shè)所有遞歸調(diào)用都能運(yùn)行。
1.4、遞歸的合成效益法則
用遞歸實(shí)現(xiàn)一個(gè)斐波那契數(shù)列:
//斐波納契數(shù)列:1、1、2、3、5、8、13、21、34
int f(int n)
{
if(n < 1)
{
return 0;
}
else if(n <= 2)
{
return 1;
}
return f(n-1) + f(n-2);
}
假設(shè)n = 8, 函數(shù)調(diào)用f(8), 遞歸調(diào)用如下圖:
8-->7;
7-->6;
6-->5;
5-->4;
4-->3;
3-->2;
8-->id0(6);
id0(6)-->id1(5);
id1(5)-->id2(4);
id2(4)-->id3(3);
id3(3)-->id4(2);
7-->id5(5);
id5(5)-->id6(4);
id6(4)-->id7(3);
id7(3)-->id8(2);
6-->id9(4);
id9(4)-->id10(3);
id10(3)-->id11(2);
5-->id12(3);
id12(3)-->id13(2);
4-->id14(2);
3-->id15(1);
id12(3)-->id16(1);
id9(4)-->id17(2);
id10(3)-->id18(1);
id5(5)-->id19(3);
id19(3)-->id20(2);
id19(3)-->id21(1);
id6(4)-->id22(2);
id7(3)-->id23(1);
id0(6)-->id24(4);
id24(4)-->id25(3);
id24(4)-->id28(2);
id25(3)-->id26(2);
id25(3)-->id27(1);
id1(5)-->id29(3);
id29(3)-->id30(2);
id29(3)-->id31(1);
id2(4)-->id32(2);
id3(3)-->id33(1);
由上圖我們不厭其煩的數(shù)一下:
n = 1時(shí),f()調(diào)用1次;
n = 2時(shí),f()調(diào)用1次;
n = 3時(shí),f()調(diào)用3次;
n = 4時(shí),f()調(diào)用5次;
n = 5時(shí),f()調(diào)用9次;
n = 6時(shí),f()調(diào)用15次;
n = 7時(shí),f()調(diào)用25次;
n = 8時(shí),f()調(diào)用41次;
增長的是不是太快了,在f()里加一個(gè)計(jì)數(shù)器測試一下,可以看到在n = 30 的時(shí)候,f()的調(diào)用次數(shù)大約在160萬。
究其原因,是因?yàn)槲覀冊谇蠼獾倪^程時(shí),重復(fù)了大量的計(jì)算過程, 在n = 8 的時(shí)候單單是f(3)就重復(fù)調(diào)用了8次。
由上我們可以得出一個(gè)結(jié)論:在求解一個(gè)問題的同一實(shí)例時(shí),在不同的遞歸中做重復(fù)性的工作,對資源的消耗可能是災(zāi)難性的。
最后歸納一下要牢記的遞歸四條基本法則:
- 基準(zhǔn)情形。必須總有某些基準(zhǔn)情況,它無須遞歸就能求解,即遞歸必須有出口。
- 不斷推進(jìn)。對于那些需要遞歸求解的情形,每一次遞歸調(diào)用都必須要使求解狀態(tài)朝基準(zhǔn)情形的方向推進(jìn)。
- 設(shè)計(jì)法則。假設(shè)所有的遞歸調(diào)用都能運(yùn)行。
- 合成效益法則。在求解一個(gè)問題的同一實(shí)例時(shí),切勿在不同的遞歸中做重復(fù)性的工作。
1、證明當(dāng)n= 1時(shí)命題成立。2、假設(shè)n=m時(shí)命題成立,那么可以推導(dǎo)出在n=m+1時(shí)命題也成立。(m代表任意自然數(shù))。3、歸納結(jié)論。 ??
總結(jié)
- 上一篇: AJAX的写法
- 下一篇: linux 安装简洁的 zsh