Chương trình con đê quy 24

Một phần của tài liệu Giáo trình lập trình mạng đại học Đà Lạt (Trang 25 - 31)

Giải thuật đệ quy là giải thuật có chứa thao tác gọi đến nó . Giải thuật đệ quy cho phép mô tả một dãy lớn các thao tác bằng một chương trình con đê quy . Một cách tổng quát một giải thuật đệ quy được biểu diễn như một bộ P gồm các mệnh đề Si (không chứa yếu tố đệ quy ) và P : P ≡ P[ Si , P ] .

Phương tiện để mô tả giải thuật đệ quy trong các ngôn ngữ lập trình là chương trình con đệ quy : chương trình con có chứa lênh gọi đến nó trực tiếp hoặc gián tiếp .

Một giải thuật đệ quy cũng có thể dẫn tới một qúa trình gọi đê quy không kết thúc ,vì vậy quan tâm đến điều kiện dừng của một giải thuật đệ quy luôn được đặt ra . Để kiểm soát qúa trình gọi đệ quy chương trình con P người ta thường gắn thao tác gọi P với việc kiểm tra một điều kiện B , qúa trình gọi P sẻ dừng khi B không con thỏa.

Mô hình tổng quát của một giải thuật đệ quy với sự quan tâm đến sự dừng sẻ là : P ≡ if B then P[ Si , P ] hoặc P ≡ P[ Si , if B then P ] Thông thường với một gỉai thuật đệ quy P , để chỉ ra rằng P sẻ dừng sau n lần gọi ta sẻ chọn B là ( n >0 ) . Mô hình gỉai thuật đệ quy khi đó sẻ có dạng :

P(n) ≡ If ( n > 0 ) then P[ Si , P(n - 1)] ; hoặc P(n) ≡ P[ Si , if (n >0) then P(n - 1) ] ;

Trong các ứng dụng thực tế số lần gọi đệ quy (độ sâu đệ quy) không những phải hữu hạn mà còn phải đủ nhỏ . Bởi vì mỗi lần gọi đệ quy sẽ cần một vùng nhớ mới trong khi vùng nhớ cũ vẫn phải duy trì .

2 . Các chương trình con đệ qui. a) Các hàm đệ quy.

Định nghĩa hàm số bằng đệ quy thường gặp trong toán học . Dạng phổ biến là các hàm của đối số nguyên mô tả các dãy số hồi quy .

Ví dụ :

- Dãy các giai thừa : 1 ,1 , 2 , 6 , 24 , 120 , 720 , 5040 , . . . Ký hiệu FAC(n ) = n ! .

Ta có : + FAC(0 ) = 1 ; ( 0 ! = 1 )

+ FAC(n ) = n * FAC(n - 1 ) ; ( n ! = n * (n - 1 ) ! ) với n >= 1 Giải thuật đệ quy tính FAC(n ) là :

FAC(n ) If (n = 0 ) then return 1 ; ≡

else return (n * FAC(n - 1 )) ;

- Dãy số Fibonaci : 1 ,1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , 55 , 89 , 144 , 233 , 377 , . . .

+ F(0 ) = F(1 ) = 1 ;

+ F(n ) = F(n - 1 ) + F( n - 2 ) ; với n > = 2 Giải thuật đệ quy tính F( n ) là :

F(n) If ((n = 0 ) or ( n = 1 )) then Return 1 ; ≡

- Dãy các tổ hợp : 1 1 2 1 1 3 3 1 1 4 6 4 1 Cn0 = 1 với n > = 0 Cnm = 0 với m > n > 0 Cnm = Cnm−−11+Cnm− với n > m > 0 1

Giải thuật đệ quy tính Cnm là : If ( m = 0 ) then Return 1 ;

else If (m > n ) then Return 0 ;

else Return (Cnm−−11+Cnm− ) ;

1

Nhận xét : Một định nghĩa hàm đệ quy gồm :

+ Một số các trường hợp suy biến mà gía trị hàm tại đó đã được biết hoặc có thể tính một cách đơn giản (không đệ quy ) .

Ví dụ : FAC(0) = 1 , F(0) = F(1) = 1 , Cn0 = 1 .

+ Trường hợp tổng quát việc tính hàm sẻ đươc đưa về tính hàm ở mức đơn giản hơn .

Ví dụ : FAC(n ) = n * FAC(n - 1 ) ; F(n) = F(n -1) + F( n - 2 ) .

