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