《計算思維與C語言概述》-第七章_第1頁
《計算思維與C語言概述》-第七章_第2頁
《計算思維與C語言概述》-第七章_第3頁
《計算思維與C語言概述》-第七章_第4頁
《計算思維與C語言概述》-第七章_第5頁
已閱讀5頁,還剩47頁未讀 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

7.1遞歸

遞歸(recursion)是計算科學領域的一種重要的思維模式,是一種問題求解的方法。例如,數(shù)學公式n!=1×2×···×n的計算可以取n-1次乘法的結果,也可以采用另一種思維方法———n!=n(n-1)!。也就是說,給定n,則n!可由(n-1)!求出,(n-1)!可由(n-2)!求出,···,2!可由1!求出。而1!=1已知,那么2!可求得,

從而3!可求得,

依次類推,

n!可求得。

這種思維方式便是一種遞歸。

遞歸思維將復雜的計算簡化為簡單計算的不斷重復,是最重要的計算思維。在C/C++語言中,遞歸的實現(xiàn)是采用函數(shù)的自我調用來實現(xiàn)。函數(shù)直接

(或間接)調用自己就稱為函數(shù)的遞歸調用,這種函數(shù)就稱為遞歸函數(shù)。遞歸函數(shù)將反復調用其自身,每調用一次就進入新的一層,當最內層的函數(shù)執(zhí)行完畢后,

就一層一層地由里到外返回。

調用自身的過程又稱為“遞推”,由里到外返回的過程又稱為“回歸”,所以遞歸過程可以分為“遞推”和“回歸”兩個階段。下一頁返回7.1遞歸

本節(jié)以一個簡單示例為基點,闡述應用遞歸的程序設計過程?!纠罚薄坑眠f歸函數(shù)編寫計算階乘n!的函數(shù)factorial?!痉治觥侩A乘n!的計算公式如下:根據(jù)上面階乘的計算公式可以發(fā)現(xiàn),階乘的計算公式是一個遞歸計算公式。為了計算n!,

需要調用計算階乘的函數(shù)factorial(n),

它因要計算(n-1)!,

而調用factorial(n-1),于是形成遞歸調用。

這個過程一直持續(xù)到1!為止。

在求得1!=1以后,

逐層返回,

最后求得n!。上一頁下一頁返回7.1遞歸

算法的偽代碼如下:(1)輸入一個自然數(shù),

存入變量n;(2)遞歸調用函數(shù)factorial;(3)輸出n!的值;上一頁下一頁返回7.1遞歸

7.1.1

遞歸的思想遞歸是程序設計中的一種常見方法。采用遞歸方法編寫程序可以使程序更加簡潔、清晰、

容易理解。

階乘問題是一個經(jīng)典的數(shù)學遞歸問題。

為了得到n!,

操作步驟如下(注意參數(shù)的變化):(1)第1次調用函數(shù)factorial(n)。

根據(jù)公式可知:n!=n×(n-1)!。所以,

需要知道(n-1)!的數(shù)值,

那么就需要第2次調用函數(shù)factorial。(2)第2次調用函數(shù)factorial(n-1)。

根據(jù)公式可知:(n-1)!=(n-1)×(n-2)!。所以,需要知道(n-2)!的數(shù)值,

那么就需要第3次調用函數(shù)factorial。(3)第3次調用函數(shù)factorial(n-2)。

根據(jù)公式可知:(n-2)!=(n-2)×(n-3)!。所以,需要知道(n-3)!的數(shù)值,

那么就需要第4次調用函數(shù)factorial。上一頁下一頁返回7.1遞歸

直到調用函數(shù)factorial(2),

都可采用同樣的方法進行處理,

2!=2×1!。

根據(jù)公式可知:1!=1。

因此,

問題最終可以求解??梢钥闯觯看蚊鎸Φ膯栴}在本質上是同一個問題,只是問題的規(guī)模不同。上面的解決方法體現(xiàn)了遞歸的思想,將復雜問題分解為規(guī)模較小的子問題,

且子問題和原問題本質上是相同的問題。在將子問題求解后,原問題也將求解。接下來,

以求解5!為例,

說明遞歸的遞推過程和回歸過程。上一頁下一頁返回7.1遞歸

7.1.2

遞歸的遞推求解5!的遞推過程如下:(1)求解5!,

即調用factorial(5)。

當進入factorial()函數(shù)體后,

由于形參n的值為5,不等于1,

所以執(zhí)行factorial(n-1)?n,

即執(zhí)行factorial(4)?5。

為了求得這個表達式的結果,

必須先調用factorial(4),

并暫停其他操作。

換言之,

在得到factorial(4)的結果之前,不能進行其他操作。(2)調用factorial(4)時,

實參為4,

形參n

也為4,

不等于1,

因此將繼續(xù)執(zhí)行factorial(n-1)?n,

即執(zhí)行factorial(3)?4。

為了求得這個表達式的結果,

必須先調用facto-rial(3)。上一頁下一頁返回7.1遞歸

(3)調用factorial(3)時,

實參為3,

形參n

也為3,

不等于1,

因此將繼續(xù)執(zhí)行factorial(n-1)?n,

也即執(zhí)行factorial(2)?3。

為了求得這個表達式的結果,

又必須先調用factorial(2)。(4)調用factorial(2)時,

實參為2,

形參n也為2,

不等于1,

因此將繼續(xù)執(zhí)行factorial(n-1)?n,

即執(zhí)行factorial(1)?2。

為了求得這個表達式的結果,

必須先調用facto-rial(1)。(5)在進行4次調用后,

實參的值為1,

因此將調用factorial(1)。

此時,

能夠直接得到常量為1的值,

并把結果返回,

不需要再次調用factorial()函數(shù),

遞歸結束。表7-1列出了在遞歸調用過程中逐層進入的遞推過程。上一頁下一頁返回7.1遞歸

7.1.3遞歸的回歸當遞歸進入最內層時,就結束遞歸,開始逐層退出,即逐層執(zhí)行return語句,這就是遞歸的回歸過程。

求解5!的回歸過程如下:(1)當n的值為1時,

達到最內層,

此時return的結果為1,

即factorial(1)的調用結果為1。(2)有了factorial(1)的結果,

就可以返回上一層,

計算factorial(1)?2的值。

此時,得到的值為2,

return的結果也為2,

即factorial(2)的調用結果為2。(3)有了factorial(2)的結果,

就可以返回上一層,

計算factorial(2)?3的值。

此時,得到的值為6,

return的結果也為6,

即factorial(3)的調用結果為6。上一頁下一頁返回7.1遞歸

(4)有了factorial(3)的結果,

就可以返回上一層,

計算factorial(3)?4的值。

此時,得到的值為24,

return的結果也為24,

即factorial(4)的調用結果為24。(5)有了factorial(4)的結果,

就可以返回上一層,

計算factorial(4)?5的值。

此時,得到的值為120,

return的結果也為120,

即factorial(5)的調用結果為120。

這樣就得到了5!的值。表7-2列出了在遞歸調用過程中逐層退出的回歸過程。上一頁下一頁返回7.1遞歸

7.1.4遞歸的條件每一個遞歸函數(shù)都應該只進行有限次遞歸調用,否則就會進入死胡同,

永遠也不能退出,這樣的程序是沒有意義的。要想讓遞歸函數(shù)逐層進入再逐層退出,需要解決以下兩方面的問題:(1)存在限制條件,

當符合該條件時,

遞歸便不再繼續(xù)。

對于函數(shù)factorial,

當形參n等于1時,遞歸就結束。這種限制條件就是遞歸結束條件,稱為遞歸出口。(2)每次遞歸調用后,

越來越接近該限制條件。

對于函數(shù)factorial,

每次遞歸調用的實參為n-1,這會使形參n的值逐漸減小,越來越趨近于1。上一頁下一頁返回7.1遞歸

7.1.5

遞歸的實現(xiàn)經(jīng)過上面遞歸調用的分析,例7-1的程序代碼如下:上一頁下一頁返回7.1遞歸

上一頁下一頁返回7.1遞歸

【練習】(1)用遞歸方法求1~100的和,

即1+2+3+4+···+100。提示:遞歸求和公式為(2)用遞歸方法求1~100的連乘積,

即1×2×3×4×···×100。提示:遞歸求連乘積公式為(3)從1,2,3,···,n中取出m個數(shù),一共有多少種選擇方案?提示:計算從n個數(shù)中取m個數(shù)的組合數(shù)的公式為上一頁返回7.2迭代與遞歸

在計算機編程實現(xiàn)中,

常常有兩種編程思想:迭代(iterate);

遞歸(recursion)。

從概念上講,遞歸是指程序調用自身的編程思想,即一個函數(shù)調用本身;迭代是利用已知的變量值,根據(jù)遞推公式不斷演進得到變量新值的編程思想。簡而言之,遞歸將大問題化為相同結構的小問題,從待求解的問題出發(fā),一直分解到已知答案的最小問題為止,然后逐級返回,從而得到大問題的解;而迭代則從已知值出發(fā),通過遞推式,不斷更新變量新值,直到能夠解決問題為止。下面以斐波那契數(shù)列的求解為例,介紹這兩種典型編程思想的實現(xiàn),并分析二者的區(qū)別與聯(lián)系。下一頁返回7.2迭代與遞歸

【例7-2】斐波那契數(shù)列(又稱為黃金分割數(shù)列,

通常記做Fibonacci)指的是這樣一個數(shù)列:1,1,2,3,5,8,13,21,···這個數(shù)列從第3項開始,每一項都等于前兩項之和。該數(shù)列剛好和一個有趣的兔子問題相關。一般而言,兔子在出生兩個月后,就有繁殖能力,一對兔子每個月能生出一對小兔子來。如果所有兔子都不死,那么若干個月以后的兔子對數(shù)為:1,1,2,3,5,8,13,31,···【分析】如果在數(shù)列的前面加上數(shù)字“0”,那么從第2項開始,每一項都是該項前兩項的數(shù)字之和。

數(shù)列的通項表達式F(n)如下:上一頁下一頁返回7.2迭代與遞歸

7.2.1

遞歸實現(xiàn)可以采用遞歸的編程方式求解例7-2的問題,方法是定義一個函數(shù)Fib,返回數(shù)列第n項的值。將規(guī)模為n的問題分解為更小規(guī)模的問題,即規(guī)模為n-1的問題與規(guī)模為n-2的問題,

一直分解到規(guī)模為0和規(guī)模為1的問題。

數(shù)列的通項表達式Fib(n)如下所示:例7-2遞歸函數(shù)的偽代碼如下:(1)如果函數(shù)形參是0,

則函數(shù)返回0,

并回歸上一層;(2)如果函數(shù)形參是1,

則函數(shù)返回1,

并回歸上一層;(3)如果函數(shù)形參大于1,

則繼續(xù)遞歸調用函數(shù)。上一頁下一頁返回7.2迭代與遞歸

將例7-2采用遞歸求解的函數(shù)Fib的代碼如下:上一頁下一頁返回7.2迭代與遞歸

7.2.2

迭代實現(xiàn)例7-2也可以采用迭代的方式求解。同樣,需要定義一個函數(shù)Fib,但不將問題分解,而是直接利用已知的變量值,根據(jù)遞推公式不斷演進。由例7-2的遞推公式可知,已知兩個初值(即數(shù)列第0項的值和數(shù)列第1項的值),那么根據(jù)遞推公式,就可以得到第2項的值,,得到第n項的值。算法的偽代碼如下:(1)定義兩個變量f0和f1,