Trong tập biến của hàm có một nhóm mà độ lớn của nó quyết định độ phức tạp của việc tính gía trị hàm . Nhóm biến đó gọi là nhóm biến điều khiển . Gía trị biên của nhóm biến điều khiển ứng với trường hợp suy biến . Gía trị của nhóm biến điều khiển sẻ thay đổi qua mỗi lần gọi đệ quy với xu hướng tiến đến gía trị biên ( tương ứng với các trường hợp suy biến của hàm ).

b) Các thủ tục đệ quy .

Thủ tục đệ quy là thủ tục có chứa lệnh gọi đến nó .thủ tục đệ quy thường được sử dụng để mô tả các thao tác trên cấu trúc dữ liệu có tính đệ quy

Ví dụ :

- Thủ tục bầu cử trong một nước theo phương thức đại cử tri : Từng nhóm cử tri bầu ra đại cử tri , đại cử tri bầu ra tổng thống.

- Thủ tục chọn 1 phần tử lớn nhất trong một danh sách : chia danh sách làm M danh sách , chọn phần tử lớn nhất trong từng phần , rồi chọn trong những cái đã chọn được.

Thủ tục đệ qui đặc biệt thích hợp khi sử lý trên các cấu trúc dữ liệu có tính đệ qui .

- Một dãy n phần tử a[1:n] là sự kết hợp giữa dãy a[1:n-1] và a[n] . Do do ù:

+ Phép tìm 1 phần tử lớn nhất trong 1 dãy a[1:n] ( thủ tục TMax) có thể được xác định 1 cách đệ qui :

TMax(a[1:n] ) max(a[n],TMax(a[1:n-l]) ) ≡

+ Phép tìm tổng các phần tử (TSUM)

TSUM(a[1:n]) Tg(a[n] ,TSUM(a[1:n-1]) ) ≡

với TSUM(a[m:m]) cho a[m] Tg là phép tìm tổng 2 số .

- Một dãy a[m : (m+n)] là sự kết nối giữa hai dãy: dãy a[m:((m+n) div 2)] và dãy a[(((m+n) div 2)+1) : (m+n)], nên phép tính tổng các phần tử (TSUM) là : TSUM(a[m : (m+n)]) Tg(TSUM(a[m:((m+n) div 2)]) , TSUM(a[((m+n) div 2)+1:n)]))

- Thụ tục quét cây nhi nhân theo thứ tự giữa (LNF) là : + Quét cây con trái theo thứ tự giữa (L) ;

+ Thăm nút gốc (N) ;

+ Quét cây con phải theo thứ tự giữa (F) ; Nhận xét :

Trong một thủ tục đệ qui , để cho việc gọi đệ quy dừng lại sau hữu hạn lần gọi nó cần chứa điều kiện kiểm tra ( một biểu thức boolean B trên một nhóm biến ) , để khi điều kiện này không còn thỏa thì việc gọi đệ qui kết thúc .

Dạng thường gặp của thủ tục đệ qui là : a) S1 ; {không chứa yếu tố đệ qui}

if B then S2 {phần lệnh trực tiếp , không có lệnh gọi đệ qui} else Sdq ; {phần lệnh có lệnh gọi đệ qui}

S3 ; {không có gọi đệ qui} b) S1 ; { Không đệ quy }

While B do Sdq ; {phần lệnh có lệnh gọi đệ qui} Sø2 ; {phần không có lệnh gọi đệ qui}

3. Hai dạng chương trình con đệ qui :

- Đệ qui trực tiếp : Chương trình con P được gọi là đệ quy trực tiếp nếu trong P có lệnh gọi dến P một cách tường minh .

Ví dụ : Các hàm và thủ tục nêu ở các ví dụ trên .

- Đệ qui gián trực tiếp : Chương trình con P được gọi là đệ quy gián tiếp nếu trong P có lệnh gọi dến P một cách không tường minh . Như P gọi Q và Q gọi P. Ví dụ : Procedure A(x : . . . ) ; . . . Begin . . . B(f(x),. . .); . . . end ; Procedure B(y : . . . ); . . . Begin . . .

A(g(y), . . . ); . . .

end ;

Khi thực hiện A có lệnh gọi B khi thực hiện B lại có lệnh gọi A . Cả A và B đều là thủ tục đệ quy gián tiếp .

Trong trường hợp chương trình con P là đệ quy gián tiếp , sự xuất hiện lệnh gọi P có thể xảy ra sau khi đi qua 1 dây chuyền nhiều bước trung gian .

4 . Thể hiện đệ qui trong NNLT PASCAL và C++ :

