很簡單的例子:
已知有五個數,求前四個數與第五個數分 別相乘后的最大當數。給出兩個算法分別如下:
max1(int a,b,c,d,e) max2(int a,b,c,d,e) { int x ; { int x a=a*e; if (a>b) b=b*e; x=a; c=c*e; else d=d*e; x=b; if( a>b) if (c>x) x=a; x=c; else if (d>x) x=b; x=d; if (c>x) x=x*e; x=c; print(x); if (d>x) } x=d; print(x); }
以上兩個算法基於的數學模型是不同的,一個算法先積再求最大值,另一個算法先求最大值再求積,求從上表可以看出,后一個算法的效率明顯要高於前一個算法。
數學建模就是把現實世界中的實際問題加以提煉,抽象為數學模型,求出模型的解,驗證模型的合理性,並用該數學模型所提供的解答來解釋現實問題,我們把數學知識的這一應用過程稱為數學建模。
數學建模的基本方法
從分析問題的幾種簡單的、特殊的情況中,發現一般規律或作出某種猜想,從而找到解決問題的途徑。這種研究問題方法叫做歸納法。即歸納法是從簡單到復雜,由個別到一般的一種研究方法。
楊輝三角形的應用
【例】求n次二項式各項的系數:已知二項式的展 開式為:
問題分析:若只用的數學組合數學的知識,直接建模
k=0,1,2,3……n。
用這個公式去計算,n+1個系數,即使你考慮到了前后系數之間的數值關系:算法中也要有大量的乘法和除法運算,效率是很低的。
數學知識是各階多項式的系數呈楊輝三角形的規律
(a+b)0 1
(a+b)1 1 1
(a+b)2 1 2 1
(a+b)3 1 3 3 1
(a+b)4 1 4 6 4 1
(a+b)5 ……
則求n次二項式的系數的數學模型就是求n階楊輝三角形。
算法設計要點: 除了首尾兩項系數為1外,當n>1時,(a+b)n的中間各項系數是(a+b)n-1的相應兩項系數之和,如果把(a+b)n的n+1的系數列為數組c,則除了c(1)、c(n+1)恆為1外,設(a+b)n的系數為c(i),(a+b)n-1 的系數設為c’(i)。則有:
c(i)=c’(i)+c’(i-1)
而當n=1時,只有兩個系數 c(1) 和 c(2) (值都為1)。不難看出,對任何n, (a+b)n的二項式系數可由(a+b)n-1的系數求得,直到n=1時,兩個系數有確定值,故可寫成遞歸子算法。
coeff(int a[ ],int n) { if (n==1) { a[1]=1; a[2]=1;} else { coeff(a,n-1); a[n+1]=1; for (i=n;i>=2;i- -) a[i]=a[i]+a[i-1]; a[1]=1; } } main( ) {int a[100],i,n; input( n ); for(i=1;i<=n;i=i+1) input(a[i]); coeff(a,n); for(i=1;i<=n;i=i+1) print(a[i]); }
最大公約數的應用
【例】數組中有n個數據,要將它們順序循環向后移k位,即前面的元素向后移k位,后面的元素則循環向前移k位,例:1、2、3、4、5循環移3位后為:3、4、5、1、2。考慮到n會很大,不允許用2*n以上個空間來完成此題。
問題分析1:若題目中沒有關於存儲空間的限制,我們可以方便地開辟兩個一維數組,一個存儲原始數據,另一個存儲移動后的數據。
main( ) {int a[100],b[100],i,n,k; input(n,k ); for(i=0;i<n;i=i+1) input(a[i]); for(i=0;i<n;i=i+1) b[(k+i) mod n]=a[i]; for(i=0;i<n;i=i+1) print(b[i]); }
這個算法的時間效率是n次移動(賦值)。
有可能k>n,這樣移動會出現重復操作,可以在輸入數據后,執行k = k mod n; 以保證不出現重復移動的問題。這個算法的移動(賦值)次數為k*n。當n較大時不是一個好的算法。
問題分析2:
● 將最后一個存儲空間的數據,存儲在臨時存儲空間中;
● 其余數據逐個向后移動一位。
這樣操作共k次就能完成問題的要求。
算法2如下:
main( ) {int a[100],b[100],i,j,n,k,temp; input(n,k); for(i=0;i<n;i=i+1) input(a[i]); for(i=0;i<k;i=i+1) {temp=a[n-1]; for(j=n-1;j>0;j=j-1) a[j]=a[j-1]; a[0]= temp ; } for(i=0;i<n;i=i+1) print(b[i]); }
問題分析3 利用一個臨時存儲空間,把每一個數據一次移動到位:
1) 一組循環移動的情況:
通過計算我們可以確定某個元素移動后的具體位置,如n=5, k=3時:
0、1、2、3、4循環移3位后為2、3、4、0、1。
可通過計算得出0移到3的位置,3移到1的位置,1移到4的位置,4移到2的位置,2移到0的位置;一組移動(0-3-1-4-2-0)正好將全部數據按要求進行了移動。這樣只需要一個輔助變量,每個數據只需一次移動就可完成整個移動過程。
如果算法就這樣按一組移動去解決問題,就錯了。因為還有其它情況。
2)多組循環移動的情況:算法不能按一組移動去解決問題。
看下一個例子,當n=6,k=3時,1、2、3、4、5、6經移動 的結果是4、5、6、1、2、3. 並不象上一個例子,一組循環 移動沒有將全部數據移動到位。還需要(2-5-2)(3-6-3)兩組移動,共要進行三組循環移動(1-4,2-5,3-6)才能將全部數據操作完畢。
循環移動的組數,與k、n的是怎么樣的關系呢?
我們再看一個例子,當N=6,K=2時, 1、2、3、4、5、6經移動的結果是5、6、1、2、3、4. 1移到3的位置,3移到5的位置,5移到1的位置,一組移動完成了3個數據的移動,接下來,還有一組2-4-6-2。共進行二組循環移動,就能將全部數據移動完畢。
數學模型:循環移動的組數等於N與K的最大公約數。
算法設計:
1)編寫函數,完成求n , k最大公約數m的功能
2)進行m組循環移動。
3)每組移動,和算法2一樣,通過計算可以確定某個
元素移動后的具體位置。在移動之前,用臨時變量
存儲需要被覆蓋的數據。
算法3如下:
main( ) {int a[100],b0,b1,i,j,n,k,m,tt; print(“input the number of data”); input(n); print(“input the distant of moving”); input(k ); for(i=0;i<n;i=i+1) input(a[i]); m=ff(n,k); for(j=0;j<m;j=j+1) {b0= a[j]; tt=j; for(i=0;i<n/m;i=i+1) {tt=(tt+k) mod n;b1=a[tt]; a[tt]= b0; b0=b1;} } for(i=0;i<n;i=i+1) print(a[i]); } ff ( int a ,int b) { t = 1; for ( i = 2;i<=a and i<=b;i++) while (a mod i=0 and b mod i=0 ) { t=t * i ; a=a / i ; b= b / i ; } return(t) ; }
算法分析:每組循環移動都是從前向后進行的,一次移動需要兩次賦值,總體大約需要賦值2n次。
能不能繼續提高效率為賦值n次呢?請考慮改進每組循環移動的方式為從后開始移動,以提高運行效率。以例子說明:
n=6,k=2時,第一組循環移動0-2-4,在算法3中是這樣實現的:
a[0]=>b0,
a[2]=>b1, b0(a[0])=>a[2],b1=> b0;
a[4]=>b1, b0(a[2])=> a[4],b1=> b0;
a[0]=>b1, b0(a[4])=> a[0] ,b1=> b0;
改進后(和算法2類似):
a[4]=>b ;a[2]=>a[4],a[0]=>a[2], b=>a[0]。
將每組最后一個數據元素存儲在輔助存儲空間,以后就可以安全地覆蓋后面的數組元素了(注意覆蓋順序)。這樣,一組循環移動只需一次將數據存入輔助存儲空間,其后一次移動只需一次賦值,全部工作大約需要賦值n次就可完成。
公倍數的應用
【例】編程完成下面給“余”猜數的游戲:
你心里先想好一個1~100之間的整數x,將它分別除以3、5和7並得到三個余數。你把這三個余數告訴計算機,計算機能馬上猜出你心中的這個數。游戲過程如下:
please think of a number between 1 and 100
your number divided by 3 has a remainder of? 1
your number divided by 5 has a remainder of? 0
your number divided by 7 has a remainder of? 5
let me think a moment…
your number was 40
問題分析:算法的關鍵是:找出余數與求解數之間的關系,
也就是建立問題的數學模型。
數學模型:
1)不難理解當s=u+3*v+3*w時,s除以3的余數與u除以3的
余數是一樣的。
2)對s=cu+3*v+3*w,當c除以3余數為1的數時, s除以3的
余數與u除以3的余數也是一樣的。證明如下:
c 除以 3余數為1,記c=3*k+1,則s=u+3*k*u+3*v+3*w,由1)的結論,上述結論正確。
記a,b,c分別為所猜數據d除以3,5,7后的余數,則
d=70*a+21*b+15*c。
為問題的數學模型,其中70稱作a的系數,21稱作b的系數,15稱作c的系數。
由以上數學常識,a、b、c的系數必須滿足:
1) b、c的系數能被3整除,且a的系數被3整除余1;這樣d除以3的余數與a相同。
2) a、c的系數能被5整除,且b的系數被5整除余1;這樣d除以5的余數與b相同。
3) a、b的系數能被7整除,且c的系數被7整除余1;這樣d除以7的余數與c相同。
由此可見:
c的系數是3和5的最公倍數且被7整除余1,正好是15;
a的系數是7和5的最公倍數且被3整除余1,最小只能是70;
b的系數是7和3的最公倍數且被5整除余1,正好是21。
算法設計:用以上模型求解的數d,可能比100大,這時只要減去3,5,7的最小公倍數就是問題的解了。
main( ) { int a,b,c,d; print( “please think of a number between 1 and 100.”); print( “your number divded by 3 has a remainker of”); input(a); print( “your number divded by 5 has a remainker of”); input(b); print( “your number divded by 7 has a remainker of”); input(c); print( “let me think a moment…”); for (i=1 ,i<1500; i=i+1); //消耗時間,讓做題者思考 d=70*a+21*b+15*c; while (d>105) d=d-105; print( “your number was ”, d); }
裴波那契數列應用
【例】樓梯上有n階台階,上樓可以一步上1階,也可以一步上2階,編寫算法計算共有多少種不同的上樓梯方法。
數學模型:此問題如果按照習慣,從前向后思考,也就是從第一階開始,考慮怎么樣走到第二階、第三階、第四階……,則很難找出問題的規律;而反過來先思考“到第n階有哪幾種情況?”,答案就簡單了,只有兩種情況:
1) 從第n-1階到第n階;
2) 從第n-2階到第n階。
記n階台階的走法數為f(n),則
f(n)= 1 n=1
f(n)=2 n=2
f(n-1)+f(n-2) n>2
算法設計:算法可以用遞歸或循環完成。下面是問題的遞歸算法。
算法如下:
main( ) { int n:; print('n='); input(n); print('f(',n,')=',f(n)); } f(int n) { if (n = 1 ) return(1); if (n = 2 ) return(2); else return(f(x-1)+f(x-2)); }
遞推關系求解方程
【例】核反應堆中有α和β兩種粒子,每秒鍾內一個α粒子可以反應產生3個β粒子,而一個β粒子可以反應產生1個α粒子和2個β粒子。若在t=0時刻的反應堆中只有一個α粒子,求在t時刻的反應堆中α粒子和β粒子數。
數學模型1:本題中共涉及兩個變量,設在i時刻α粒子數為ni,β粒子數為mi,則有:n0=1,m0=0,ni=mi—1,mi=3ni—1+2mi—1
算法設計1:本題方便轉化為求數列N和M的第t項,可用遞推的方法求得nt和mt,此模型的算法如下:
main() { int n[100],m[100],t,i; input(t); n[0]=1; //初始化操作 m[0]=0; for (i=1;i<=t;i++) //進行t次遞推 { n[i]=m[i-1]; m[i]=3 * n[i-1] + 2 * m[i-1]; } print(n[t]); //輸出結果 print(m[t]); }
算法分析1:此模型的空間需求較小,時間復雜度為O(n),但隨着n的增大,所需時間越來越大。
數學模型2:設在t時刻的α粒子數為f(t),β粒子數為g(t),依題可知:
g(t)=3f(t -1)+2g(t -1) (1)
f(t)=g(t -1) (2)
g(0)=0,f(0)=1
下面求解這個遞歸函數的非遞歸形式
由(2)得f(t -1)=g(t-2) (3)
將(3)代入(1)得
g(t)=3g(t -2)+2g(t-1) (t≥2) (4)
g(0)=0,g(1)=3
(4)式的特征根方程為:
x2—2x—3=0
其特征根為x1=3,x2= -1
所以該式的遞推關系的通解為
g(t)=C1·3t+C2·( -1)t
代入初值g(0)=0,g(1)=3得
C1+C2=0
3C1—C2=3
解此方程組
所以該遞推關系的解為
∴g(t)=
即
由數學模型2,設計算法2如下
main() { int t,i; input(t); n=int(exp(t*ln(3))); m=int(exp((t+1)*ln(3))); if (t mod 2=1) { n=n-3; m=m+3;} else { n=n+3; m=m-3;} n=int(n/4); // 4|n m=int(m/4); // 4|m print(n); print(m); }
算法分析:在數學模型2中,運用數學的方法建立了遞歸函數並轉化為非遞歸函數。它的優點是算法的復雜性與問題的規模無關。針對某一具體數據,問題的規模對時間的影響微乎其微。