初值分別為0和1;(2)如果函數(shù)形參n是0,

則函數(shù)返回0;(3)如果函數(shù)形參n是1,

則函數(shù)返回1;(4)循環(huán)變量i初始化為2,

當循環(huán)變量i≤n時,(4.1)計算數(shù)列下一項的值,

f=f0+f1;(4.2)更新變量,

f0=f1,

f1=f;(43)i++;(5)函數(shù)返回f的值。上一頁下一頁返回7.2迭代與遞歸

將例7-2采用迭代求解的函數(shù)Fib代碼如下:上一頁下一頁返回7.2迭代與遞歸

上一頁下一頁返回7.2迭代與遞歸

【練習】(1)改寫上述迭代算法,

將一維數(shù)組作為Fib函數(shù)的形參,

把前n項斐波那契數(shù)列存入數(shù)組,并編寫main函數(shù),輸出斐波那契數(shù)列前100項的數(shù)值,輸出方式為每行10項。(2)將715節(jié)練習1和練習2的遞歸程序改寫為迭代程序。(3)求x平方根近似值的迭代公式為xn

1=(xn+x/xn

)/2。

當n為1時,

x1為x,

迭代一次求得的平方根近似值為x2;n為2時,求得的近似值為x3,依次類推。輸入正整數(shù)x和整數(shù)n(n≥0,

且x和n都不會出現(xiàn)溢出情況)。

求利用上述公式,

求x迭代n次后的平方根近似值,保留計算結果小數(shù)點后5位有效數(shù)字。例如,輸入16和4,

則輸出400226。上一頁下一頁返回7.2迭代與遞歸

7.2.3

遞歸與迭代的關系遞歸,實際上就是不斷地深層調用函數(shù),直到函數(shù)有返回值才會逐層返回。因此,

遞歸涉及運行時的堆棧開銷(參數(shù)必須壓入堆棧保存,直到該層函數(shù)調用返回為止),有可能導致堆棧溢出的錯誤。但是,遞歸編程所體現(xiàn)的思想正是人們追求簡潔、將問題交給計算機,以及將大問題分解為相同小問題從而解決大問題的動機。迭代在大部分時候需要人為地對問題進行剖析,

將問題轉變?yōu)橐淮未蔚倪f推來逼近答案。迭代不像遞歸一樣對堆棧有一定要求,一旦問題剖析完畢,就可以很容易地通過循環(huán)加以實現(xiàn)。在理論上,遞歸和迭代在時間復雜度方面是等價的(暫不考慮函數(shù)調用開銷和函數(shù)調用產(chǎn)生的堆棧開銷),但實際上,遞歸的效率比迭代低。既然遞歸沒有任何優(yōu)勢,

那么是不是就沒有使用遞歸的必要了?遞歸的存在有何意義呢?上一頁下一頁返回7.2迭代與遞歸

從理論上說,所有的遞歸函數(shù)都可以轉換為迭代函數(shù),反之亦然,然而代價通常都比較高。但是,就算法結構而言,遞歸聲明的結構并不總能轉換為迭代結構;就實際而言,所有的迭代都可以轉換為遞歸,但遞歸不一定可以轉換為迭代。迭代雖然效率高,運行時間只因循環(huán)次數(shù)增加而增加,沒什么額外開銷,空間上也沒有什么增加,但其代碼不易理解,

在編寫復雜問題時,算法實現(xiàn)較為困難。上一頁返回7.3實訓與實訓指導

實訓1