NN LT Pascal và C++ đều cho phép tổ chức chương trình con đê quy nhờ vào cơ chế tạo vùng nhớ Stak của phần mềm ngôn ngữ .

a) Trong NN LT Pascal :

Pascal có 2 hình thức chương trình con đệ quy là : hàm đê quy và thủ tục đệ quy . Lệnh gọi đệ quy cũng phải tuân theo quy luật tầm vực của ngôn ngữ : một CT PASCAL , chỉ có thể thực hiện lệnh gọi đến một chương trình con đã được khai báo trong khối chứa lệnh gọi .

Ví dụ : Với mô hình chương trình sau .

Trong phần lệnh của khối A có thể gọi đến :

Program + Các thủ tục , hàm con trực tiếp của nó : E gọi được B nhưng không gọi được C + Gọi chính A ( gọi đệ quy trực tiếp )

–A + Gọi thủ tục hàm cùng cấp nhưng phải được

–B khai báo trước gọi được E nhưng không gọi C được D , muốn gọi D phải dùng forward để

khai báo trước .

-D

Khai báo FORWARD : Để từ thủ tục hàm A có thể gọi đến D là thủ tục hàm cùng cấp nhưng được mô tả sau A , ta cần có một khai báo trước của D ở phía trước của A .

Khai báo này gồm : tiêu đề của D, với danh sách thông số của D, tiếp theo là từ khoá FORWARD . Sau đó , lúc khai báo lại hoàn chỉnh D thì chỉ cần khai báo từ khoá PROCEDURE ( hoặc FUNCTION ) , tên của D ( không cần danh sách thông số) rồi đến phần thân của D.

Ví dụ :

procedure second (var M,N : integer ; p : char) ; Forward ; procedure first (A,B:integer; var X : real);

var ... begin . . . second(W,Y,Z) ; . . . end ; procedure second ; var ... begin ... end ; b) Trong NNLT C++

NNLT C++ cho phép tổ chức chương trình con đệ quy thuận lợi hơn . Bởi vì mọi hàm con trong C++ đều phải khai báo trước tiêu đề nên không có sự phân biệt nào về việc khai báo đối với hàm con thường và hàm con đệ quy.

5. Các ví dụ về các chương trình con đệ qui trong PASCAL và C++ . Ví dụ 1 : Hàm F(n) tính số hạng n của dãy FIBONACCI

+ Dạng hàm trong Pascal:

Function F(n : integer) : integer; begin if( n < 2 ) then F := 1 else F := F(n-1) + F(n-2) end; + Dạng hàm trong C++ : int F(n :int) { if ( n < 2 ) return 1 ; else return (F(n -1) + F(n -2)) ; }

Ví dụ 2 : Chương trình đảo ngược một câu (sử dụng CT con đệ quy ) : Đọc vào một câu kết thúc bằng dấu ‘.’xuất ra màn hình theo thứ tự ngược .

( ví dụ : nhập : good _ bye see You agean . xuất : . naega ees eyb_doog ) + Chương trình trong Pascal :

Program VD1; Uses crt ;

Procedure DAO ; var kt : char ; Begin read(kt) ;

if(kt <> ‘.’) then DAO ; write(kt) ;

end ;

Begin (* main program *) clrscr ;

writeln(‘ go vao mot cau ket thuc bang dau . ‘ ) ; DAO ;

readln ; end . + Chương trình trong C++ : # include <iostream.h> # include <conio.h> void DAO(void) ; int main() { clrscr() ;

cout << “ nhap vao 1 cau ket thuc bang dau . \n “ ; DAO ; getch() ; return 1 ; } void DAO() { char kt ; cin >> kt ; if(kt != ‘.’) DAO ; cout << kt ; }

vi dụ 3 : Chương trình con tính USCLN của 2 số dựa vào thuật toán Euclide : USCLN(a,b) = USCLN(b,a mod b ) với a <> 0 , b <> 0

USCLN(n,0) = n

+ Dạng hàm trong Pascal :

Function USCLN(a,b : integer ) : integer ; begin

if(b = 0 ) then USCLN := a

else USCLN := USCLN(b , a mod b ) ; end ;

+ Dạng hàm trong C++ : int USCLN(int a , int b ) { if(b = 0 ) return (a) ;

else return ( USCLN(b , a mod b)) ; }

$ 2 . BÀI TOÁN ĐỆ QUY

Một phần của tài liệu Giáo trình lập trình mạng đại học Đà Lạt (Trang 25 - 31)

Tải bản đầy đủ (PDF)

(110 trang)