第三周:循環
3.1 循環
while循環
語法:
while(條件表達式){
//循環體語句
}
- 如果我們把while翻譯作“當”,那么一個while循環的意思就是:當條件滿足時,不斷地重復循環體內的語句。
- 循環體執行之前判斷是否繼續循環,所以有可能循環一次也沒有被執行、
- 條件成立時循環繼續的條件
- 循環體執行步驟
- 檢查條件表達式是否成立
- 不成立結束循環,成立執行循環體內語句后回到第一步。
例子:數整數的位數
用戶輸入一個整數,要求輸出整數的位數。比如輸入123,輸出3
輸入樣例
123
輸出樣例
3
程序實現:
#include <stdio.h>
int main(int argc, char *argv[]) {
int x = 0;
int n = 0;
printf("請輸入一個整數:");
scanf("%d", &x);
n++;
x /= 10;
while(x > 0) {
x /= 10;
n++;
}
printf("這個整數的位數是%d", n);
return 0;
}
運行結果:
請輸入一個整數:123
這個整數的位數是3
復合運算
- 5個算術運算符,+、-、、/、% 都可以和賦值運算符
"="
結合起來,形成復合運算符:“+=”、“-=”、“=”、“/=”、“%=”- total += 5;
- total = total + 5;
- 注意兩個運算符中間不要有空格
遞增遞減運算符
- "++"和"--"是兩個很特殊的運算符,它們是單目運算符,這個算子還必須是變量。
- 以上兩個運算符分別叫做遞增和遞減運算符,它們的作用就是給這個變量+1或者-1
- count ++;
- count += 1;
- count = count + 1;
- 以上3個表達式是等價的
前綴和后綴
- ++和--可以放在變量的前面,叫做前綴形式,也可以放在變量的后面,叫做后綴形式。
- a++的值是先將a參與運算,再將a的值自增1,而++a則是先將a的值自增1再參與運算。
- 遞減運算符同理
比如:
int a = 0;
int b = a++ ; // a = 1,b = 0,
int c = ++a; // a = 2 ,c = 2
do-while循環
- 在進入循環體的時候不做條件表達式的檢查,而是在執行完一輪循環體的代碼之后,再來檢查循環的條件是否滿足,如果滿足則進行下一輪循環,不滿足則結束循環。
- 注意do-while的結尾需要分號的
- do-while和while循環區別是前者先執行語句再判斷條件,而后者先判斷條件再執行語句,具體使用那種循環需要根據實際情況而定。
語法:
do
{
<循環體語句>
} while(<循環條件>);
3.2 循環計算
猜數游戲
- 讓計算機來想一個數,然后讓用戶來猜,用戶每輸入一個數,就告訴他打了還是小了,直到用戶猜中為止,最后還要告訴用戶它猜了多少次。
程序實現分析:
- 計算機隨機想一個數,記在變量number里;
- 一個負責記次數的變量count初始化為0
- 讓用戶輸入一個數字a
- count遞增(加1)
- 判斷a和number的大小關系,如果a大,就輸出“大”;如果a小就輸出“小”;
- 如果a和number是不相等的(無論大還是小),程序轉回到第3步;
- 否則,程序輸出“猜中”和次數,然后結束。
程序實現:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char *argv[]) {
srand(time(0));
int number = rand() % 100 + 1;
int count = 0;
int a = 0;
printf("我已經想好了一個1到100之間的數。");
do{
printf("請猜這個1到100之間的數:");
scanf("%d", &a) ;
count ++;
if( a > number){
printf("你猜的數大了。");
} else if(a < number){
printf("你猜的數小了。");
}
} while (a != number);
printf("太好了,你用了%d次就猜到了答案。\n", count);
return 0;
}
測試樣例:
我已經想好了一個1到100之間的數。請猜這個1到100之間的數:50
你猜的數大了。請猜這個1到100之間的數:25
你猜的數大了。請猜這個1到100之間的數:13
你猜的數小了。請猜這個1到100之間的數:19
你猜的數大了。請猜這個1到100之間的數:16
你猜的數大了。請猜這個1到100之間的數:15
太好了,你用了6次就猜到了答案。
tips:在7次內一定能猜到答案,詳見討論題
3.3 課后習題
1、 題目內容:
你的程序要讀入一系列正整數數據,輸入-1 表示輸入結束,-1 本身不是輸入的數據。程
序輸出讀到的數據中的奇數和偶數的個數。
輸入格式:
一系列正整數,整數的范圍是(0,100000)。如果輸入-1 則表示輸入結束。
輸出格式:
兩個整數,第一個整數表示讀入數據中的奇數的個數,第二個整數表示讀入數據中的偶數的
個數。兩個整數之間以空格分隔。
輸入樣例:
9 3 4 2 5 7 -1
輸出樣例:
4 2
算法分析:
- 讀取用戶輸入一個數記錄在變量number中
- 判斷number是奇數或者偶數還是-1(能被2整除的數就是偶數,反之就是奇數)
2-1. 如果是-1,程序繼續執行第3步
2-2. 如果用戶輸入數字超出范圍,提示用戶重新輸入,程序回到第1步
2-3. 如果是奇數記錄在變量odd中,並自增1,程序回到第1步
2-4. 如果是偶數記錄在變量even中,並自增1,程序回到第1步 - 輸出奇數和偶數,程序結束
程序實現:
#include <stdio.h>
int main(int argc, char *argv[]) {
int number = 0;
int odd = 0;
int even = 0;
printf("請輸入一系列整數范圍在0-100000,輸入-1表示結束輸入:");
scanf("%d", &number);
while(number != -1) {
if(number < 0 || number > 100000){ //"||"表示 在"||"左邊表達式或者右邊的表達式任意一個成立,也就是說當number<0 或者 number>100000 時表達式成立
printf("您輸入的數字%d,超出范圍,不會被統計。\n", number);
} else if (number % 2 == 0){
even++;
} else {
odd++;
}
scanf("%d", &number);
}
printf("%d %d", odd, even);
}
//odd 2
//even 4
//9 3 4 2 5 7 -1
//4 2
測試樣例:
請輸入一系列整數范圍在0-100000,輸入-1表示結束輸入:9 3 4 2 5 7 -1
4 2
--------------------------------
請輸入一系列整數范圍在0-100000,輸入-1表示結束輸入:9 3 4 2 5 7 -2 110000 -1
您輸入的數字-2,超出范圍,不會被統計。
您輸入的數字110000,超出范圍,不會被統計。
4 2
--------------------------------
2、 題目內容:
對數字求特征值是常用的編碼算法,奇偶特征是一種簡單的特征值。對於一個整數,從個位
開始對每一位數字編號,個位是 1 號,十位是 2 號,以此類推。這個整數在第 n 位上的數
字記作 x,如果 x 和 n 的奇偶性相同,則記下一個 1,否則記下一個 0。按照整數的順序把
對應位的表示奇偶性的0和1都記錄下來,就形成了一個二進制數字。比如,對於342315,
這個二進制數字就是 001101。
這里的計算可以用下面的表格來表示:
按照二進制位值將 1 的位的位值加起來就得到了結果 13。
你的程序要讀入一個非負整數,整數的范圍是[0,1000000],然后按照上述算法計算出表
示奇偶性的那個二進制數字,輸出它對應的十進制值。
提示:將整數從右向左分解,數位每次加 1,而二進制值每次乘 2。
輸入格式:
一個非負整數,整數的范圍是[0,1000000]。
輸出格式:
一個整數,表示計算結果。
輸入樣例:
342315
輸出樣例:
13
算法分析:
- 讀取用戶輸入的數字記錄到number
- 判斷用戶輸入的值,超出范圍,提示用戶,程序回到第1步
- 否則在合理范圍內求number的特征值
3-1. 如果number為0,程序跳轉第4步
3-2. 當前的位數digit自增1
3-3. 取number的個位到tmp中
3-4. 分別取tmp和digit的奇偶性記錄到isEvenTmp和isEvenDigit中
3-5. 如果tmp和digit的奇偶性相同,累加2當前的平方數squareOfTwo到eigenvalue中
3-6. 將number整除10,移除掉已經處理的位數,squareOfTwo乘2得到下一個平方數,程序回到第3-1 - 輸出特征值eigenvalue,程序結束
tips:有一種特殊情況用戶第一次輸入的number就是0,那么我們的程序會在3-1跳轉到第4步結束,並不會計算特征值,我們知道0的特征值為0,因此我們只需要將eigenvalue的默認值設置為0即可。當然還有別的處理方法,具體使用哪種我們需要考量程序的可讀性和效率擇優選擇。你是否能想出其他的處理方式呢?
程序實現:
#include <stdio.h>
int main(int argc, char *argv[]) {
int number = 0;
int digit = 0;
int tmp = 0;
int eigenvalue = 0;
int isEvenTmp = 0;
int isEvenDigit = 0;
int squareOfTwo = 1; //2的0次方為1
printf("請輸入一個整數范圍在0-1000000:");
scanf("%d", &number);
while(number < 0 || number > 1000000 ) {
printf("%d已超出范圍,請重新輸入:", number);
scanf("%d", &number);
}
while(number != 0){
digit++;
tmp = number % 10;
isEvenTmp = tmp % 2 == 0;
isEvenDigit = digit % 2 == 0;
if(isEvenTmp == isEvenDigit){
eigenvalue += squareOfTwo;
}
number /= 10;
squareOfTwo *= 2;
}
printf("%d", eigenvalue);
return 0;
}
測試樣例:
請輸入一個整數范圍在0-1000000:342315
13
--------------------------------
請輸入一個整數范圍在0-1000000:0
0
--------------------------------
請輸入一個整數范圍在0-1000000:12345
31
--------------------------------
請輸入一個整數范圍在0-1000000:-1
-1已超出范圍,請重新輸入:1100000
1100000已超出范圍,請重新輸入:123
7
--------------------------------
3.4 討論題(不需要掌握)
三、(3-2 循環計算)
標題:猜數游戲
內容:為什么方法正確的話,100 以內的數最多猜 7 次就夠了?
題目分析:
當我們知道一個數的范圍時,我們可以采取二分法來猜這個數字的大小:
- 猜這個范圍的中間值
2.程序反饋數字是否猜對
2-1. 如果不對程序會告訴我們大小,就能把范圍會縮小一半然后回到第1步
2-2. 否則我們猜對了
我們先小范圍測試,把范圍縮小到1-10
tips:
- 在最壞的情況下我們使用二分法了4次猜出正確的數字
- 在第一次猜5,也可能是大的情況,范圍則為[1,4]有4個數字,但是我們要求每次都是最壞的情況因此取小的情況范圍[6,10]
- mid:用戶猜的中間數字
- com: num和實際數字之間的關系,E=相等,B=大了,S=小了
- ran: 縮小后的范圍 比如[4-8]代表4、5、6、7、8
- count: 范圍內的整數個數
- time: 用戶猜了多少次
- 變量star代表范圍起始,變量end代表范圍結束。
- 中間數mid的計算公式:mid = star + (end - star + 1)/2;
- 縮小的范圍range計算公式:
- 當用戶猜的數大了的情況,end = mid- 1; ran = [star,end];
- 當用戶猜的數小了的情況,star = mid + 1; ran =[star,end];
- rang整數個數count的計算公式: count = (end - star + 1);
那么采用二分法最壞的情況下需要多少次能猜中100呢?
假設我們有n個連續的整數,它的范圍為[1,n]
如果我們采取從遞增猜方法從1開始猜一直猜到n,1、2、..n 那么最壞的情況下我們就需要猜n次。
對於100個數來說就是要猜100次。
但是我們用了二分法,如果是最壞的情況,我們的范圍中整數的個數每次都會縮小一半,n/21、n/22...n/2^m,
當n除以2的m次冪等於1時我們就能猜出程序的那個數字了,也就是說理論上我們需要猜m次。
因此如果有n個數,計算猜m次的公式是m=log2n; m是以2為底n的對數。
通過計算log2100 = 6.6438561898,我們知道理論最多猜7次就能得到正確答案。
這里用連續猜和二分法猜數的效率差了大約只有10倍,當這個數字的范圍是1到100萬時,連續猜需要猜100萬次,而二分法只需要猜20次,足足差了5萬倍,如果這個猜數也是由一個程序來完成,A程序使用遞增猜算法(n),B程序使用二分猜算法(log2n),當n等於100萬時他們的效率就差了5萬倍,可以預見當n增加復雜度還會以幾何式的增加, 由此可見一個好的算法往往會給程序帶來效率的是數量級上的提升。
擴展:理論次數和實際次數
細心的讀者會發現通過上面公式計算所得的理論次數和實際次數在一些情況下會不一樣。比如說當范圍為[1,8]時最多應該猜多少次呢?通過計算我們知道是3,但是這個結果對嗎?我們通過表格來模擬一下程序。
范圍[1,8] 程序模擬:
結果是4次,我們確實在第3次知道了結果,但是需要猜第4次程序才會告訴我們猜對了。因此實際猜的次數 rm = log2n + 1;
如果我們要猜的整數恰好是7的話,我們算出來的結果是 3次(舍去了log2n小數的部分,比如2.99 舍掉小數部分就是2),模擬下程序的結果是否和我們理論計算的一致
范圍[1,7] 表格模擬:
確實是3次
范圍[1,6]:
范圍[1,5]:
范圍[1,4]:
擴展-思考:
你能用實現模擬猜數程序嗎?