溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

算法設(shè)計(jì)與分析之循環(huán)與遞歸

發(fā)布時(shí)間:2020-04-26 02:34:37 來(lái)源:網(wǎng)絡(luò) 閱讀:1098 作者:Remyspot 欄目:移動(dòng)開(kāi)發(fā)

前言:

    循環(huán)與遞歸可以說(shuō)是算法設(shè)計(jì)中最基本但卻也是最重要的工具方法。循環(huán)和遞歸對(duì)于學(xué)習(xí)過(guò)高級(jí)程序設(shè)計(jì)語(yǔ)言的人來(lái)說(shuō)都并不陌生,但還是有必要仔細(xì)的探究一下循環(huán)和遞歸之間的相似和區(qū)別。循環(huán)與遞歸最大的相似之處莫不是在于他們?cè)谒惴ㄔO(shè)計(jì)中的工具作用,它們都起到了“以不變應(yīng)萬(wàn)變”的作用。“不變應(yīng)萬(wàn)變”不正是程序設(shè)計(jì)的核心內(nèi)容嗎?正因?yàn)槿绱?,更有必要探究一下這兩種不同的設(shè)計(jì)工具的區(qū)別。本文先從利用循環(huán)和遞歸工具設(shè)計(jì)算法時(shí)的設(shè)計(jì)要點(diǎn)來(lái)認(rèn)識(shí)循環(huán)和遞歸,然后再給出幾個(gè)具體的實(shí)例來(lái)說(shuō)明循環(huán)和遞歸的差異和優(yōu)劣。

 

1.循環(huán)設(shè)計(jì)要點(diǎn)

    循環(huán)設(shè)計(jì)的要點(diǎn)可以概括為三個(gè)部分:效率、正向思維、具體到抽象。下面以不同的具體實(shí)例來(lái)討論這三個(gè)要點(diǎn)。

1.1效率

    例1.1 求1/1!-1/3!+1/5!-1/7!+....+(-1)^(n+1)/(2n-1)!

    算法1:使用二重循環(huán)實(shí)現(xiàn),循環(huán)不變式為:s(n)=s(n-1)+(-1)^(n+1)/(2n-1)!。這樣的算法的時(shí)間復(fù)雜度為O(n^2)。針對(duì)本題有沒(méi)有時(shí)間復(fù)雜度為O(n)的算法呢?

    算法2:循環(huán)不變式:s(n)=s(n-1)+(-1)^(n+1)A(n); A(n)=A(n-1)*1/((2*n-2)*(2*n-1))。這個(gè)算法的時(shí)間復(fù)雜度為O(n)。其C++代碼實(shí)現(xiàn)如下:   

#include <iostream>
using namespace std;

int main(void){
 int n;
 cout<<"Please input a number:"<<endl;
 cin>>n;
 float sum=1; //存儲(chǔ)結(jié)果
 float t=1;// 存儲(chǔ)階乘
 int sign=1; //存儲(chǔ)負(fù)號(hào)

 //循環(huán)
 for(int i=2; i<=n; i++){
  sign=-sign;
  t=t*(2*i-2)*(2*i-1);
  sum+=sign/t;
 }
 cout<<"Result is: "<<sum<<endl; 
}

1.2正向思維

    正向思維的方法是從全局到局部、從概略到詳細(xì)的設(shè)計(jì)方法。是對(duì)一個(gè)問(wèn)題由整體到分解和細(xì)化的方法。這也是循環(huán)設(shè)計(jì)的一般方法。下面求完數(shù)的例子可以說(shuō)明正向思維設(shè)計(jì)要點(diǎn)的過(guò)程。

    例1.2  一個(gè)數(shù)如果恰好等于它的因子之和(包括1但不包括這個(gè)數(shù)本身),這個(gè)數(shù)就成為完數(shù)。

    1)頂層算法

        for(i=1; i<=n; i++){

            判斷i是否為完數(shù);

            是完數(shù),輸出;

        }

    2)判斷i是否為完數(shù)的算法

        for(j=2; j<i; j++)

            計(jì)算i的因子,并累加;

            如果累加的值為i,輸出i;

    3)進(jìn)一步細(xì)化判斷i是否為完數(shù)的算法

        s=1;

        for(j=2; j<i; j++){

            if(i mod j ==0)

                s += j;

        }

        if(s==i)

            輸出i;

1.3具體到抽象

    具體到抽象的方法也可以說(shuō)成是數(shù)學(xué)歸納法。數(shù)學(xué)歸納法可以說(shuō)是算法設(shè)計(jì)中非常重要的一種方法。算法設(shè)計(jì)的本質(zhì),可以說(shuō)就是在看似雜亂無(wú)章的信息中總結(jié)歸納出一種不變的規(guī)律,以不變應(yīng)萬(wàn)變。下面的這個(gè)打印規(guī)則圖形的例子可以說(shuō)明這點(diǎn)。

    例1.3  打印具有下圖規(guī)律的圖形

        1

        5     2

        8     6    3

        10   9    7    4

算法C++實(shí)現(xiàn)如下:

#include<iostream>
#define N 10
using namespace std;
int main(void){
 int a[N+1][N+1];
 int k=1;
 for(int i=1; i<=N; i++)
  for(int j=1; j<=N+1-i; j++)
   a[i+j-1][j]=k++;

 for(int i=1; i<=N;i++){
  for(int j=1; j<=i; j++)
   cout<<a[i][j]<<" "; 
  cout<<endl; 
 }  

 

2.遞歸設(shè)計(jì)要點(diǎn)

    遞歸設(shè)計(jì)方法,也可以叫做逆向思維方法(對(duì)比與循環(huán)設(shè)計(jì)的正向思維)。遞歸設(shè)計(jì)通常有以下三個(gè)步驟:

    (1)尋找遞歸關(guān)系:找出大規(guī)模問(wèn)題于小規(guī)模問(wèn)題的關(guān)系,這樣通過(guò)遞歸是問(wèn)題的規(guī)模逐漸變小。

    (2)找出遞歸停止的條件,即算法可解的最小規(guī)模問(wèn)題。

    (3)設(shè)計(jì)函數(shù),確定函數(shù)所需參數(shù)。

    下面給出一個(gè)整數(shù)劃分問(wèn)題的遞歸算法設(shè)計(jì):

    列2.1  求一個(gè)整數(shù)劃分的種類(lèi)數(shù)。列如6有11種劃分如下:

        6

        5+1

        4+2                    4+1+1

        3+3                    3+2+1                    3+1+1+1

        2+2+2                2+2+1+1                2+1+1+1+1

        1+1+1+1+1+1+1

算法描述及C++實(shí)現(xiàn):

/*
*定義一個(gè)函數(shù)Q(n,m)表示整數(shù)n的任何加數(shù)都不超過(guò)m的劃分?jǐn)?shù)目
*n的所有劃分?jǐn)?shù)目P(n)就應(yīng)該表示為Q(n,n).
*
*一般Q(n,m)有如下遞歸關(guān)系:
*Q(n,n)=1+Q(n,n-1);
*Q(n,m)=Q(n,m-1)+Q(n-m,m)
*右邊第一部分表示不包含m的劃分,第二部分表示包含m的劃分
*那么就意味著剩下的部分就是對(duì)n-m進(jìn)行不超過(guò)m的劃分。
*
*遞歸的停止條件:
*Q(n,1)=1,表示當(dāng)最大的被加數(shù)是1時(shí),該整數(shù)n只有一種劃分
*Q(1,m)=1,表示整數(shù)1只有一個(gè)劃分,不管最大被加數(shù)的上限
*是多少。
*
*算法的穩(wěn)健性:
*如果n<m,則Q(n,m)是無(wú)意義的,此時(shí)Q(n,m)=Q(n,n)
*同樣的當(dāng)n<1或m<1時(shí),Q(n,m)也是無(wú)意義的。 
*/

#include<iostream>
using namespace std;

int Divinteger(int n, int m){

 if( n<1||m<1 )//錯(cuò)誤條件 
  cout<<"Error input!"<<endl;

 else if( n==1||m==1 )//停止條件 
  return 1;

 else if( n<m )//穩(wěn)健條件 
  return Divinteger(n, n);

 else if( n==m )// Q(n,n)=1+Q(n,n-1)
  return (1+Divinteger(n, n-1)); 

 else //Q(n,m)=Q(n,m-1)+Q(n-m,m)
  return (Divinteger(n, m-1)+Divinteger(n-m, m));

int main(void){
 int n;
 cout<<"Please input a number:"<<endl;
 cin>>n;
 cout<<"Result is: "<<Divinteger(n,n)<<endl;
}

3.循環(huán)與遞歸的比較

    這個(gè)部分以不同的例子引出三個(gè)結(jié)論。

3.1結(jié)論1:在具體實(shí)現(xiàn)時(shí),方便的情況下應(yīng)該把遞歸算法轉(zhuǎn)化成等價(jià)的循環(huán)結(jié)構(gòu)算法,以提高算法的時(shí)空效率。

    例3.1 將一個(gè)十進(jìn)制整數(shù)由低位到高位按位輸出。

/*
* 將一個(gè)十進(jìn)制整數(shù)從低位到高位逐位輸出 
* 循環(huán)不僅在時(shí)間而且在空間效率均高于遞
* 歸程序。 
*/

void for_low_to_high(int n){
 while(n){
  cout<<n%M<<" ";
  n=n/M;
 }
 cout<<endl;
}

void f_ltoh(int n){
 if(n<M)
  cout<<n<<endl;
 else{
  cout<<n%M<<" ";
  f_ltoh(n/M);
 }
}

    例3.2 將一個(gè)十進(jìn)制整數(shù)由高位到低位按位輸出。

/*
* 將一個(gè)十進(jìn)制整數(shù)從高位到低位逐位輸出 
* 循環(huán)與遞歸空間效率一樣,雖然時(shí)間效率
* 有差異,但遞歸程序間單可讀性好。 
*/ 

void for_high_to_low(int n){

 int i=0;int a[16];
 while(n){
  a[i]= n%M;
  n=n/M;
  i++;
 }
 for(int j=i-1; j>=0; j--)
  cout<<a[j]<<" ";
 cout<<endl;

void f_htol(int n){
 if(n<M)
  cout<<n<<" ";
 else{
  f_htol(n/M);
  cout<<n%M<<" ";
 }
}
3.2結(jié)論2:當(dāng)問(wèn)題需要后進(jìn)先出的操作時(shí),還是遞歸算法更有效 。如樹(shù)的遍歷和圖的深度優(yōu)先算法等都是如此。所以不能僅僅從效率上評(píng)價(jià)兩種控制重復(fù)操作機(jī)制的好壞。

    例3.3 用2的冪次方表示一個(gè)正整數(shù)。例如:137=2^7+2^3+2^0,則137可表示為: 2(7)+2(3)+2(0),進(jìn)一步:7=2^2+2+2^0,3=2+2^0 所以137可表示為:2(2(2)+2+2(0))+2(2+2(0))+2(0) 。

算法的C++實(shí)現(xiàn):

#include<iostream>
using namespace std;

void tryf(int n, int r=0){//n為數(shù),r為深度 
 if(n==1)//遞歸結(jié)束條件 
  cout<<"2("<<r<<")";
 else{//n除以2,深度加1 
  tryf(n/2,r+1);
  if(n%2==1)//如果余數(shù)不為0輸出2的r冪次 
   cout<<"+2("<<r<<")";
 }
}

void tryff(int n, int r=0){
 if(n==1){
  switch(r){
   case 0:cout<<"2(0)";break;
   case 1:cout<<"2";break;
   case 2:cout<<"2(2)";break;
   default:{cout<<"2(";tryff(r, 0);cout<<")";}
  }
 }else{
  tryff(n/2, r+1);
  if(n%2==1){
   switch(r){
    case 0:cout<<"+2(0)";break;
    case 1:cout<<"+2";break;
    case 2:cout<<"+2(2)";break;
    default:{cout<<"+2(";tryff(r, 0);cout<<")";}
   }//switch
  }//if 
 }//else
}

int main(void){
 int n;
 cout<<"Please input a number:"<<endl;
 cin>>n;
 if(n>1){
  tryf(n);
  cout<<endl;
  tryff(n);
 }else
  cout<<"Inupt Error!"<<endl;

3.3結(jié)論3:遞歸是一種強(qiáng)有力的算法設(shè)計(jì)工具。遞歸是一種比循環(huán)更強(qiáng)、更好用的實(shí)現(xiàn)重復(fù)操作的機(jī)制。因?yàn)檫f歸不需要編程者自己構(gòu)造循環(huán)不變式,而只需找出遞歸關(guān)系和最小問(wèn)題的解。遞歸在很多算法策略中得以運(yùn)用,如分治策略、動(dòng)態(tài)規(guī)劃、圖的搜索等算法策略。

    由下面的例子可以看出遞歸的層次可以控制的,而循環(huán)嵌套的層次只能是固定的。

    例3.4 找出n個(gè)自然數(shù)(1,2,3,4,5,.....,n)中取r個(gè)數(shù)的組合。

算法C++實(shí)現(xiàn):

#include <iostream>
#define N 5
#define R 3 
using namespace std;

void for_fun1(){
 int t=0;
 for(int i=1; i<=N; i++)
  for(int j=1; j<=N; j++)
   for(int k=1; k<=N; k++)
    if( (i<j) && (j<k) ){
     t++;
     cout<<i<<" "<<j<<" "<<k<<endl; 
    }
 cout<<"Total= "<<t<<endl;
}

//more efficiency
void for_fun2(){
 int t=0;
 for(int i=1; i<=N-R+1; i++)
  for(int j=i+1; j<=N-R+2; j++)
   for(int k=j+1; k<=N-R+3; k++){
    t++;
    cout<<i<<" "<<j<<" "<<k<<endl;
   }
}

//recruisive 
int a[100];
int t=0;
void comb(int n, int r){
 for(int i=n; i>=r; i--){//循環(huán)n-r+1次 
  a[r]=i;//n中選r個(gè)數(shù),確定第一個(gè)數(shù) 
  if(r>1){//遞歸深度為r 
   comb(i-1,r-1);
  }
  else{//結(jié)束條件為深度r等于1 
   for(int j=a[0]; j>0; j--)
     cout<<a[j]<<" ";
    cout<<endl;
    t++;
  }
 }
} //算法復(fù)雜度O(n*r)
void rec_fun(){
 a[0]=R;
 comb(N,R);
 cout<<"Total= "<<t<<endl;

int main(void){
 for_fun1();
 for_fun2();
 rec_fun();
}

    最后對(duì)遞歸說(shuō)明一點(diǎn),遞歸可以說(shuō)是一種算法策略,也可以說(shuō)是一種算法設(shè)計(jì)的工具,不管從哪一方面來(lái)看對(duì)算法設(shè)計(jì)都是很好的幫助。遞歸在很多算法策略中得以運(yùn)用,如分治策略、動(dòng)態(tài)規(guī)劃、圖的搜索等算法策略。有很多數(shù)據(jù)結(jié)構(gòu)都是具有遞歸定義的,如鏈表,隊(duì)列,棧,二叉樹(shù)。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI