程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> 對C說話中遞歸算法的深刻解析

對C說話中遞歸算法的深刻解析

編輯:關於C++

對C說話中遞歸算法的深刻解析。本站提示廣大學習愛好者:(對C說話中遞歸算法的深刻解析)文章只能為提供參考,不一定能成為您想要的結果。以下是對C說話中遞歸算法的深刻解析正文


很多教科書都把盤算機階乘和菲波那契數列用來講明遞歸,異常不幸我們心愛的有名的老潭先生的《C說話法式設計》一書中就是從階乘的盤算開端的函數遞歸。招致讀過這本經籍的同窗們,看到階乘盤算第一個設法主意就是遞歸。然則在階乘的盤算裡,遞合並沒有供給任何優勝的地方。在菲波那契數列中,它的效力更是低的異常恐懼。

這裡有一個簡略的法式,可用於解釋遞歸。法式的目標是把一個整數從二進制情勢轉換為可打印的字符情勢。例如:給出一個值4267,我們須要順次發生字符‘4',‘2',‘6',和‘7'。就如在printf函數中應用了%d格局碼,它就會履行相似處置。

我們采取的戰略是把這個值重復除以10,並打印各個余數。例如,4267除10的余數是7,然則我們不克不及直接打印這個余數。我們須要打印的是機械字符集中表現數字‘7'的值。在ASCII碼中,字符‘7'的值是55,所以我們須要在余數上加上48來取得准確的字符,然則,應用字符常量而不是整型常量可以進步法式的可移植性。‘0'的ASCII碼是48,所以我們用余數加上‘0',所以有上面的關系:

          ‘0'+ 0 =‘0'
          ‘0'+ 1 =‘1'
          ‘0'+ 2 =‘2'
             ...
從這些關系中,我們很輕易看出在余數上加上‘0'便可以發生對應字符的代碼。接著就打印出余數。下一步再取商的值,4267/10等於426。然後用這個值反復上述步調。

這類處置辦法存在的獨一成績是它發生的數字順序正好相反,它們是逆向打印的。所以在我們的法式中應用遞歸來修改這個成績。

我們這個法式中的函數是遞歸性質的,由於它包括了一個對本身的挪用。乍一看,函數仿佛永久不會終止。當函數挪用時,它將挪用本身,第2次挪用還將挪用本身,以此類推,仿佛永久挪用下去。這也是我們在剛接觸遞歸時最想不明確的工作。然則,現實上其實不會湧現這類情形。

這個法式的遞歸完成了某品種型的螺旋狀while輪回。while輪回在輪回體每次履行時必需獲得某種停頓,慢慢逼近輪回終止前提。遞歸函數也是如斯,它在每次遞歸挪用後必需愈來愈接近某種限制前提。當遞歸函數相符這個限制前提時,它便不在挪用本身。

在法式中,遞歸函數的限制前提就是變量quotient為零。在每次遞歸挪用之前,我們都把quotient除以10,所以每遞歸挪用一次,它的值就愈來愈接近零。當它終究釀成零時,遞歸便了結止。

/*接收一個整型值(無符號0,把它轉換為字符並打印它,前導零被刪除*/


#include <stdio.h>

int binary_to_ascii( unsigned int value)
{
          unsigned int quotient;

     quotient = value / 10;
     if( quotient != 0)
           binary_to_ascii( quotient);
     putchar ( value % 10 + '0' );
}

遞歸是若何贊助我們以准確的次序打印這些字符呢?上面是這個函數的任務流程。
1. 將參數值除以10
2. 假如quotient的值為非零,挪用binary-to-ascii打印quotient以後值的列位數字
3. 接著,打印步調1中除法運算的余數

留意在第2個步調中,我們須要打印的是quotient以後值的列位數字。我們所面對的成績和最後的成績完整雷同,只是變量quotient的值變小了。我們用方才編寫的函數(把整數轉換為各個數字字符並打印出來)來處理這個成績。因為quotient的值愈來愈小,所以遞歸終究會終止。

一旦你懂得了遞歸,浏覽遞歸函數最輕易的辦法不是糾纏於它的履行進程,而是信任遞歸函數會順遂完成它的義務。假如你的每一個步調准確無誤,你的限制前提設置准確,而且每次挪用以後更接近限制前提,遞歸函數老是能准確的完成義務。

然則,為了懂得遞歸的任務道理,你須要追蹤遞歸挪用的履行進程,所以讓我們來停止這項任務。追蹤一個遞歸函數的履行進程的症結是懂得函數中所聲明的變量是若何存儲的。當函數被挪用時,它的變量的空間是創立於運轉時客棧上的。之前挪用的函數的變量扔保存在客棧上,但他們被新函數的變量所掩飾,是以是不克不及被拜訪的。

當遞歸函數挪用本身時,情形因而如斯。每停止一次新的挪用,都將創立一批變量,他們將掩飾遞歸函數前一次挪用所創立的變量。當我追蹤一個遞歸函數的履行進程時,必需把分數分歧次挪用的變量辨別開來,以免混雜。

法式中的函數有兩個變量:參數value和部分變量quotient。上面的一些圖顯示了客棧的狀況,以後可以拜訪的變量位於棧頂。一切其他挪用的變量飾以灰色的暗影,表現他們不克不及被以後正在履行的函數拜訪。