走臺階有個人剛剛看完電影?第39級臺階?,離開電影院時,他數(shù)了數(shù)禮堂前的臺階數(shù),恰好是39級!站在臺階前,他想到一個問題:如果我每一步只能邁上1個或2個臺階,先邁左腳,然后左右交替,最后一步邁右腳,也就是說一共要走偶數(shù)步。那么,上完39級臺階,有多少種不同的上法呢?1實訓分析為了統(tǒng)計走完39級臺階的方法,設結果變量result表示上臺階的方法數(shù),初始值是0。由于每走到一級臺階,都需要統(tǒng)計步數(shù),所以設變量num表示臺階,step表示走到num級臺階的步數(shù)。題目中說明每一步只能邁上1個或2個臺階,所以第step+1步有可能到達num+1級臺階,也有可能到達num+2級臺階。第step+2步也是同樣的情況,依次類推。這里也就是程序的遞歸。那么當num=39時,表示此時走完39級臺階。下一頁返回7.3實訓與實訓指導

由于題目中要求先邁左腳,然后左右交替,最后一步邁右腳,也就是說一共要走偶數(shù)步,所以當num=39時,還需要判斷當前的步數(shù)是否為偶數(shù)步,即“step%2==0”是否成立。如果滿足偶數(shù)步,則表示一種方案成立,即result++;如果不滿足偶數(shù)步,表示這種走法不符合要求,舍棄。算法的偽代碼如下:(1)設置遞歸的初始條件,

第num=0級臺階,

共走step=0步;(2)處理遞歸的下一步,

對于step+1級臺階,

num-2或者num-1,

判斷遞歸是否結束;(3)輸出結果。上一頁下一頁返回7.3實訓與實訓指導

程序的代碼如下:上一頁下一頁返回7.3實訓與實訓指導

上一頁下一頁返回7.3實訓與實訓指導

上一頁下一頁返回7.3實訓與實訓指導

2實訓練習編寫程序遞歸實現(xiàn)某個自然數(shù)n的k次冪。提示:遞歸公式為上一頁下一頁返回7.3實訓與實訓指導

實訓2

換汽水已知1元可以買1瓶汽水,3個瓶蓋可以換1瓶汽水,2個空瓶也可以換1瓶汽水。那么,n元總共可以買到多少瓶汽水?1實訓分析不妨設變量money、water分別表示錢的金額數(shù)和當前的汽水數(shù)量。題目中說明1元可以買1瓶汽水,所以汽水的最初數(shù)量等于錢的金額大小,即water=money。另外,設變量cap、bottle分別表示當前的瓶蓋數(shù)量和空瓶數(shù)量。由題目可知,3個瓶蓋可以換1瓶汽水,2個空瓶也可以換1瓶汽水,所以此時瓶蓋和空瓶可以兌換的汽水數(shù)量分別為cap/3、bottle/2。因此,汽水的數(shù)量由兩部分組成,即之前的汽水數(shù)量和兌換的汽水數(shù)量。上一頁下一頁返回7.3實訓與實訓指導

兌換汽水后,

瓶蓋和空瓶的數(shù)量分別是cap%3、bottle%2,可以用來兌換的汽水數(shù)量是cap/3+bottle/2。如此便可以進行下一次兌換,這就是該題的遞歸部分。直到可兌換的汽水數(shù)量等于0時,遞歸結束。算法的偽代碼如下:(1)根據(jù)1元買1瓶汽水的原則,

可知初始汽水的數(shù)量water=money;(2)初始化遞歸的條件,

可以用來兌換的汽水數(shù)量:water=money,

cap=0,

bottle=0;(3)兌換后,

更新cap、

bottle的數(shù)值,

并判斷是否有下一次兌換的可能性。

如果還可以兌換,則繼續(xù)遞歸;否則,退出遞歸。上一頁下一頁返回7.3實訓與實訓指導

程序的代碼如下:上一頁下一頁返回7.3實訓與實訓指導

上一頁下一頁返回7.3實訓與實訓指導

2實訓練習編寫程序,利用遞歸公式求函數(shù)值:上一頁下一頁返回7.3實訓與實訓指導

實訓3

排列數(shù)從n個不同元素中任?。?m≤n)個元素,

按照一定的順序排列起來。

當m=n時,

所有的排列情況稱為全排列。例如,1、2、3三個元素的全排列如下:1,2,31,3,22,1,32,3,13,1,23,2,1上一頁下一頁返回7.3實訓與實訓指導

1實訓分析常用的全排列方法有遞歸算法和非遞歸算法。其中,遞歸算法有四種:字典序法、遞增進位制數(shù)法、遞減進位制數(shù)法和鄰位對換法。字典序法的排列規(guī)則:對給定的字符集中的字符規(guī)定一個先后關系,在此基礎上規(guī)定兩個全排列的先后是從左到右逐個比較對應的字符的先后。

例如,

對于字符集{1,2,3},

要求將較小的數(shù)字排序較先,

這樣按字典序法生成的全排列是:123,132,213,231,312,321。

遞歸算法由函數(shù)Perm(intlist[],intk,intm)實現(xiàn),將list的前k個元素作為前綴、剩下的m-k個元素進行全排列而得到排列。其想法是將第k個元素與后面的每個元素進行交換,求出其全排列。上一頁下一頁返回7.3實訓與實訓指導

算法的偽代碼如下:(1)讀入?yún)⑴c全排列的數(shù)據(jù)個數(shù)以及具體的數(shù)值;(2)從左向右逐個與后面的元素進行交換;(3)遞歸上述過程,

直到得到n個數(shù),

得到一個全排列;(4)為了下一個全排列,

需要把剛才發(fā)生交換的兩個數(shù)值恢復原狀態(tài)。

然后,

繼續(xù)2和3,尋找下一種全排列。上一頁下一頁返回7.3實訓與實訓指導

程序的代碼如下:上一頁下一頁返回7.3實訓與實訓指導

上一頁下一頁返回7.3實訓與實訓指導

上一頁下一頁返回7.3實訓與實訓指導

上一頁下一頁返回7.3實訓與實訓指導

2實訓練習A~I代表1~9的數(shù)字,不同的字母代表不同的數(shù)字。字母A~I組成了下面的算式:某些數(shù)字的排列剛好使該式成立。例如,6+8/3+952/714就是一種解法,5+3/1+972/486是另一種解法。這個算式一共有多少種解法?提示:考慮1~9的每種全排列能否使該式成立即可。上一頁下一頁返回7.3實訓與實訓指導

實訓4

漢諾塔漢諾塔(HanoiTower)又稱河內塔,

已知有三根柱子,

在一根柱子上從下往上按照大小順序摞著64個圓盤。現(xiàn)在需要把圓盤按大小順序重新擺放在另一根柱子上。規(guī)定:任何時候,在小圓盤上都不能擺放大圓盤,且在三根柱子之間一次只能移動一個圓盤。如果將三根柱子分別命名為A、B、C,A柱子上有n片黃金圓盤,應該如何操作才能實現(xiàn)該任務?編寫程序輸出圓盤移動的操作步驟。要求從鍵盤接收圓盤的數(shù)量。上一頁下一頁返回7.3實訓與實訓指導

1實訓分析對于這樣一個問題,我們很難直接給出圓盤移動的順序。但是,可以先觀察圓盤數(shù)量較少時的規(guī)律,進而解決這個問題。假設A柱子上的n個圓盤從上至下的編號依次為1、2、···、n,規(guī)定將圓盤從A柱子直接移動到C柱子上的過程記為A→C,則:(1)當n=1時,第1次移動:1號圓盤A→C。(2)當n=2時,第1次移動:1號圓盤A→B;第2次移動:2號圓盤A→C;第3次移動:1號圓盤B→C??梢钥闯?,第1次移動將2號圓盤上的1號圓盤從A柱子移到B柱子上。第2次移動將2號圓盤從A柱子移到C柱子上。第3次移動除將2號圓盤外的1號圓盤從B柱子移動到C柱子上。上一

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論