第六章Maple程序設計_第1頁
第六章Maple程序設計_第2頁
第六章Maple程序設計_第3頁
第六章Maple程序設計_第4頁
第六章Maple程序設計_第5頁
已閱讀5頁,還剩16頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領

文檔簡介

/第六章Maple程序設計教學目的:學習并駕馭計算機代數(shù)系統(tǒng)Maple下的算法設計和程序設計原理和方法,包括基本程序結構、子程序求值、程序的嵌套以及程序的調(diào)試。教學要求:駕馭算法設計和程序設計原理和基本方法。重點內(nèi)容:算法設計,程序設計。難點內(nèi)容:算法設計,程序設計。前面,我們運用的是Maple的交互式叮囑環(huán)境.所謂交互式叮囑環(huán)境,就是一次輸入一條或幾條叮囑,然后按回車,這些叮囑就被執(zhí)行了,執(zhí)行的結果顯示在同一個可執(zhí)行塊中.對于大多數(shù)用戶來說,利用交互式叮囑環(huán)境解決問題已經(jīng)足夠了,但假如要解決一系列同一類型的問題或者希望利用Maple編寫須要的解決特定問題的函數(shù)和程序,以期更加充分地利用Maple的強大功能,提高大規(guī)模問題的計算效率,進行確定的程序設計是必要的.程序設計主要包括兩個方面:行為特性的設計和結構特性的設計.所謂行為特性的設計,通常是指將解決問題的過程的每一個微小環(huán)節(jié)精確地加以定義,并且還應當將全部的解題過程用某種工具完整地描述出來,這一過程也稱為算法的設計.而結構特性設計是指為問題的解決確定合適的數(shù)據(jù)結構.幸運的是,Maple自身供應了一套編程工具,即Maple語言.Maple語言事實上是由Maple各種叮囑以及一些簡潔的過程限制語句組成的.1編程基礎1.1算子所謂算子,是從一個抽象空間到另一個抽象空間的函數(shù).在數(shù)學上算子的含義通常是函數(shù)到函數(shù)的映射.在Maple中,算子常用“箭頭”記號定義(也稱箭頭操作符):>f:=x->a*x*exp(x);>g:=(x,y)->a*x*y*exp(x^2+y^2);另外,函數(shù)unapply也可以從表達式建立算子:>unapply(x^2+1,x);>unapply(x^2+y^2,x,y);當我們依次把算子f作用到參數(shù)0,a,x^2+a時即可得平常意義上的函數(shù)值:>f:=t->t*sin(t);>f(0);>f(a);>f(x^2+a);上述結果是函數(shù)作用的例子.而最終一個結果事實上是算子f和算子g:=t->t^2+a復合后再作用到參數(shù)x的結果.從數(shù)學上講,作用和復合是不同的,它們產(chǎn)生的結果是有區(qū)分的,但在運用它們時,兩者還是有些重疊的.在Maple中,可以依靠于語法把它們區(qū)分開:當復合兩個算子時,結果仍是算子,兩個算子的定義域必需是相容的;當把一個算子作用于一個參數(shù)(參數(shù)必需在算子的定義域中)時,結果是一個表達式;在Maple中,函數(shù)作用的語法是運用括號(),如函數(shù)f作用到參數(shù)u寫作f(u).而復合算子的符號是@,多重復合時運用符號@@.通過進一步的例子可以清楚區(qū)分作用和復合的功能:f和g復合的結果是算子,而把這個算子作用到參數(shù)x得到表達式f(g(x)).例如,,則是一個算子,而是一個表達式,因為x是一個實數(shù).試比較下述兩例:>D(g@f);>D(g*h);另外一個應引起留意的問題是算子(函數(shù))和表達式的異同,在第一章中曾探討過函數(shù)和表達式的區(qū)分,這里再通過幾個例子說明其中的微妙差異:>f1:=x^2+1;>f2:=y^2+1;>f3:=f1+f2;再看下面的例子:>g1:=x->x^2+1;>g2:=y->y^2+1;>g3:=g1+g2;和前面例子不同的是,兩個算子(函數(shù))g1,g2相加的結果照舊是函數(shù)名g3,出現(xiàn)這個問題的主要緣由是g1和g2分別為x,y的函數(shù),Maple認為它們的定義域不相容.要得到和前例的結果,只需稍作改動:>g3:=g1(x)+g2(y);下面的例子想說明生成Maple函數(shù)的兩種方式“箭頭操作符”及“unapply”之間微妙的差異:>restart:a:=1:b:=2:c:=3:>a*x^2+b*x+c;>f:=unapply(a*x^2+b*x+c,x);>g:=x->a*x^2+b*x+c;由此可見,f中的a,b,c已經(jīng)作了代換,而g中則顯含a,b,c。再看下面試驗:>f(x);g(x);f和g兩者相同,再對其微分:>D(f);D(g);再變更常數(shù)c的值,視察f和g的變更:>c:=15;>f(x);g(x);由此可見,在利用Maple進行函數(shù)探討時,對同一問題應當用不同方法加以校驗,而這一切的支撐是數(shù)學基礎!1.2編程初體驗利用算子可以生成最簡潔的函數(shù)—單個語句的函數(shù),但嚴格意義上講它并非程序設計,它所生成的數(shù)據(jù)對象是子程序.所謂子程序,簡潔地說,就是一組預先編好的函數(shù)叮囑,我們由下面的簡潔程序來看看Maple程序的結構:>plus:=proc(x,y)x+y;end;這個程序只有2個參數(shù),在程序內(nèi)部它的名稱是x,y,這是Maple最簡潔的程序結構,僅僅在proc()和end中間加上在計算中須要的一條或者多條叮囑即可,Maple會把最終一個語句的結果作為整個子程序的返回結果,這一點須要引起留意.再看下例:>P:=proc(x,y)x-y;x*y;x+y;end:>P(3,4);明顯,盡管程序P有三條計算叮囑,但返回的只是最終一個語句x+y;的結果.要想輸出全部的計算結果,須要在程序中增加print語句:>P:=proc(x,y)print(x-y);print(x*y);print(x+y);end:>P(3,4);再看下面幾個例子:>forifrom2to6doexpand((x+y)^i);od;>F:=proc(n::integer)ifnmod12=0thentrueelsefalsefiend:>F(123^123),F(1234567890^9);從上面幾個簡潔的例子可以看出Maple子程序主要包含以下一些內(nèi)容:把定義的子程序賦值給程序名procname,以后就可以用子程序名procname來調(diào)用程序;子程序一律以proc()開頭,括號里是程序的輸入?yún)?shù),假如括號中什么都沒有,表示這個子程序沒有任何輸入?yún)?shù);子程序中的每一個語句都用分號(或冒號)分開(這一點不是主要的,程序設計時,在可能的時候—過程當中的最終一個語句、for-循環(huán)、if語句中的最終一個語句省略終結標點也是允許的,這并不是為了懶散,而是因為在終結語句后面插入一個語句產(chǎn)生的影響要比僅僅執(zhí)行一個新語句產(chǎn)生的影響大);在定義完子程序之后,Maple會顯示它對該子程序的說明(除非在end后用冒號結束),它的說明和你的定義是等價的,但形式上不愿定完全相同;Maple會自動地把除了參數(shù)以外的變量都作為局部變量(localvariable),這就是說,它們僅僅在這個子程序的定義中有效,和子程序以外的任何同名變量無關.在定義了一個子程序以后,執(zhí)行它的方法和執(zhí)行任何Maple系統(tǒng)子程序一樣—程序名再加上一對圓括號(),括號中包含要調(diào)用的參數(shù),假如子程序沒有參數(shù),括號也是不能省略的.除了上面給出的程序設計方法外,在Maple中還可以干脆由“->”(箭頭)生成程序,如下例:>f:=x->ifx>0thenxelse-xfi;>f(-5),f(5);甚至于程序名也可以省略,這種狀況通常會在運用函數(shù)map時遇到:>map(x->ifx>0thenxelse-xfi,[-4,-3,-2,0,1]);假如須要察看一個已經(jīng)定義好的子程序的過程,用eval叮囑,查看Maple中源程序(如factor函數(shù))運用下述組合叮囑:interface(verboseproc=2);print(factor);1.3局部變量和全局變量Maple中的全局變量,是指那些在交互式叮囑環(huán)境中定義和運用的變量,前面所運用的變量幾乎都屬于全局變量.而在編寫子程序時,須要定義一些只在子程序內(nèi)部運用的變量,稱其為局部變量.當Maple執(zhí)行子程序時,全部和局部變量同名的全局變量都保持不變,而不管在子程序中給局部變量賜予了何值.假如要把局部變量定義為全局變量,須要用關鍵詞global在程序最起先加以聲明,而局部變量則用local聲明,雖然這是不必要的,但在程序設計時,聲明變量是有確定好處的.下面通過實例演示局部變量和全局變量的不同.為了更清楚地視察子程序對全局變量的影響,在子程序外先設定一個變量a的值:>a:=1;>f:=proc()locala;a:=12345678/4321;evalf(a/2);end;>f();>a;>g:=proc()globala;a:=12345678/4321;evalf(a/2);end;>g();>a;明顯,在前一個程序中,由于在子程序外已經(jīng)賦值給a,a是全局變量,它的值不受子程序中同名局部變量的影響;而在后一個子程序中,由于重新把a定義為全局變量,所以子程序外的a隨著子程序中的a值的變更而變更.子程序中的輸入?yún)?shù),它既不是全局的,也不是局部的.在子程序內(nèi)部,它是形式參數(shù),也就是說,它的詳細取值尚未被確定,它在程序調(diào)用時會被替換成真正的參數(shù)值.而在子程序外部,它們僅僅表示子程序接受的參數(shù)的多少,而對于詳細的參數(shù)值沒有關系.1.4變量nargs,args和procname在全部程序中都有三個有用的變量:nargs,args和procname.前兩個給出關于調(diào)用參量的信息:nargs變量是調(diào)用的實際參量的個數(shù),args變量是包含參量的表達式序列,args的子序列通過范圍或數(shù)字的參量選取.例如,第i個參量被調(diào)用的格式為:args[i].nargs,args變量通常在含有可選擇參量的程序中運用.下面看一個例子:>p:=proc()locali;RETURN(nargs,[seq(i^3,i=args)])endproc:>p(1,2,3,4,5,6,7,8,9,10);該程序利用Maple函數(shù)RETURN返回了輸入?yún)⒘康膫€數(shù)以及參量序列的立方列表,RETURN函數(shù)運用時必需在其后加圓括號,即使無結果返回時也得如此。下面是一個關于求隨意序列最大值的困難程序:>maximum:=proc()localr,i;r:=args[1];forifrom2tonargsdoifargs[i]>rthenr:=args[i]endifenddo;r;endproc:>maximum(12^4,5^7,69^3,10^4+4.9*1000);假如變量procname(程序被調(diào)用的名字)存在的話,它可以用來干脆訪問該程序,通常用procname(args)完成調(diào)用:>f:=proc(a)ifa>0thenRETURN(a^(1/2))elseRETURN('procname(args)')endifendproc:>f(100-3^6),f(10^2+90-9*4); 2基本程序結構全部的高級程序設計語言都具有程序結構,因為為了完成一項困難的任務,僅僅依據(jù)語句依次依次執(zhí)行是遠遠不夠的,更須要程序在特定的地方能夠跳轉、分叉、循環(huán),……,和此同時,程序結構就產(chǎn)生了.2.1分支結構(條件語句)所謂分支結構,是指程序在執(zhí)行時,依據(jù)不同的條件,分別執(zhí)行兩個或多個不同的程序塊,所以常常稱為條件語句.if條件結構的語法為:if邏輯表達式1(條件1)then程序塊1elif邏輯表達式2(條件2)then程序塊2else程序塊3fi;其中,fi是條件語句的結束標記,也可以寫作endif.下面是一個推斷兩個數(shù)中較大者的例子:>bigger:=proc(a,b)ifa>=bthenaelsebfiend;再如下例:>ABS:=proc(x)ifx<0then-x

elsex;fi;end;明顯,這個確定值函數(shù)的簡潔程序,對于隨意實數(shù)范圍內(nèi)的數(shù)值型變量計算都是沒有問題的,但對于非數(shù)值型的參數(shù)則無能為力.為此,我們須要在程序中添加一條推斷參數(shù)是否為數(shù)值型變量的語句:>ABS:=proc(x)iftype(x,numeric)thenifx<0then-xelsex;fi;else'ABS'(x);fi;end:這里,我們用到一個if語句的嵌套,也就是一個if語句處于另一個if語句當中.這樣的結構可以用來推斷困難的條件.我們還可以用多重嵌套的條件結構來構造更為困難的函數(shù),例如下面的分段函數(shù):>HAT:=proc(x)iftype(x,numeric)thenifx<=0then0;elseifx<=1thenx;else0;fi;fi;else'HAT'(x);fi;end;盡管在上面的程序中我們用了不同的縮進來表示不同的層次關系,這段程序還是很難懂.對于這種多分支結構,可以用另一種方式書寫,只需在其次層的if語句中運用elif,這樣就可以把多個分支形式上寫在同一個層次中,以便于閱讀.>HAT:=proc(x)iftype(x,numeric)thenifx<=0then0;elifx<=1thenx;else0;fi;else'HAT'(x);fi;end;和許多高級語言一樣,這種多重分支結構理論上可以多到隨意多重.再來看看前面的確定值函數(shù)的程序,似乎是完備的,但是,對于乘積的確定值則沒有方法,下面用map叮囑來修正,留意其中的type(…,`*`)是用來推斷表達式是否為乘積,進而對其進行化簡.>ABS:=proc(x)iftype(x,numeric)thenifx<0then-xelsexfi;eliftype(x,`*`)thenmap(ABS,x);else'ABS'(x);fi;end:>ABS(-1/2*b);分段函數(shù)是一類重要的函數(shù),條件語句在定義分段函數(shù)時具有明顯的優(yōu)越性,下面學習幾個實例:(1)>f:=proc(x)ifx<0thenx^2+1elsesin(Pi*x)fi;end;(2)>g:=proc(x)ifx<=0thenx^2+xelseifx<3*Pithensin(x)elsex^2-6*x*Pi+9*Pi^2-x+3*Pififiend;2.2for循環(huán)在程序設計中,常常須要把相同或者類似的語句連續(xù)執(zhí)行多次,此時,通過for循環(huán)結構可以更便捷地編寫程序.試看下面幾個例子:①求1至5自然數(shù)的和:>total:=0:forifrom1to5dototal:=total+i:od;②列示2,4,6,8,10及其平方數(shù)、立方數(shù):>forifrom2to10by2do'i'=i,'i^2'=2^i,'i^3'=i^3;od;③列示第100到第108個素數(shù)(第1個素數(shù)是2):>forjfrom100to108doprime[j]=ithprime(j);od;④列示5到10的階乘及其因數(shù)分解:>forninseq(i!,i=5..10)don=ifactor(n);od;⑤一個困難的冪指函數(shù)生成:>f:=proc(x,n)locali,t;t:=1;forifrom1tondot:=x^(t^x);od;t;end:>f(x,10);通過上述例子可以看出,Maple的for循環(huán)結構很接近于英語語法.在上面例子中,i稱為循環(huán)變量,用于循環(huán)計數(shù),do后面的部分,稱為循環(huán)體,就是須要反復執(zhí)行的語句,可以是一條語句,也可以是多條語句.在循環(huán)體中,也可以引用循環(huán)變量.循環(huán)體的結束標記,也就是整個for循環(huán)結構的結束標記,是字母“od”(也可以寫作enddo).for循環(huán)結構的語法可總結為:for循環(huán)變量from初始值to終止值(by步長)do循環(huán)體od在for循環(huán)結構中,初始值和終了值必需是數(shù)值型的,而且必需是實數(shù),假如初始值是1,可以省略不寫,而步長用by引出.對于上面程序中的重復循環(huán)似乎可有可無,但實際科學計算中,重復次數(shù)往往成千上萬,或者重復的次數(shù)無法確定,循環(huán)就必不行少了.下面再看一個好用程序—從1到n的自然數(shù)的求和程序:>SUM:=proc(n)locali,total;total:=0;forifrom1tondototal:=total+i;od;end;>SUM(1000);該程序僅僅具有練習例舉的意義,因為在Maple中像這樣一個問題只須要一個簡潔語句就可以完成:>Sum(n,n=1..1000)=sum(n,n=1..1000);更一般地,對于確定的求和可用add叮囑(該叮囑無惰性函數(shù)形式):>add(n,n=1..1000);再看下面一個動畫制作例子(圖略去):>forifrom-20to30dop||i:=plots[implicitplot](x^3+y^3-5*x*y=1-i/8,x=-3..3,y=-3..3,numpoints=800,tickmarks=[2,2])od:>plots[display](p||(-20..30),insequence=true);2.3while循環(huán)for循環(huán)在那些已知循環(huán)次數(shù),或者循環(huán)次數(shù)可以用簡潔表達式計算的狀況下比較適用.但有時循環(huán)次數(shù)并不能簡潔地給出,我們要通過計算,推斷一個條件來確定是否接著循環(huán),這時,可以運用while循環(huán).while循環(huán)標準結構為:while條件表達式do循環(huán)體odMaple首先推斷條件是否成立,假如成立,就一遍遍地執(zhí)行循環(huán)體,直到條件不成立為止.下面看一個簡潔的程序,是用輾轉相除法計算兩個自然數(shù)的最大公約數(shù)(Euclidean算法).>GCD:=proc(a::posint,b::posint)localp,q,r;p:=max(a,b);q:=min(a,b);r:=irem(p,q);whiler<>0dop:=q;q:=r;r:=irem(p,q);od;q;end:>GCD(123456789,987654321);在上面程序中的參數(shù)a、b后面的雙冒號“::”指定了輸入的參數(shù)類型.若類型不匹配時輸出錯誤信息.再看下面一個擴展Euclidean算法的例子:>mygcdex:=proc(a::nonnegint,b::nonnegint,x::name,y::name)locala1,a2,a3,x1,x2,x3,y1,y2,y3,q;a1:=a;a2:=b;x1:=1;y1:=0;x2:=0;y2:=1;while(a2<>0)doa3:=a1moda2;q:=floor(a1/a2);x3:=x1-q*x2;y3:=y1-q*y2;a1:=a2;a2:=a3;x1:=x2;x2:=x3;y1:=y2;y2:=y3;od;x:=x1;y:=y1;RETURN(a1)end:>mygcdex(2^10,6^50,'x','y');2.4遞歸子程序正如在一個子程序中我們可以調(diào)用其他的子程序一樣(比如系統(tǒng)內(nèi)部函數(shù),或者已經(jīng)定義好的子程序),一個子程序也可以在它的內(nèi)部調(diào)用它自己,這樣的子程序我們稱為遞歸子程序.關于遞歸算法及其設計將在下節(jié)詳述.在數(shù)學中,用遞歸方式定義的例子許多,如Fibonacci數(shù)列:下面我們在Maple中利用遞歸子程序求Fibonacci數(shù)列的第n項:>F:=proc(n::nonnegint)::listifn<=1thenn;elseF(n-1)+F(n-2);fi;end:>seq(F(i),i=0..19);>F(20);但是,應用上述程序計算F(2000)時則不能進行,緣由是要計算F(2000),就先得計算F(1999)、F(1998)、……,無限制地遞歸會耗盡系統(tǒng)的堆??臻g而導致錯誤.由數(shù)據(jù)結構有關理論知道,遞歸程序理論上都可以用循環(huán)實現(xiàn),比如我們重寫上面的程序如下:>F:=proc(n::nonnegint)localtemp,fnew,fold,i;ifn<=1thenn;elsefold:=0;fnew:=1;forifrom2tondotemp:=fnew+fold;fold:=fnew;fnew:=temp;od;fi;end:>F(2000):>time(F(2000));利用循環(huán),程序不像前面那么易懂了,但同時帶來的好處也是不行忽視的,循環(huán)結構不僅不會受到堆棧的限制,而且計算速度得到了很大的提高.我們知道,Maple子程序默認狀況下把最終一條語句的結果作為子程序的結果返回.我們可以用RETURN(或return)叮囑來顯式地返回結果,比如前面的遞歸結果可等價地寫成:>F:=proc(n::nonnegint)optionremember;ifn<=1thenRETURN(n);fi;F(n-1)+F(n-2);end:程序進入n<=1的分支中,執(zhí)行到RETURN叮囑就跳出程序,而不會接著執(zhí)行后面的語句了.另外,運用RETURN叮囑在確定程度上可以增加程序的可讀性.在其次章中曾提到Maple的自動求導功能,下面通過實例說明。第一個例子是關于分段函數(shù)求導問題:>F:=x->ifx>0thensin(x)elsearctan(x)fi;>Fp:=D(F);將分段函數(shù)及其導數(shù)的曲線繪制在同一個坐標系中,實線為F,虛線為Fp:>plot({F,Fp},-3*Pi..3*Pi,linestyle=[1,4],color=black);其次個例子試圖說明Maple能夠完成對多個函數(shù)的自動求導:>f:=proc(x)locals,t;s:=ln(x);t:=x^2;s*t+3*t;end;>fp:=D(f);程序fp是怎樣構造的呢?對比f和fp的源代碼可以幫助我們了解Maple自動求導機理:在fp的源代碼中,每一條賦值語句V:=f(v1,v2,…vn)之前是Vx:=fp(v1,v2,…vn),此處vi是局部變量或形式參數(shù),fp(v1,v2,…vn)是對f(v1,v2,…vn)在形式上的微分,用該賦值語句的的導數(shù)取代最終一條語句或返回值。這種方法被稱為向前自動微分法。在Maple內(nèi)部對這種方法有一些限制:不能有遞歸程序,不能有記憶選項,不能對全局變量賦值,只能有常數(shù)的循環(huán)變量,等。下面一個例子說明自動求導在計算時間和存儲空間上的優(yōu)越性,這是一個利用遞歸定義的的冪函數(shù)的自動求導問題:>f:=proc(x,n)locali,t;t:=1;foritondot:=x^tod;tend;>f1_3:=D[1$3](f):#求f對x的三階導數(shù)>setbytes:=kernelopts(bytesused):#運用寄存器內(nèi)存量>settime:=time():#起先計時>f1_3(1.1,22);>cpu_time:=(time()-settime)*seconds;#計算時間>memory_used:=evalf((kernelopts(bytesused)-setbytes)/1024*kbytes,5);#存儲空間大小再利用符號微分重復上述步驟:>f22:=unapply(f(x,22),x):>setbytes:=kernelopts(bytesused):>settime():>(D@@3)(f22)(1.1);>cpu_time:=(time()-settime)*seconds;>memory_used:=evalf((kernelopts(bytesused)-setbytes)/1024*kbytes,5);明顯Maple自動求導在計算時間和存儲空間上具有明顯的優(yōu)越性。奇異利用自動微分解決較困難的函數(shù)求導問題有特殊現(xiàn)實的意義。3算法及其設計算法(algorithm)這一術語是對9世紀巴格達數(shù)學家al-Khowarizmi(阿爾-花拉子米)這一名字的訛用,他寫的書《Kitabal-jabrw’almuqabalah》(代數(shù)學)演化成為現(xiàn)在中學的代數(shù)教科書,al-Khowarizmi強調(diào)求解問題的有條理的步驟.最初,訛用的algorism表示運用十進制做算術運算的規(guī)則.18世紀時algorism演化為algorithm.隨著計算機的發(fā)展,算法的概念有了更廣的含義.所謂算法,簡潔地說,是一組嚴謹?shù)囟x運算依次的規(guī)則,并且每一個規(guī)則都是有效的且是明確的,此依次將在有限的次數(shù)下終止.作為一個算法,應具有四個特征:1)有效性(effectiveness):即算法中的每一個步驟必需是能夠實現(xiàn)的,算法執(zhí)行的結果要能達到預期的目的;2)確定性(definiteness):算法中的每一個步驟都必需是有明確定義的,不允許有模棱兩可的說明,也不允許有多義性;3)有窮性(finiteness):算法必需在有限個步驟之后終止,且還應具有合理的執(zhí)行時間;4)通用性(currency):算法過程應適用于要求形式的全部問題,而不只是用于一組特定的輸入值.對于一個問題,假如可以通過一個計算機程序,在有限的存儲空間內(nèi)運行有限長的時間而得到正確的結果,則稱該問題是算法可解的.但算法不等于程序,也不等于計算方法.程序可以作為算法的一種描述,但程序通常還需考慮許多和方法和分析無關的微小環(huán)節(jié)問題,這是因為在編寫程序時要受到計算機系統(tǒng)運行環(huán)境的限制.通常,程序設計的質(zhì)量不行能優(yōu)于算法的設計.算法的設計方法許多,下面介紹幾種常用的算法設計方法.3.1枚舉法枚舉法(也稱列舉法,窮舉法)的基本思想是依據(jù)提出的問題,列舉全部可能狀況,并檢驗那些滿意問題中提出的條件,那些不滿意.因此,枚舉法常用于“是否存在”或“有多少種可能”等類型的問題.枚舉法是重要而好用的算法之一.枚舉法的特點是算法比較簡潔,但當列舉的可能狀況較多時,執(zhí)行列舉算法的工作量將會很大.因此,在用列舉法設計算法時,應當留意使方案優(yōu)化,盡量削減運算工作量.通常,只要對實際問題作詳細的分析,將和問題及關的學問條理化,完備化,系統(tǒng)化,從中找出規(guī)律,或對全部可能的狀況進行分類,引出一些有用的信息,列舉量是可以削減的.例1(百錢買百雞問題):用100元錢買100只雞,大公雞5元錢1只,大母雞3元錢1只,小雞1元錢3只,問如何買法?這是一個不定方程求解問題.事實上,設大公雞,大母雞,小雞的數(shù)量分別為x,y,z,則:算法1)完全枚舉法>baiji:=proc()localx,y,z;forxfrom1to100doforyfrom1to100doforzfrom1to100doifx+y+z=100and5*x+3*y+z/3=100thenprint(`COCK`=x,`HEN`=y,`CHICK`=z)fi;od;od;od;end:在算法1)中,各層循環(huán)均需100次,總循環(huán)次數(shù)1003次,明顯計算量太大.對問題稍做思索,則可知x最大值不超過19,其次層循環(huán)中y的最大值為33-5x/3,z的值自然等于100-x-y,即第三層循環(huán)已經(jīng)沒有必要.由此,可得改進算法.算法2)改進算法>baiji2:=proc()localx,y,z;forxfrom1to19doforyfrom1to33-5*x/3doz:=100-x-y;if5*x+3*y+z/3=100thenprint(`COCK`=x,`HEN`=y,`CHICK`=z)fi;od;od;end:不難分析,算法2的總循環(huán)次數(shù)大為降低,僅為310次.枚舉原理是計算機應用領域中特殊重要的原理.許多實際問題,若接受人工列舉是不行想象的,但由于計算機運算速度快,擅長重復操作,因此,枚舉法是計算機算法中的一個基礎算法.例如,為了證明一個命題為真,則只需一一列舉每一種狀況均為真即可.枚舉法是比較笨拙而原始的方法,它最大的缺點是運算量大,但在實際問題中,局部運用枚舉法卻是有效的.為了更進一步說明枚舉法的運用,下面再看一個例子:例2(均分問題):有三個人均分油,共有7滿桶,7半桶,7空桶,但不許打開桶倒油,如何分法?>sever_oil:=proc()locala1,a2,a3,b1,b2,b3,k1,k2,k3,k,kk,n,i;kk:=[];fora1from1to4dofora2from1to6doif2*a1+a2=7thena3:=7-a1-a2;forb1from1to6doforb2from1to6doif2*b1+b2=7anda1+b1<7anda2+b2<7thenb3:=7-b1-b2;if((7-a1-b1)+(7-a2-b2)+(7-a3-b3)=7)and(2*(7-a1-b1)+(7-a2-b2)=7)thenk1:=[a1,a2,a3];k2:=[b1,b2,b3];k3:=[(7-a1-b1),(7-a2-b2),(7-a3-b3)];k:={k1,k2,k3};n:=nops(kk);ifn=0thenkk:=[[k1,k2,k3]];elseforifrom1tondoif{op(kk[i])}=kthenbreak;endif;enddo;ifi>nthenkk:=[op(kk),[k1,k2,k3]];endif;endif;endif;endif;enddo;enddo;endif;enddo;enddo;n:=nops(kk);forifrom1tondoprintf("第%a種狀況\n",i);printf("第一家:%a個滿桶%a個半桶%a個空桶\n",kk[i,1,1],kk[i,1,2],kk[i,1,3]);printf("其次家:%a個滿桶%a個半桶%a個空桶\n",kk[i,2,1],kk[i,2,2],kk[i,2,3]);printf("第三家:%a個滿桶%a個半桶%a個空桶\n\n",kk[i,3,1],kk[i,3,2],kk[i,3,3]);enddo;endproc:>sever_oil();第一種狀況第一家:1個滿桶,5個半桶,1個空桶其次家:3個滿桶,1個半桶,3個空桶第三家:3個滿桶,1個半桶,3個空桶其次種狀況第一家:3個滿桶,1個半桶,3個空桶其次家:2個滿桶,3個半桶,2個空桶第三家:2個滿桶,3個半桶,2個空桶在該程序中,為了給出可讀性的輸出,大量運用了printf語句格式.3.2歸納法歸納法的基本思想是通過列舉少量的特殊狀況,經(jīng)過分析,最終找出一般關系.明顯,歸納法要比枚舉法更能反映問題的本質(zhì).但是,要從一個實際問題中總結歸納出一般的關系卻并非易事,尤其要歸納出其數(shù)學模型更難.從本質(zhì)上講,歸納就是通過視察一些簡潔而特殊的狀況,最終總結出有用的結論或解決問題的有效途徑.歸納是一種抽象,即從特殊現(xiàn)象中找出一般關系.由于在歸納過程中不行能對全部可能的狀況進行列舉,因而最終得到的結論還只是一種揣測(即歸納假設).所以,對于歸納假設還必需加以嚴格的證明,通常接受數(shù)學歸納法.下面是一個歸納法的典型例子。例3:求前n個自然數(shù)學的平方和:明顯,有:從上述幾個值很難看出Sn和n之間的關系.G.Polya曾經(jīng)提出下表所示的歸納公式:n123456…1+2+3+…+n136101521…12+22+32+…+n21514305591…(12+22+32+…+n2)/(1+2+3+…+n)3/35/37/39/311/313/3…由此即可揣測:而于是:要證明上式是否正確,需用數(shù)學歸納法對其加以證明.3.3遞歸法若一個算法通過把問題連續(xù)地歸納到帶更小輸入的相同問題,據(jù)此來解決原來的問題,則這個算法稱為遞歸的.例4:給出計算an的遞歸算法,其中a是非0實數(shù)而n是非負整數(shù).>A:=proc(a::nonnegative,n::nonnegint)ifn=0then1;elseA(a,n):=a*A(a,n-1);fi;end:例5:把線性搜尋算法表達成遞歸過程.>search1:=proc(a::list,i::nonnegint,j::nonnegint,x)ifa[i]=xtheni;elifi=jthen0;elsesearch1(a,i+1,j,x);fi;end:例6:構造對分搜尋算法的遞歸形式.>search2:=proc(a::set,i,j,x)localm;m:=trunc((i+j)/2);ifx=a[m]thenreturnm;elifx<a[m]andi<mthenreturnsearch2(a,i,m-1,x);;elifx>a[m]andj>mthenreturnsearch2(a,m+1,j,x);elsereturn0;fi;end:下面的例子想說明遞歸和迭代的微妙關系.例7:n!的計算.>fact:=proc(n::posint)ifn=1then1;elsefact(n):=n*fact(n-1);fi;end:明顯,這是遞歸算法.而假如是下述程序,則為迭代算法:>fact1:=proc(n::posint)localx,i;x:=1;forifrom1tondox:=x*i;od;end:3.4遞推法所謂遞推,是指從已知條件動身,逐次推出所要求的各中間結果及最終結果.其中,初始條件或是問題本身已經(jīng)給定,或是通過對問題的分析和化簡后確定.遞推本質(zhì)上也屬于歸納法.下面通過一個著名的難題的遞推來說明該方法.例8(漢諾塔問題):19世紀后期一個著名的游戲叫做漢諾塔,它是由安裝在一個板上的3根柱子和若干大小不同的盤子構成。起先時,這些盤子依據(jù)大小次序放在第一根柱子上,使得大盤子在底下。游戲的規(guī)則是:每次把1個盤子從一根柱子移動到另個根柱子,但是不允許這個盤子放在比它小的盤子上面。游戲的目標是把全部的盤子依據(jù)大小次序都放在其次根柱子上,并且將最大的盤子放在底部。令Hn表示解n個盤子的漢諾塔問題所須要的移動次數(shù)。建立一個關于序列{Hn}的遞推關系。設初始時刻n個盤子均在柱1。依據(jù)游戲規(guī)則我們可以用Hn-1次移動將上邊的n-1個盤子移到柱3。在這些移動中保留最大的盤子不動。然后,用一次移動將最大的盤子移到其次根柱子上。我們可以再運用Hn-1次移動將柱3上的n-1個盤子移到柱2,把它們放到最大的盤子上面,這個最大的盤子始終放在柱2的底部。簡潔看出,運用更少的步數(shù)是不行能求解這個難題的。這就證明白Hn=2Hn-1+1初始條件是H1=1,因為依照規(guī)則一個盤子可以用一次移動從柱1移到柱2。我們可以運用迭代方法求解這個遞推關系。留意H=2H+1=2(2H+1)+1=2H+2+1=2(2H+1)+2+1=2H+2+2+1……=2H+2+2+……+2+1=2+2+……+2+1=上述公式可以用數(shù)學歸納法證明。一個古老的傳聞告知我們,漢諾塔里的僧侶依據(jù)這個游戲規(guī)則從一個柱子到另一個柱子移動64個金盤子。他們1秒鐘移動1個盤子,據(jù)說當他們結束游戲時世界就到了末日。這個世界將在僧侶起先移動盤子多久以后終結?依據(jù)公式,僧侶須要=18446744073709551615次移動來搬這些盤子。每次移動須要1秒鐘,他們將用5849億年來求解這個難題,因此這個世界的壽命應當比它已有的壽命更長。例9(編碼字的枚舉):一個計算機系統(tǒng)把一個十進制數(shù)字串作為一個編碼字,假如它包含偶數(shù)個0就是有效的,否則就不是有效的.設an是有效的n位編碼字的個數(shù),試找出an的遞推關系.留意到a1=9(因為串0是無效的).本問題只需通過考慮由n-1位數(shù)字串構成n位數(shù)字串的方式來解決.而該方式共有兩種:第一種:在一個n-1位有效數(shù)字串后加上一個非0數(shù)字,而加這個數(shù)字的方式有9種,此時,an=9an-1;其次種:在一個n-1位無效數(shù)字串后加上0數(shù)字,這樣做的方式數(shù)等于無效n-1位數(shù)字串的個數(shù),因為共存在10n-1個n-1位有效數(shù)字,此時,an=10n-1-an-1.從而,有:an=9an-1+(10n-1-an-1)=10n-1+8an-1>a:=proc(n::posint)ifn=1then9;else10^(n-1)+8*a(n-1);fi;end;有些實際問題,既可以用遞推法也可以用遞歸法求解。但遞歸和遞推算法的實現(xiàn)方法是不一樣的。遞推是從初始條件動身,逐次推出所需求的結果;而遞歸則是從算法本身到達遞歸邊界。通常,遞歸算法要比遞推算法更清楚易讀,其結構也比較簡練。特殊是在許多比較困難的問題中,很難找到從初始條件推出所需結果的全過程,此時,設計遞歸算法要比遞推算法簡潔得多。但遞歸算法也有一個致命的缺點,即執(zhí)行的效率比較低。3.5回溯法回溯法也稱為摸索法,該方法首先短暫放棄規(guī)模大小的限制,并將問題的候選按某種依次逐一枚舉和檢驗.當發(fā)覺當前問題選解不行能是解時,就選擇下一個候選解;倘如當前候選解除了還不滿意規(guī)模要求外,滿意全部其他要求時,接著擴大當前候選解的規(guī)模,并接著摸索.假如當前候選解滿意規(guī)模在內(nèi)的全部要求時,該候選解就是問題的一個解.在回溯法中,放棄當前候選解,找尋下一個候選解的過程稱為回溯.擴大當前候選解的規(guī)模,并接著摸索的過程稱為向前摸索.例10(皇后問題):由n2個方塊排成n行n列的正方形,稱之為“n元棋盤”.假如兩個皇后位于n元棋盤上的同一行、或同一列、或同一對角線上,則稱它們在相互攻擊。現(xiàn)要求找出訪n元棋盤上的n個皇后互不攻擊的布局。n個皇后在n元棋盤上的布局共有nn種為了從中找出互不攻擊的布局,須要對此nn種方案逐個進行檢查,將有攻擊的布局刪掉。這是一種列舉法。但這種方法對于較大的n,其工作量會急劇增加。而事實上,從互不攻擊的布局的要求可分析出逐一列舉也是沒有必要的。例如:每一行只能放一個皇后。又如,假如第一行上的皇后已經(jīng)在某一列,則其它行上的皇后就不行能在該列。下面用回溯法來設計求解這個問題的算法。首先,將問題歸結為在n元棋盤的每一行上安置一個皇后,并假設第i個皇后被安置在第i行上。定義一個一維數(shù)組A(1:n),其中每一個元素A(i)用于在安置皇后過程中隨時記錄第i行上的皇后所在的列號。由此可知,這種狀況下,事實上已經(jīng)剔除了兩個皇后在同一行上的可能性。因此,在安置每一行上的皇后時,只需考慮每兩個皇后不能在同一列或同一對角線上即可。簡潔驗證,第i行上的皇后和第k行上的皇后正好在同一列的充要條件為:A(i)-A(k)=0正好同在某一對角線上的充要條件為:|A(i)-A(k)|-|i-k|=0初始時,將每一行的皇后均放在第一列,即初始狀態(tài)為A(i)=1,i=1,2,….n.然后,從第一行(即i=1)起先進行以下過程。設前i-1行上的皇后已經(jīng)布局好,即它們互不攻擊?,F(xiàn)考慮支配第i行上的皇后的位置,使之和前i-1行的皇后也都互不攻擊。為了實現(xiàn)這一點,可以從第i行皇后的當前位置起先向右搜尋:(1)若A(i)<=n,則需檢查第i-1行皇后是否互不攻擊。若無攻擊,則考慮支配下一行皇后的位置;若有攻擊,則將第i行皇后右移一個位置,重新進行這個過程。(2)若A(i)>n,則說明在前i-1行皇后的當前布局下,第i行皇后已無法安置。此時將第*行皇后重新放在第一列,且回退一行,再考慮第i-1行皇后和前i-2行皇后均互不攻擊的下一個位置。在這種狀況下,假如已退到第0行(n元棋盤不存在第0行),則過程結束。(3)若當前安置好的皇后是在最終一行(即第n行),則說明已找到了n個皇后互不攻擊的一個布局,將這個布局打印輸出。然后將第n行的皇后右移一個位置,重新進行這個過程,以便進一步找出另外的布局。綜合以上過程,可以形象地概括成一句話:“向前走,碰壁回頭”。這種方法也稱為深度優(yōu)先搜尋技術DFS(DepthFirstSearch)。>Q:=proc(col::integer,n::integer,Row,Lbias,Rbias,result::array)locali,j,A,m;forifrom1tondoifRow[i]=1andLbias[col+i]=1andRbias[n+col-i]=1thenresult[col]:=i;Row[i]:=0:Lbias[col+i]:=0:Rbias[n+col-i]:=0;ifcol=nthenA:=matrix(n,n);forjfrom1tondoformfrom1tondoA[j,m]:=0;enddo;enddo;forjfrom1tondoA[result[j],j]:=Q;enddo;print(A);elseQ(col+1,n,Row,Lbias,Rbias,result);endif;Row[i]:=1:Lbias[col+i]:=1:Rbias[n+col-i]:=1;endif;enddo;endproc:>Queen:=proc(n::integer)locali,Row,Lbias,Rbias,result;ifn<4thenprint(2??üμ?μ??á1?);return;endif;Row:=array(1..n);result:=array(1..n);Lbias:=array(1..(2*n));Rbias:=array(1..(2*n));forifrom1tondoRow[i]:=1;enddo;forifrom1to2*ndoLbias[i]:=1:Rbias[i]:=1;enddo;Q(1,n,Row,Lbias,Rbias,result);return;endproc:>Queen(4);例11:在9()個方格中填入1到N(N≥10)內(nèi)的某9個數(shù)字,每個方格填一個整數(shù),使全部相鄰兩個方格內(nèi)的數(shù)字和為質(zhì)數(shù).試求出全部滿意這個要求的各種數(shù)字填法.可用摸索法找到問題的解.即從第一個方格起先,為當前方格找尋一個合理的整數(shù)填入,并在當前位置正確填入后,為下一個方格找尋填入的合理整數(shù),如不能為當前方格找到一個合理的可填整數(shù),就要回退到前一方格,調(diào)整前一方格的填入數(shù).當為第9格也填入了合理的整數(shù)后,就找到了一個解,將該解輸出,并調(diào)整第9格的填數(shù)去找下一個解.為了進一步說明回溯法及其程序設計的本質(zhì),下面的程序給出的是求解類似問題的通用形式,即個方格中中填數(shù)的問題.其中check子程序是為判定兩數(shù)和為質(zhì)數(shù)而設計的.>check:=proc(s::array,top::integer,n::integer)locali,j,m;iftop=1thenreturntrue;endif;forifrom1totopdoforjfrom1totopdoifi<>jands[i]=s[j]thenreturnfalse;endif;enddo;enddo;m:=(n-1)*n;forifrom1totop-1doif(imodn)<>0andisprime(s[i]+s[i+1])=falsethenreturnfalse;endif;ifi<=mandtop>n+iandisprime(s[i]+s[i+n])=falsethenreturnfalse;endif;enddo;returntrue;endproc:>Get:=proc(number::integer,n::integer)localresult,top,i,ok,A;result:=array(1..(n*n));top:=1;i:=1;whiletruedoresult[top]:=i;ok:=check(result,top,n);ifokthentop:=top+1;i:=1;elseifi<numbertheni:=i+1;elsetop:=top-1;i:=result[top]+1;whilei>=numberandtop>1dotop:=top-1;i:=result[top]+1;enddo;endif;endif;iftop>n*nthenA:=matrix(n,n,result);print(A);top:=top-1;i:=result[top]+1;whilei>=numberandtop>1dotop:=top-1;i:=result[top]+1;enddo;endif;iftop<=1andresult[top]>=numberthenreturn;endif;enddo;endproc:要運行該程序,只需輸入最大的N及方格的行數(shù)n即可:>Get(10,3);3.6貪欲法貪欲法是一種不追求最優(yōu)解,只希望得到較為滿意的解的方法.貪欲法一般可以快速得到滿意的解,因為它省去了為找最優(yōu)解要窮盡全部可能而必需耗費的大量時間.貪欲法常以當前狀況為基礎作最優(yōu)選擇,而不考慮整體狀況,所以貪欲法不如回溯法.例如去商場購物找錢時,為使找回的零錢數(shù)最少,不考慮找零錢的全部可能的方案,而是從最大面值的幣種起先,按遞減的依次考慮各幣種,先盡量用大面值的幣種,當不足大面額時才去考慮下一種較小面值的幣種.這就是貪欲法.例12(裝箱問題):設有編號為0,1,2,…,n-1的n種物品,體積分別為v0,v1,…,vn-1,將這n種物品裝到容量為V的箱子里.約定這n種物品的體積不超過V.不同的裝箱方案所須要的箱子數(shù)可能不同,求裝盡這n種物品的箱子數(shù)最少是多少?>count:=proc(cargo::list,volume::float)localm,n,box,i,j,box_count,cargos;m:=nops(cargo);cargos:=sort(cargo);box:=[];box_count:=0;forifrom1tomdon:=nops(box);forjfrom1tondoifvolume-value(convert(box[j],`+`))>cargos[i]thenbreak;endif;enddo;ifj=0thenbox:=[[cargos[i]]];elifj<=nthenbox[j]:=[op(box[j]),cargos[i]];elsebox:=[op(box),[cargos[i]]];box_count:=box_count+1;endif;enddo;return[box_count,box];endproc:>count([1,1.1,1.2,2,2.5,3.4,3,4,6,7.9,8,5],10.0);3.7數(shù)字模擬法數(shù)字模擬也稱數(shù)字仿真或計算機仿真,也就是用計算機模擬實物系統(tǒng),對實物系統(tǒng)的結構和行為進行動態(tài)演示,以評價或預料系統(tǒng)的行為效果,為決策供應信息.一般分為兩類:確定性模擬和隨機性模擬.在實際問題的求解中,當遇到很難建立數(shù)學模型或算法的問題時,通常接受模擬法.例13(戰(zhàn)艦問題):一敵艦在某海疆內(nèi)沿正北方向航行時,我方戰(zhàn)艦恰位于敵艦的正西方向1nmile處,我艦向敵艦放射制導魚雷,敵艦速度為0.42nmile/min,魚雷的速度為敵艦速度的2倍.假設魚雷和敵艦距離<0.02nmile時敵艦被擊中,試寫出敵艦被擊中的Maple仿真程序.建立直角坐標系(圖1),設敵艦為動點Q,魚雷為動點P,Q點的初始位置為Q(1,0),P點的初始位置為P(0,0).為了計算出追擊過程中每一時刻P點和Q點的詳細位置,需分別描P、Q兩點運動的方向、速度及位置變更規(guī)律。由于Q點從初始點動身沿y軸方向運動且速度為常數(shù)v,故Q點在t=tk時刻的位置為Q(1,v0tk)。由于P點的運動方向始終指向Q,設在t=tk時刻,P點位置是P(xk,yk),則向量,此時P點的運動方向可由其方向余弦表示:其重量表示為:P點運動速度為常數(shù)v=2v。取時間步長t=2s,設在t=t時刻,P點的位置為P(x,y),于是P點的位置變更規(guī)律為:據(jù)此,可寫出其計算機仿真程序如下:>Digits:=4:with(plots):>f:=proc()locald,x,y,x1,y1,dt,v0,v1,t,i,j,px,py,qx,qy,F,G:i:=0:dt:=2:v0:=0.42/60:v1:=2*v0:x:=0:y:=0:d:=1:t:=0:whiled>0.02doi:=i+1:d:=sqrt((1-x)^2+(v0*t-y)^2):x1:=x+v1*dt*(1-x)/d:y1:=y+v1*dt*(v0*t-y)/d:x:=x1:y:=y1:t:=t+dt:px[i]:=x:py[i]:=y:qx[i]:=1:qy[i]:=v0*t:od:printf("擊中敵艦的時間t=%as\n",t);printf("[%12a,%12a]%12a\n",px,py,qx,qy);forjfrom1toidoprintf("[%12a,%12a]%12a\n",px[j],py[j],qy[j]);od:F:=plot([seq([px[j],py[j]],j=1..i)],style=point,color=red);G:=plot([seq([qx[j],qy[j]],j=1..i)],style=point,color=blue);display({F,G},scaling=constrained,title='Figure1');endproc:>f();擊中敵艦的時間t=96s數(shù)字模擬總體來說是一種近似方法.在上述問題的解決過程中,接受逐點掃描法將各個時刻的P,Q坐標標示出來,并推斷P,Q兩點間的距離而得結論.因此,時間步長,相應的最小距離值的是確定該問題解的精度的主要參數(shù).給參數(shù)賦不同的值將會得到不同的結果.當然,該問題還可通過數(shù)學分析的方法求解.下面的內(nèi)容想進一步說明仿真法和數(shù)學分析法的異同.設敵艦的速度為常數(shù)v0,追曲線為y=y(x)。即在時刻t,魚雷的位置在點P(x,y)處,這時敵艦的位置在Q(1,vt)處。由于魚雷在追擊過程中始終指向敵艦,而魚雷運動方向沿曲線的切線方向,所以有:即:兩邊對x求導,得:即:由已知魚雷的速度為2v,即:*因為所以,即代入(*)式得曲線滿意的微分方程模型令方程化為:分別變量,積分并代入初始條件計算得:即而兩式相減得干脆積分并代入初始條件得:這就是魚雷追擊曲線的方程.因魚雷擊中敵艦時,它的橫坐標x=1,代入曲線方程得y=.即敵艦航行至nmile處時將被擊中,這段航程所須要的時間是95.2381s.毫無疑問,隨著計算機技術的進一步普及,計算機模擬在科學探討中將會充當更為重要的角色.為了進一步展示計算機模擬的作用和地位,下面的例子是古典概型中一個典型問題—擲骰子問題的仿真:>ludo:=proc()localm,s,i,S,bool,dice,budeng;dice:=[seq(rand(1..6)(),i=1..10^5)]:print(`Theprobabilityofludoisasfollow:`);print(evalf(add(i,i=dice)/nops(dice)));s:=[]:print(`Theprobabilityofiofdiceisasfollow:`);formfrom1to6dos:=[op(s),evalf(nops(select(has,dice,m))/nops(dice))]:od:s:S:=s;forifrom1tonops(s)doprintf("s[%a]=%a\n",i,s[i]);S[i]:=evalf(ceil(s[i]*100)/100,2);od:budeng:=[];print(`So,wewillhavetheresultasfollows:`);printf("");forifrom1to6doifevalf(1/6,2)=evalf(s[i],2)thenprintf("s[%a]=",i);elsebudeng:=[op(budeng),i];endif;enddo;printf("0%a\n",evalf(1/6,2));ifbudeng<>[]thenprintf("");forifrom1tonops(budeng)doprintf("s[%a]=0%a",budeng[i],S[budeng[i]]);enddo;endif;return;endproc:>ludo();s[1]=.1664000000s[2]=.1683300000s[3]=.1674300000s[4]=.1651900000s[5]=.1651200000s[6]=.1675300000s[1]=s[2]=s[3]=s[4]=s[5]=s[6]=0.174子程序求值在Maple子程序中的語句,求值的方式和交互環(huán)境中的求值有所不同,這在某種程度上是為了提高子程序的執(zhí)行效率而考慮的.在交互式環(huán)境中,Maple對于一般的變量和表達式都進行完全求值(除了數(shù)組、映射表等數(shù)據(jù)結構外).比如先將b賦給a,再將c賦給b,則最終a的值也指向c.>a:=b:b:=c:a+1;但在子程序內(nèi)部狀況就不一樣了,試看下述試驗:>f:=proc()locala,b;a:=b;b:=c;a+1;end:結果盡然是:>f();這是因為a和b都是局部變量,Maple對于子程序中的局部變量只進行一層的求值.下面,我們針對子程序中的不同的變量,系統(tǒng)介紹其求值機制.4.1參數(shù)子程序中的參數(shù),就是那些在proc()的括號中的變量.參數(shù)是一類特殊的變量,在調(diào)用子程序時,會把它們替換成實際的參數(shù).>sqrt1:=proc(x::anything,y::name)y:=x^2;end:>sqrt1(d,ans);>ans;我們再來試試別的參數(shù),比如前面賦值過的a作為第一個參數(shù),其次個參數(shù)仍用ans,只不過加了單引號:>sqrt1(a,'ans');>ans;事實上,Maple在進入子程序以前就已經(jīng)對參數(shù)進行了求值,因為是在子程序外進行求值,所以求值規(guī)則聽從調(diào)用時所在的環(huán)境.上面是在交互式環(huán)境下調(diào)用sqrt1得到的結果,作為比照看看在子程序內(nèi)部調(diào)用上面的sqrt1子程序會有什么不同.>g:=proc()locala,b,ans;a:=b;b:=c;sqrt1(a,ans);end:>g();因為這次調(diào)用是在程序內(nèi)部,所以只進行第一層求值,進入sqrt1的參數(shù)值為b.Maple對于子程序的參數(shù),都只在調(diào)用之前進行一次求值,而在子程序內(nèi)部出現(xiàn)的地方都用這次求值的結果替換,而不再重新進行求值.如前所述之sqrt1:>sqrt1:=proc(x::anything,y::name)y:=x^2;y+1;end:>sqrt1(d,'ans');可見,對參數(shù)賦值的作用只有一個—返回確定的信息.因為在子程序內(nèi)部,恒久不會對參數(shù)求值.我們可以認為,參數(shù)是一個0層求值的變量.4.2局部變量和全局變量Maple對于局部變量只進行一層求值,也就相當于用eval(a,1)得到的結果.這種求值機制不僅可以提高效率,而且還有著更重要的作用.比如在程序中,我們往往須要把兩個變量的值交換,一般地,我們運用一個中間變量來完成這樣的交換.但假如求值機制和交互式環(huán)境中一樣,將達不到我們的目的.試看,在交互式環(huán)境下,我們企圖用這樣的方法交換兩個未被賦值的變量會有什么后果:>temp:=p:p:=q:q:=temp;不管在交互式環(huán)境下還是在程序中,Maple對于幾乎每一個全局變量都進行完全求值,而對于數(shù)組、映射表和子程序,Maple接受賦值鏈中的上一名稱來求值.除了上面這些特殊數(shù)據(jù)對象及下面一個特例外,總結起來,Maple對于子程序的參數(shù)進行0層求值,對于局部變量進行1層求值,而對于全局變量,則進行完全求值.對于上面說的求值規(guī)則,在Maple中還有個特例,就是“同上”操作符“%”須要引起留意.就其作用來說,它應當屬于局部變量.在進入一個子程序時,Maple會自動地把該子程序中的%設置成NULL(空).但是,對于這個“局部變量”,Maple不遵循上面的規(guī)則,無論在那兒都會將其完全求值.我們下面通過一個例子說明它在子程序中的求值機制:>f:=proc()locala,b,c,d;print("Atthebeginning,[%]is",%);a:=b;b:=c;c:=d;a+1;print("Now[%]is",%);end:>f();可以看出,盡管在子程序內(nèi)部,局部變量a僅進行一層求值,但指代a+1的同上操作符卻進行了完全求值,得到d+1的結果.4.3環(huán)境變量所謂環(huán)境變量就是從一個程序退出時系統(tǒng)自動設置的全局變量.Maple中有幾個內(nèi)建的環(huán)境變量,如Digits,Normalizer,Testzero,mod,printlevel等.從作用域來看,它們應當算作是全局變量.在求值上,它們也像其他全局變量一樣,始終都是完全求值.但是假如在子程序中間對環(huán)境變量進行了設置,那么,在退出該子程序時,系統(tǒng)會自動地將其復原成進入程序時的狀態(tài),以保證當前行時的環(huán)境.正因如此,我們稱其為環(huán)境變量.試看下面程序:>f:=proc()print("Enteringf.Digitsis",Digits);Digits:=20;print("NowDigitshasbecome",Digits);end:>g:=proc()print("Enteringg.Digitsis",Digits);Digits:=100;print("NowDigitsis",Digits);f();print("Backingfromf,Digitsis",Digits);end:>f();>g();>Digits;可以看出,從子程序g中返回時,Digits又被自動地設成了原來的值(10).假如你須要

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論