1. 打靶問題的遞歸解法
倫敦奧運會火熱進行中,讓我們來看個打靶的問題:一個射擊運動員打靶,靶一共有10環,求連開10槍打中90環的可能行有多少種?
分析:這是一個典型遞歸求解問題。假設第10槍打x環,則將問題轉換為剩下9槍打90-x環的可能有多少種,x的取值范圍為[0, 10],根據加法原理,則:10槍打90環的可能 = 第10槍打0環,剩下9槍打90環的可能 + 第10槍打1環,剩下9槍打89環的可能 + 第10槍打2環,剩下9槍打88環的可能
+ 第10槍打3環,剩下9槍打87環的可能 + 第10槍打4環,剩下9槍打86環的可能 + 第10槍打5環,剩下9槍打85環的可能
+ 第10槍打6環,剩下9槍打84環的可能 + 第10槍打7環,剩下9槍打83環的可能 + 第10槍打8環,剩下9槍打82環的可能
+ 第10槍打9環,剩下9槍打81環的可能 + 第10槍打10環,剩下9槍打80環的可能。
遞歸的停止條件為:
1. 若環數小於0 或者 剩下的環數大於剩下的槍數乘以10(即剩下每槍打10環用不玩所剩環數),則該遞歸路徑不記入可能情況。
2. 若不滿足條件1,且所剩槍數為1,則該遞歸路徑記為1中可能情況。
1: #include <stdio.h>
2:
3: int recursion(int count, int score)
4: {
5: if (score < 0 || score > count * 10) {
6: return 0;
7: }
8:
9: if (1 == count) {
10: return 1;
11: }
12:
13: int i = 0;
14: int sum = 0;
15:
16: for (; i <= 10; ++i) {
17: sum += recursion(count - 1, score - i);
18: }
19:
20: return sum;
21: }
22:
23: int main()
24: {
25: printf("result: %d\n", recursion(10, 90));
26: return 0;
27: }
2. 求多項式的值與求冪的快速算法
2.1 求多項式的值
已知多項式中的系數a0,a1,a2……an及x的值,求f(x)。
多項式求值問題可以利用輾轉相乘的方式進行計算,根據上面的式子分析,可以將上式轉換為: f(x) = ((((an * x) + an-1) * x + an-2) * x ……. + a1) * x + a0
有了這個變形,代碼自然而然就能寫出來了:
1: #include <stdio.h>
2:
3: int polynomial(const int *coefficient, int n, int x)
4: {
5: int sum = 0;
6:
7: int i = n - 1;
8: for (; i != 0; --i) {
9: sum = sum * x + coefficient[i];
10: }
11:
12: return sum * x + coefficient[0];
13: }
14:
15: int main()
16: {
17: int coeff[5] = {1, 2, 3, 4, 5};
18: printf("result: %d\n", polynomial(coeff, sizeof(coeff) / sizeof(coeff[0]), 10));
19: return 0;
20: }
2.2 求冪的快速算法
求x的p次冪本是簡單的問題,可以將x次乘p次就可以了,這里我們當然不是要討論這種計算方法,這里討論的是怎樣高效計算。
有沒有可能減少做乘法的次數呢?讓我們來做一個分析,考慮x的6次冪的情況:x * x * x * x * x * x = (x * x * x) * (x * x * x)
等式前面部分需進行6次乘法,后半部分需計算(x * x * x) ,然后乘以上次計算的值即可,共4次乘法。
通過上述分析可知,求x的p次冪可以通過遞歸折半的方法來減少乘法的次數,循着這個思考,實現就不太困難了。
1: #include <stdio.h>
2:
3: /* 統計乘法的次數 */
4: static int multiply_count = 0;
5:
6: /* 求x的p次冪 */
7: int power(int x, int p)
8: {
9: int ret = 0;
10:
11: if (1 == p) {
12: return x;
13: }
14:
15: /* 先計算x的p/2次冪 */
16: ret = power(x, p >> 1);
17: ret *= ret;
18: ++multiply_count;
19:
20: /* 若p是奇數,則再乘一次x */
21: if (0 != p % 2 /* 求模本身是一個耗性能的運算,這里可優化為 p – ((p >> 1) << 1) */) {
22: ret *= x;
23: ++multiply_count;
24: }
25:
26: return ret;
27: }
28:
29: int main()
30: {
31: printf("resulut: %d\n", power(2, 20));
32: printf("multiply count: %d\n", multiply_count);
33: return 0;
34: }
計算2的20次冪總共進行5次乘法就足夠了,而原始的算法需要20次,那么這種算法究竟需要多少次乘法呢?
由於該算法根本思想是折半遞歸,類似於2分查找,所以乘法的次數為lgp取天花板值這個數量級的(lgp表示以2為底p的對數)。
整型int最多表示2的32次冪,因此最多節省24次乘法,對於現代的計算機,這似乎不是特別重要,但是在以下情況下該算法具有重要價值:
1. 對於需要高頻率計算冪的情況;
2. 對於大數高精度計算的情況,如需計算2的10000次冪。
當然了,這也是對遞歸算法和2分法的巧妙應用,學習其思想吧。
3. 一年中的第n天是幾月幾號?
和這個問題類似的問題還有:
1. 給定某年某月某日,問這是這年的第多少天?
2. 已知某年的1月1號是星期幾,求給定的某年某月某日是星期幾?
其實這類問題的共同點在於它們都需要考慮閏年問題,大月小月問題;
首先我們解決閏年問題,根據閏年的定義定義如下宏來判斷某年是否是閏年:
#define IS_LEAP(X) (((X) % 400 == 0 || (X) % 100 != 0 && (X) % 4 == 0) ? 1 : 0)
在來看大月小月問題,可以定義如下二維數組,用day_count_of_month[0][12]表示潤年情況下每月的天數,用day_count_of_month[1][12]表示非潤年情況下每月的天數(這就是傳說中的字典法了,就是根據提供的信息查表,類似於查字典,所以叫做字典法):
int day_count_of_month[2][12] = {
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
1: #include <stdio.h>
2:
3: #define IS_LEAP(X) (((X) % 400 == 0 || (X) % 100 != 0 && (X) % 4 == 0) ? 1 : 0)
4: int day_count_of_month[2][12] = {{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
5: {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} };
6: /* 計算year年的第n天是幾月幾號 */
7: void convert_to_date(int year, int n)
8: {
9: int leap = IS_LEAP(year);
10: if(year < 1970 || year > 5000 || n <= 0 || n > 365 + leap) {
11: printf("bad year\n");
12: }
13:
14: int i = 0;
15: for(; n > day_count_of_month[leap][i]; ++i) {
16: n -= day_count_of_month[leap][i];
17: }
18:
19: printf("year : %d, month : %d, day : %d\n", year, i + 1, n);
20: }
21:
22: int main()
23: {
24: convert_to_date(2012, 230);
25: }
上面這段代碼解決了第一個問題,第二個問題可以轉換為類似第一個問題的問題:
先計算給定的某年某月某日到給定已知這天的總天數s=>s%7=>利用模運算結果來推算所求日期的星期情況即可。
本文許可自由轉載,轉載請注明出處!