假定我們以4267這個值挪用遞歸函數。當函數剛開端履行時,客棧的內容以下圖所示:


履行除法以後,客棧的內容以下:


   接著,if語句斷定出quotient的值非零,所以對該函數履行遞歸挪用。當這個函數第二次被挪用之初,客棧的內容以下:   客棧上創立了一批新的變量,隱蔽了後面的那批變量,除非以後此次遞歸挪用前往,不然他們是不克不及被拜訪的。再次履行除法運算以後,客棧的內容以下:

quotient的值如今為42,依然非零,所以須要持續履行遞歸挪用,並再創立一批變量。在履行完此次挪用的動身運算以後,客棧的內容以下:   此時,quotient的值照樣非零,依然須要履行遞歸挪用。在履行除法運算以後,客棧的內容以下:


不算遞歸挪用語句自己,到今朝為止所履行的語句只是除法運算和對quotient的值停止測試。因為遞歸挪用這些語句反復履行,所以它的後果相似輪回:當quotient的值非零時,把它的值作為初始值從新開端輪回。然則,遞歸挪用將會保留一些信息(這點與輪回分歧),也就好是保留在客棧中的變量值。這些信息很快就會變得異常主要。

如今quotient的值釀成了零,遞歸函數便不再挪用本身,而是開端打印輸入。然後函數前往,並開端燒毀客棧上的變量值。

每次挪用putchar獲得變量value的最初一個數字,辦法是對value停止模10取余運算,其成果是一個0到9之間的整數。把它與字符常量‘0'相加,其成果就是對應於這個數字的ASCII字符,然後把這個字符打印出來。

輸入4:


接著函數前往,它的變量從客棧中燒毀。接著,遞歸函數的前一次挪用從新持續履行,她所應用的是本身的變量,他們如今位於客棧的頂部。由於它的value值是42,所以挪用putchar後打印出來的數字是2。

輸入42:

接著遞歸函數的此次挪用也前往,它的變量也被燒毀,此時位於客棧頂部的是遞歸函數再前一次挪用的變量。遞歸挪用從這個地位持續履行,此次打印的數字是6。在此次挪用前往之前,客棧的內容以下:

輸入426:


如今我們曾經睜開了全部遞歸進程,並回到該函數最後的挪用。此次挪用打印出數字7,也就是它的value參數除10的余數。

輸入4267: 然後,這個遞歸函數就完全前往到其他函數挪用它的所在。
假如你把打印出來的字符一個接一個排在一路,湧現在打印機或屏幕上,你將看到准確的值:4267
應用遞歸必定要有跳出的前提:
這是一個最簡略的遞歸, 不外它會一向履行, 可用 Ctrl+C 終止.

#include <stdio.h>
void prn(int num) {
    printf("%d/n", num);
    if (num > 0) prn(--num); 
}
int main(void)
{
    prn(9);
    getchar();
    return 0;
}


實例: 翻轉字符串
#include <stdio.h>
void revers(char *cs);
int main(void)
{
    revers("123456789");
    getchar();   
    return 0;
}
void revers(char *cs)
{
    if (*cs)
    {
        revers(cs + 1);
        putchar(*cs);
    }
}


實例: 階乘
#include <stdio.h>
int factorial(int num);
int main(void)
{
    int i;
    for (i = 1; i <= 9; i++)
    printf("%d: %d/n", i, factorial(i));
    getchar();   
    return 0;
}
int factorial(int num)
{
    if (num == 1)
        return(1);
    else
        return(num * factorial(num-1));
}


實例: 整數到二進制
#include <stdio.h>
void IntToBinary(unsigned num);
int main(void)
{
    IntToBinary(255); /* 11111111 */
    getchar();
    return 0;
}
void IntToBinary(unsigned num) {
    int i = num % 2;
    if (num > 1) IntToBinary(num / 2);
    putchar(i ? '1' : '0');
//    putchar('0' + i);  /* 可取代下面一句 */
}


分析遞歸:
#include <stdio.h>
void prn(unsigned n);
int main(void)
{
    prn(1);
    getchar();
    return 0;
}
void prn(unsigned n) {
    printf("%d: %p/n", n, &n);  /* A */
    if (n < 4)
        prn(n+1);               /* B */
    printf("%d: %p/n", n, &n);  /* C */
}

例輸入後果圖:

剖析:
法式運轉到 A, 輸入了第一行.
此時 n=1, 知足 < 4 的前提, 持續履行 B 開端了自挪用(接著會輸入第二行); 留意 n=1 時語句 C 還有待履行.
...如斯輪回, 一向到 n=4, A 可以履行, 但因不知足前提 B 履行不了了; 終究在 n=4 時得以履行 C.
但此時內存中有四個函數都期待前往(分離是 n=1、2、3、4 時), 我們分離叫它 f1、f2、f3、f4.
f4 履行 C 輸入了第五行, 函數前往, 前往給 f3(此時 n=3), f3 得以持續履行 C, 輸入了第六行.
f3 -> f2 -> 持續 C, 輸入了第七行.
f2 -> f1 -> 持續 C, 輸入了第八行, 履行終了!

如斯看來, 遞歸函數照樣很費內存的(有時不如直接應用輪回), 但切實其實很奇妙.

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved