340 likes | 672 Views
迴圈 & 遞迴 (Loop & Recursion). Kevingyc 2009/02/15. 迴圈. 在 C 語言中,有以下幾種迴圈 (Loop) :. For Loop. While Loop. Do while Loop. For Loop. Format : for ( init-expression ; cond-expression ; loop-expression ) { statement }. Example : 請印出 1 到 10. int i; for(i = 1; i < 11; i++) {
E N D
迴圈 & 遞迴(Loop & Recursion) Kevingyc 2009/02/15
迴圈 • 在C語言中,有以下幾種迴圈(Loop): • For Loop • While Loop • Do while Loop
For Loop • Format : for (init-expression; cond-expression ; loop-expression) {statement} Example : 請印出1到10 int i; for(i = 1; i < 11; i++) { printf("%d ", i); }
For Loop的應用 int a[3][3]={0,1,2,3,4,5,6,7,8}; int i,j; for(i=0;i<3;i++) { for(j=0;j<3;j++) { printf("%d ", a[i][j]); } printf(“\n”); } a. 利用兩個for loop印出矩陣( 巢狀迴圈 ) : b. 把陣列的所有值平方並存回陣列: int array[7]={1,2,3,4,5,6,7}; int i; for(i=0;i<7;i++) { array[i] *= array[i]; printf("%d ", array[i]); }
While Loop • Format : while (expression) {statement } Example : 請印出1到10 int i=1; while( i < 11) { printf("%d ", i); i++; } int i; for(i = 1; i < 11; i++) { printf("%d ", i); } ==
While & For • 兩者是可以互換的! 我們用上一頁的例子來看看 : // While version int i=1; while( i < 11 ) { printf("%d ", i); i++; } // For version int i; for(i = 1; i < 11; i++) { printf("%d ", i); }
While Loop的應用 int i= 1, keyIn, number, max = 0; printf("請輸入你想輸入的數字有幾個\n"); scanf("%d",&keyIn); while(i<=keyIn) { printf("請輸入第%d個數字:",i); scanf("%d",&number); if( number > max) { max = number; } i++; } printf("最大的數字是 : %d\n",max); a.請讓使用者輸入數字,並找出最大的數字:
While Loop的應用 b.請讓使用者輸入資料,直到輸入EOF( ctrl + z )為止: while(scanf("%s",&number) != EOF) { …………………………. …………………………. …………………………. } *EOF: Windows : ctrl + Z Linux : ctrl + D 這個是在寫ACM題目的時候常用的技巧, 讓使用者可以無限次的輸入資料, 直到指定的終止條件時才停止迴圈
Do While Loop • Format : do { statement } while (expression); Example : 請印出1到10 int i=1; do { printf("%d ", i); i++; } while( i < 11); do while 迴圈是while 迴圈的變形, 某些特殊需求時會需要用到 NOTE. do while 迴圈至少會執行一次
while & do while • 如果我們把…… expression=肚子還餓 statement=吃東西 while (expression) {statement } => 如果肚子還餓,就吃東西,直到肚子不餓 do { statement } while (expression) =>先吃東西,如果肚子還餓就再吃東西,直到肚子不餓 我們看看實際的例子吧!
while & do while的比較-實際例子 // do while int i=11; do { printf("%d ", i); i++; } while( i < 11); // while int i=11; while( i < 11) { printf("%d ", i); i++; }
迴圈的輔助指令-Break & Continue • Break : 執行到break時,就終止該迴圈 ( 只會跳出一層!! ) • Continue : 執行到continue時,就會跳到迴圈最下面 而直接繼續進行迴圈 // break的例子 // 當 i == 3時,break int i; for(i = 1; i < 11; i++) { if( i==3 ) { break; } printf("%d ", i); } // continue的例子 // 當 i == 7時,continue int i; for(i = 1; i < 11; i++) { if( i==7 ) { continue; } printf("%d ", i); }
Break小問題 Break、Continue、goto這些指令請盡量少用 以免破壞程式結構=>因為會影響程式的可讀性 1.當程式運作時,結果為何? 2.當程式運作時,結果為何? int i,j; for(i = 1; i <= 5; i++) { for(j = 1; j <= 5; j++) { printf("%d ", j); if( i==j ) break; } printf("\n"); } int i,j; for(i = 1; i <= 10; i++) { for(j = 1; j <= 5; j++) { break; printf("%d ", j); } printf("%d ", i); }
你不能不知道-迴圈的四大爆點 1.陣列操作不可以寫超過陣列大小 int a[3]={0,1,2}; Int i; for(i=0 ;i<=4;i++) { printf("%d ",a[i]); } 如果我們寫的範圍比我們宣告的陣列還要大, 除了會造成溢位之外還有可能會發生RE ( Runtime Error )的錯誤, 而且會跑出我們意想不到的東西,如圖中標示的那兩個數字
你不能不知道-迴圈的四大爆點 2.使用的變數都要在有效範圍內使用 int i; for(i=0 ;i<=4;i++) { int j=i; printf("%d ",j); } printf("%d ",j); `j' undeclared (first use this function) 變數都有屬於他的有效範圍, 通常是在 { } 之間 ( 全域變數為整個程式 ), 如果使用變數超出他的有效範圍時, 就會發生CE (Compile Error)的錯誤,就像下面紅色的錯誤訊息一樣
Additional : 變數生命週期(Scope) • 對某一個變數來說,可以使用到這個變數的程式範圍就稱為這個變數的作用範圍,或稱為變數的生命週期(scope)。 • 由於區塊是階層式的,大區塊可以內含小區塊,大區塊內的變數也可以在內含區塊內使用。 範圍: 生於定義變數! 死於區塊結束!
Additional : 變數週期範例 int g = 7; // global variable int main() { int i = 1; // local variable { char i = 'k'; // local variable int y = 2; // local variable printf("i = %d y = %d g = %d\n",i,y,g); // printf 1 } printf("i = %d y = %d g = %d\n",i,y,g); // printf 2 return 0; } // printf 1 g = i = y = 7 107 2 // printf 2 g = i = y = 7 1 undeclared!!!
你不能不知道-迴圈的四大爆點 3.要設定合理的終止條件, 否則會造成無預期的無限迴圈( infinite loop ) for(i= 0; ;i++ ){ } // 沒有設終止條件 for(i= 0; i<=5; ){ } // i不動,所以不會結束 一定要告訴迴圈什麼時候該結束,否則迴圈會沒辦法結束, 而造成 TLE( Time Limit Exceeded )的錯誤, 像以上兩個寫法都會造成無限迴圈
你不能不知道-迴圈的四大爆點 4.注意括號的對應, 尤其是當巢狀迴圈寫很大很複雜的時候 while(1) { while(2) { while(3) .............. .............. } ............. } while(1) { while(2) { while(3) { .............. } ............... } ............. } 寫很多層迴圈的時候,一定會有很多括號, 這時候對應的問題就很重要了 盡量不要省略任何大括號,並且把他整理清楚 這樣對以後要除蟲(Debug)的時候會很有幫助
迴圈小結 • 迴圈是一個程式裡面最基本的元件,所以一定要活用他,但是要注意以上的四個爆點,千萬不要去觸碰他,否則你的程式就會轟一聲地爆炸了! • 當你很明確的知道迴圈什麼時候會結束,用for迴圈可以有比較清楚的架構;但是當你不確定迴圈什麼時候會結束的時候,反而用while迴圈會比較合適些。這些狀況在往後的程式設計上會很常碰到,仔細想想你要的終止條件到底是什麼,以及你需要做些什麼事情,就可輕易寫出正確的迴圈啦!!
遞迴 void print_all(int i) { if(i!=0) { print_all(i-1); printf("%d ",i); } } • 什麼是遞迴(Recursion)? 就像剝洋蔥一樣, 一層一層的往裡面剝 Example : 請印出1到10 所謂recursion,就是”function在自己裡面呼叫自己”, 這種結構就叫做recursion。 把問題拆成「較小的相同問題」, 把小問題解決了,大問題自然跟著解了。
遞迴的基本結構 void recur(int k) { if(k!=0) { printf("%d ",k); recur(k-1); } } 假設我們有個程式是長這樣 當 k = 4的時候… void recur(int 4) { if(4!=0) { printf("%d ",4); recur(4-1); } } void recur(int 3) { if(3!=0) { printf("%d ",3); recur(3-1); } } void recur(int 2) { if(2!=0) { printf("%d ",2); recur(2-1); } } void recur(int 1) { ……. } 程式是這樣子運作的…
遞迴的基本結構 void recur(int k) { if(k!=0) { printf("%d ",k); recur(k-1); } } 好像有點不好懂,那換一個角度來想,如果現在有四個副程式 當 k = 4的時候… recur == recur1 == recur2 == recur3 void recur(int 4) { if(4!=0) { printf("%d ",4); recur1(4-1); } } void recur1(int 3) { if(3!=0) { printf("%d ",3); recur2(3-1); } } void recur2(int 2) { if(2!=0) { printf("%d ",2); recur3(2-1); } } void recur3(int 1) { ……. } 程式是這樣子運作的…
遞迴的基本結構 基本上程式遞迴運作的情況會像下面這個圖所表示的狀況 if k == 4 K = 2 4 1 0 3 Output : 4 3 2 1 1 2 3 4 True, Print(4) recur(4) Print(4) True, Print(3) recur(3) Print(3), return void recur(int k) { if(k!=0) { printf("%d ",k); recur(k-1); printf("%d ",k); } } recur(2) True, Print(2) Print(2), return recur(1) True, Print(1) Print(1), return recur(0) False, return
遞迴的應用-費式數列 ( 遞迴式 ) F(n) = F(n-1) + F(n-2), F(1) = F(2) = 1 int F(int n) { if(n==1 || n==2) { return 1; } else { return f(n-1) + f(n-2); } } 以n=5來舉例: F(5) = F(4) + F(3) F(4) = F(3) + F(2) F(3) = F(2) + F(1) F(2) = 1 + F(1) = 1 => F(5) = F(4) + F(3) = [F(3) + F(2) ] + [ F(2) + F(1) ] F(5) = {[ F(2) + F(1) ] + F(2) } + [ F(2) + F(1) ] F(5) = 1 + 1 + 1 + 1 + 1 = 5
int F(int n) { if(n==1 || n==2) { return 1; } else { return f(n-1) + f(n-2); } } 遞迴的應用-費式數列 ( 圖表 ) F(n) = F(n-1)+F(n-2), F(1) = F(2) = 1 以n=5來舉例: F(5)= 5 3 F(4)= F(3)= 2 2 F(3)= F(2)= 1 F(2)= 1 F(1)= 1 F(2)= 1 F(1)= 1
遞迴的應用-求GCD(最大公因數) gcd(4005,2403) = 801 int gcd( int a, int b ) { if(a%b!=0) return gcd(b,a%b); else return b; } return gcd(2403,1602) = 801 return gcd(1602,801) = 801 int gcd( int a, int b ) // 全部擠在一行 { return (a%b!=0) ? gcd(b,a%b): b; }
兩者在大部分的情況下是可以互換的! 迴圈 & 遞迴 當我們在coding的時候,有些時候用迴圈會比較好寫,有時候用遞迴比較簡單,但大部分時候兩者是可以互相轉換的 Loop Recursion int gcd(int a,int b)// loop { int x; do { x=a%b; a=b; b=x; }while(x!=0); return x; } int gcd( int a, int b ) // recursion { return (a%b!=0) ? gcd(b,a%b): b; }
遞迴的進階思考-連通問題 • 什麼叫做連通? 顧名思義, 連通就是連在一起並成為一個通路 假設A是一個人,他能走到的點是他四周的八個方格裡,數字為1的點. 能走的點,即為”連通”點。依各題型的不同,所問的問題也不一樣,但概 念都是類似的。像油田數量,走迷宮…都是屬於連通問題
連通問題範例 ACM第352題就是個典型的連通問題 “假設有一塊地圖,我們以0和1來表示不同的區塊,問1在這個地圖中有幾塊(與四周的八格彼此連通的都算同一塊)? 0 1 2 3 4 5 區塊數 : 0 1 2 3 0 0 1 1 0 1 0 1 0 1.發現1,並將他標記成0 1 2.區塊數加1 2 3.檢查四周是否有1,若有,將其標記為0 1 1 0 0 3 4.遞回檢查四周是否有1,若有,將其標記為0 1 1 1 0 0 0 4 1 0 0 1 5 FINISH!!!
遞迴小結 • 何時該使用遞迴,是依照問題的解決方法來看,如果需要做同一件事情很多次,而且你不知道到底要做幾次的話,用遞迴可能是比較好的選擇,當然用迴圈還是可以寫出來。 • 當你程式語言越學越多、越學越深的時候,你會發現很多的函式都有用到遞迴的觀念,所以不要害怕使用遞迴,他是非常直覺的一種思考方法。 一定要設終止條件!! 否則就會無窮遞迴到 system stack overflow!!
練習題 基本題 : (迴圈) 488 Triangle Wave 100 The 3n + 1problem (遞迴) 10696 F91 408 Uniform Generator 10922 2 the 9s 進階題: 10018 Reverse and Add 10035 Primary Arithmetic 572 Oil Deposits
參考資料 • 08年寒訓講義 • 07年寒訓講義 • 06年寒訓講義 • On Internet