1. Trang chủ
  2. » Công Nghệ Thông Tin

THUẬT TOÁN ĐỆ QUY MINH HỌA BẰNG FREE PASCAL

15 766 1

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 15
Dung lượng 542,25 KB

Nội dung

 Ở bước phân tích, bài toán lớn được phân tích thành bài toán đồng dạng nhưng đơn giản hơn.. Bước phân tích sẽ dừng lại khi chúng ta phân tích đến bài toán đồng dạng đơn giản nhất mà ở

Trang 1

THUẬT TOÁN ĐỆ QUY MINH HỌA BẰNG FREE PASCAL

Trong thế giới lập trình, có rất nhiều thuật toán như: các thuật toán tìm kiếm, sắp xếp, quy hoạch động, đồ thị, Trong đó có một thuật toán rất nổi tiếng, đó là Đệ quy

Nếu bạn là một người đã và đang học lập trình, chắc hẳn bạn đã tìm hiểu hoặc ít nhất cũng nghe nói

về thuật toán kinh điển này Thuật toán này được ứng dụng cũng khá rộng rãi trong tin học và cả toán học Có rất nhiều bài toán được giải quyết bằng thuật toán Đệ quy rất hiệu quả Thậm chí có những bài toán chỉ có thể suy nghĩ theo cách Đệ quy mới giải quyết được

Trang 2

1 Vấn đề Đệ quy

1.1 Các ví dụ:

Ví dụ 1: Hãy tính S(n) = 1+2+3+…+n (tính tổng của n số nguyên dương đầu tiên)

Nếu n = 10, chúng ta có thể giải bằng cách cộng 10 số nguyên dương đầu tiên, kết quả là 55:

S(10) = 1+2+3+…+10 = 55 Nếu tính tiếp n = 11 chúng ta cũng có thể giải bài toán với như trên, nghĩa là cộng 11 số nguyên dương đầu tiên và kết quả là 66 Tuy nhiên trong thực tế để tính S(11) khi đã biết S(10) người ta lấy kết quả của S(10) cộng thêm cho 11:

S(11) = S(10) + 11 = 66 Bài toán tính S(n) được giải tổng quát qua hai bước phân tích và thế ngược như sau:

(1) Bước phân tích:

 Để tích S(n) trước tiên chúng ta phải tính S(n-1), sau đó tính S(n) = S(n-1) + n

 Để tích S(n-1) trước tiên chúng ta phải tính S(n-2), sau đó tính S(n-1) = S(n-2) + n-1

 …

 Để tích S(2) trước tiên chúng ta phải tính S(1), sau đó tính S(2) = S(1) + 1

 Và cuối cùng S(1) có ngay kết quả là 1

Bước phân tích của bài toán tính S(n)

S(n) = S(n-1) + n

S(n-1) = S(n-2) + n-1

S(2) = S(1) + 1

S(1) = 1

(2) Bước thế ngược:

Có kết quả của S(1) chúng ta thay nó vào biểu thức tính S(2) và tính được S(2), có S(2) chúng ta sẽ tính được S(3),…, có S(n-1) chúng ta sẽ tính được S(n), và bài toán đã được giải quyết

Bước thế ngược của bài toán tính S(n) S(1)= 1

S(2) = S(1) + 2 = 3

S(3) = S(2) +3 = 6

S(n) = S(n-1) + n

Ví dụ 2: Tính P(n) = xn với x là số thực và n là số nguyên ≥ 0

Tương tự như bài toán trên, bài toán tính P(n) được giải tổng quát qua hai bước là bước phân tích và bước thế ngược như sau:

(1) Bước phân tích:

 Để tính P(n) trước tiên chúng ta phải tính P(n-1), sau đó tính P(n) = P(n-1) * x

 Để tính P(n-1) trước tiên chúng ta phải tính P(n-2), sau đó tính P(n-1) = P(n-2) * x

 …

 Để tính P(1) trước tiên chúng ta phải tính P(0), sau đó tính P(1) = P(0) * x

 Và cuối cùng P(0) có ngay kết quả là 1

Bước phân tích của bài toán tính P(n)

Trang 3

P(n) = P(n-1) * x

P(n-1) = P(n-2) + x

P(1) = P(0) * 1

P(0) = 1

(2) Bước thế ngược:

Có kết quả của P(0) chúng ta thay nó vào biếu thức tính P(1) và tính được P(1), có P(1) chúng ta sẽ tính được P(2),…, có P(n-1) chúng ta sẽ tính được P(n), và bài toán đã được giải quyết

Bước thế ngược của bài toán tính S(n) P(0)= 1

P(1) = P(0) * x = x

P(2) = P(1) * x = x*x

P(n) = P(n-1) * x Lưu ý:

 Trong quá trình giải hai bài toán trên giống nhau ở chỗ đều tiến hành giải qua hai bước là bước phân tích và bước thế ngược

 Ở bước phân tích, bài toán lớn được phân tích thành bài toán đồng dạng (nhưng đơn giản hơn) Bước phân tích sẽ dừng lại khi chúng ta phân tích đến bài toán đồng dạng đơn giản nhất mà ở đó chúng ta có thể xách định kết quả một cách trực tiếp

 Ở bước thế ngược giúp chúng ta tuần tự xác định kết quả các bài toán đồng dạng (từ đơn giản đến phức tạp hơn), và cuối cùng chúng ta sẽ xác định được kết quả của bài toán

1.2 Vấn đề Đệ quy là gì?

Vấn đề Đệ quy là vấn đề được định nghĩa bằng chính nó, ví dụ:

 Tính tổng S(n) (tính tổng n số nguyên dương đầu tiên) được định nghĩa thông qua S(n-1) (tính tổng n-1 số nguyên dương đầu tiên)

 Tính P(n) ( tính xn) được định nghĩa thông qua P(n-1) (tính (xn-1)

Vấn đề Đệ quy thường được giải qua hai bước là bước phân tích và bước thế ngược Bài toán (hay vấn đề) giải quyết theo phương pháp Đệ quy cần hai điều kiện sau để hiện hữu (tồn tại) tính Đệ quy

và không bị gọi Đệ quy bất tận (bị loop):

(1) Để hiện hữu (tồn tại) tính Đệ quy – nghĩa là để giải một bài toán chúng ta phải giải các bài toán đồng dạng, để giải bài toán đồng dạng này chúng ta phải giải bài toán đồng dạng khác – phải tồn tại bước Đệ quy Bài toán 1 có bước Đệ quy là S(n) = S(n-1) + n, bài toán 2 có bước Đệ quy là

xn=xn-1*x

(2) Để không bị gọi Đệ quy bất tận (bị loop) thì phải có điều kiện dừng Bài toán 1 có điều kiện dừng là S(1) = 1, bài toán 2 có điều kiện dừng là x0 =1

=> Bài toán Đệ quy là những bài toán này có thể được phân rã thành các bài toán nhỏ hơn, đơn giản hơn nhưng có cùng dạng với bài toán ban đầu Những bài toán nhỏ lại được phân rã thành các bài toán nhỏ hơn Cứ như vậy, việc phân rã chỉ dừng lại khi bài toán con đơn giản đến mức có thể suy

ra ngay kết quả mà không cần phải phân rã nữa Ta phải giải tất cả các bài toán con rồi kết hợp các kết quả đó lại để có được lời giải cho bài toán lớn ban đầu Cách phân rã bài toán như vậy gọi là

"chia để trị" (devide and conquer), là một dạng của Đệ quy

2 Chương trình con Đệ quy

Trang 4

Một chương trình con (hàm hoặc thủ tục) được gọi là Đệ quy nếu trong quá trình thực hiện nó có phần phải gọi đến chính nó

Ví dụ 1: Hàm tính lũy thừa nguyên của một số thực x (tính xn)

Function LT(x: real, n: integer): real;

Begin

If n=0 then LT:=1 else

LT:=LT(x,n-1)*x;

End;

Khi có lệnh gọi hàm, chẳng hạn: a:=LT(3,4);

Thì máy sẽ nhớ là: LT(3,4):=3*LT(3,3) và đi tính LT(3,3);

Kế tiếp máy lại phải ghi nhớ: LT(3,3):=3*LT(3,2) và đi tính LT(3,2);

Kế tiếp máy lại phải ghi nhớ: LT(3,2):=3*LT(3,1) và đi tính LT(3,1);

Kế tiếp máy lại phải ghi nhớ: LT(3,1):=3*LT(3,0) và đi tính LT(3,0);

Theo định nghĩa của hàm LT: LT(3,0):=1;

Máy sẽ quay ngược lại: LT(3,1):=3*1=3;

Rồi tiếp tục quy ngược lại: LT(3,2):=3*3=9; LT(3,3):=9*3=27; LT(3,4):=27*3:=81;

Ví dụ 2: Hàm tính giai thừa của n (tính n!)

Function GT(n: word): longint;

Begin

If n=1 then GT:=1 else

GT:=GT(n-1)*n;

End;

3 Cấu trúc chính của một chương trình con Đệ quy

Một chương trình con Đệ quy vè căn bản gồm 2 phần:

(1) Phần cố định (điều kiện dừng):

Trong đó chứa các tác động của hàm hoặc thủ tục với với một số giá trị của thể ban đầu của tham

số

Trong ví dụ 1: If n=0 then LT:=1;

Trong ví dụ 2: If n=1 then GT:=1;

(2) Phần hạ bậc

Trong đó tác động cần được thực hiện cho giá trị hiện thời của các tham số được định nghĩa bằng các tác động đã được định nghĩa trước đây với kích thước nhỏ hơn của tham số:

Trong ví dụ 1: If n > 0 then LT:=LT(x,n-1)*x;

Trong ví dụ 2: If n > 1 then GT:=GT(n-1)*n;

4 Các loại Đệ quy

4.1 Đệ quy đuôi

Đệ quy đuôi là dạng Đệ quy mà trong một cấp Đệ quy, chỉ có lời gọi Đệ quy duy nhất xuống cấp thấp

Ví dụ 1: Hàm tính giai thừa của n (tính n!)

Funtion GT(n: word): longint;

Trang 5

Begin

If n:=1 then GT:=1 else

GT:=GT(n-1)*n;

End;

Ví dụ 2: Tìm vị trí xuất hiện của số x trong dãy a gồm n số được sắp tăng dần Ta giải bài toán này

bằng tìm kiếm nhị phân

Tư tưởng thuật toán: Xét số ở chính giữa dãy số gội là phần tử giữa, nếu x lớn hơn số này, nghĩa

là x phải nằm ở dãy số phía bên phải phần tử giữa, nếu x nhỏ hơn thi x phải nằm ở dãy số bên trái phần tử giữa, nếu x bằng giữa thì đã tìm được x

Type DaySo=Array[1 100] of integer;

Function Tim(a:Dayso;left, right:integer): integer;

Begin

If (left>right) then Tim:= -1 Else

Begin

Mid:=(right+left) div 2;

If (a[mid]=x) then Tim:=mid Else

If x>a[mid] then Tim(mid+1,right) Else Tim:=Tim(left, mid -1);

End;

End;

Nhận xét: Chúng ta thấy lời gọi Đệ quy xuất hiện hai lần trong hàm (Tim(mid+1,right) và Tim(left,

mid -1), nhưng trong một lần thi hành hàm chỉ một trong hai được thi hành

4.2 Đệ quy nhánh

Đệ quy nhánh là dạng Đệ quy mà trong suốt quá trình thực hiện hàm Đệ quy, lời gọi hàm Đệ quy được thực hiện nhiều hơn một lần

Ví dụ 1: Bài toán tháp Hà Nội

Cho n dĩa tròn có kích thước khác nhau và được đặt xuyên qua một cọc sao cho dĩa nhỏ ở trên dĩa

to Hãy chuyển toàn bộ số dĩa này sang một cọc khác với điều kiện: Khi chuyển, khi chuyển, có thể chuyển dĩa sang một cọc trung gian khác và trên mỗi cọc, dĩa nhỏ luôn nằm trên dĩa lớn

Để đơn giản, gọi cọc nguồn là cọc A, cọc đích là B và cọc trung gian là cọc C Các dĩa được đánh

số theo thứ tự từ 1 đến n Dĩa có số lớn hơn thì đường kính lớn hơn

Thuật giải Đệ quy

Để chuyển n dĩa từ cọc A sang cọc B thì cần:

1 chuyển n-1 dĩa từ A sang C Chỉ còn lại dĩa n trên cọc A

2 chuyển dĩa n từ A sang B

3 chuyển n-1 dĩa từ C sang B cho chúng nằm trên dĩa n

Trang 6

Phương pháp trên được gọi là thuật giải Đệ quy: Để tiến hành bước 1 và 3, áp dụng lại thuật giải

cho n-1 Toàn bộ quá trình là một số hữu hạn các bước, vì đến một lúc nào đó thuật giải sẽ áp dụng cho n = 1 Bước này chỉ đơn giản là chuyển một dĩa duy nhất từ cọc A sang cọc C

Giải thích thuật giải với n=3:

1 chuyển đĩa 1 sang cọc C

2 chuyển đĩa 2 sang cọc B

3 chuyển đĩa 1 từ C sang B sao cho nó nằm lên 2

Vậy ta hiện có 2 đĩa đã nằm trên cọc B, cọc C hiện thời trống

1 chuyển đĩa 3 sang cọc C

2 lặp lại 3 bước trên để chuyển 1 & 2 cho nằm lên 3

Mỗi lần dựng xong tháp từ đĩa i đến 1, chuyển đĩa i+1 từ cọc A là cọc xuất phát, rồi lại di chuyển tháp đã dựng lên đĩa i+1

Program ThapHaNoi;

Var n:integer;

Procedure ChuyenDia(n:integer; nguon,dich:char);

Begin

writeln('chuyen dia: ',n,' tu coc ',nguon,' sang coc ',dich);

End;

Procedure ChuyenChongDia(n:integer; nguon,dich:char);

Var trunggian:char;

Begin

if(nguon<>'A') and (Dich<>'A') then trunggian:='A';

if(nguon<>'B') and (Dich<>'B') then trunggian:='B';

if(nguon<>'C') and (Dich<>'C') then trunggian:='C';

if n=1 then ChuyenDia(n,nguon,dich)

else Begin

ChuyenChongDia(n-1,nguon,trunggian);

ChuyenDia(n,nguon,dich);

ChuyenChongDia(n-1,trunggian,dich);

End;

End;

Begin

Write('Nhap vao so dia: '); readln(n);

ChuyenChongDia(n,'A','B');

readln

End

Nhận xét: Trong một lần thi hành, có hai lời gọi Đệ quy – Đệ quy rẽ nhánh

Ví dụ 2: Liệt kê tất cả hoán vị của một dãy n phần tử khác nhau (tổng số hoán vị của dãy n phần tử

là n!) Chẳng hạn với dãy có 3 phần tử là ABC thì tất cả hoán vị là ABC, ACB, BAC, BCA, CAB, CBA

Tư tưởng của thuật toán này như sau:

Xét các phần tử ai từ 1 đến n (n là chiều dài dãy số - DaySo):

+ Bỏ phần tử ai ra khỏi dãy số

+ Ghi nhận đã lấy ra phần tử ai

+ Hoán vị (dãy số)

Trang 7

+ Đưa phần tử ai vào lại dãy số

Nếu dãy số rỗng thì thứ thự các phần tử được lấy ra chính là một hoán vị

Ví dụ: Dãy số ABC

Bỏ A (còn BC)

Bỏ B (còn C)

Bỏ C -> 1 hoán vị ABC

Bỏ C (còn B)

Bỏ B -> 1 hoán vị ACB

Bỏ B (còn AC)

Bỏ A (còn C)

Bỏ C -> 1 hoán vị BAC

Bỏ C (còn A)

Bỏ A -> 1 hoán vị BCA

Bỏ C (còn AB)

Bỏ A (còn B)

Bỏ B -> 1 hoán vị CAB

Bỏ B (còn A)

Bỏ A -> 1 hoán vị CBA

Program HoanViDaySo;

Var Day: Array[1 10] of char; // mảng lưu các phần tử

SoKyTu,i: Integer;// Số phần tử

DuocChon: Array[1 10] of boolean;//ghi nhận phần tử nào đã lấy ra khỏi dãy

Procedure HoanVi(kytu:string);

Var i:integer;

Chon: Boolean;// =false, không còn ký tự nào để lấy ra nữa, dãy rỗng

Begin

chon:=false;

for i:=1 to SoKyTu do Begin

if (DuocChon[i]=false) then Begin

DuocChon[i]:=True;

HoanVi(KyTu+Day[i]); // Lời gọi Đệ quy được gọi nhiều lần trong vòng lặp DuocChon[i]:=false;

Chon:=true;

End;

End;

if chon=false then writeln(KyTu);// dãy rỗng các phần tử đã chọn theo thứ tự là 1 HV End;

Begin

Write('Nhap vao so phan tu cua day: '); readln(SoKyTu);

For i:=1 to SoKyTu do Begin

Write('Nhap vao phan tu thu ',i,': ');

readln(Day[i]);

End;

For i:=1 to SoKyTu Do DuocChon[i]:=false;

Trang 8

hoanvi('');

readln

End

4.3 Đệ quy tương hỗ tương

Đệ quy hỗ tương là dạng Đệ quy mà trong đó có sự gọi quay vòng như A gọi B, B gọi C rồi C lại gọi A Đây là trường hợp rất phức tạp nhưng cũng có nhiều ví dụ rất hấp dẫn

Ví dụ: Viết chương trình tính X(n) và Y(n) X(n) và Y(n) được xác định bởi công thức sau:

( ) = ( − 1) + ( − 1)

( ) = ( − 1) ∗ ( − 1) A(0) và B(0) =1

Cây nhị phân biểu diễn tính A(3) và B(3) như sau:

Chương trình:

Program TinhXnVaYn;

Var n:byte;

Function TinhYn(n:byte): longint; forward;// Từ khóa dùng để khai báo trước tên hàm.

Function TinhXn(n:byte): longint; forward;// Nếu không có thì không thể sự dụng Đệ quy

Function TinhXn(n:byte): longint; // tương hỗ

Trang 9

Begin

if n=0 then TinhXn:=1

else TinhXn:=TinhXn(n-1)+TinhYn(n-1);

End;

Function TinhYn(n:byte): longint;

Begin

if n=0 then TinhYn:=1

else TinhYn:=TinhXn(n-1)*TinhYn(n-1);

End;

Begin

Write('Nhap vao n: '); readln(n);

Writeln('Gia tri cuar X(',n,') la: ',TinhXn(n));

Write('Gia tri cuar Y(',n,') la: ',TinhYn(n));

readln

End

5 Ưu và nhược điểm của Đệ quy

5.1 Ưu điểm

Đệ quy mạnh ở chỗ có thể định nghĩa một tập hợp rất lớn các tác động chỉ bởi một số ít mệnh đề Một chương trình viết theo giải thuật Đệ quy sẽ tự nhiên hơn đó là:

+ Sáng sủa

+ Dễ hiểu

+ Nêu bật được bản chất của vấn đề

Có nhiều bài toán cho đến nay vẫn chưa có cách giải không Đệ quy

5.2 Nhược điểm

Trong giải thuật Đệ quy các bài toán con có thể được gọi lại rất nhiều lần, dù trước đó kết quả của bài toán con đã được tính rồi Nên thực hiện giải thuật Đệ quy sẽ:

+ Tốn nhiều dung lượng

+ Cực kỳ chậm

6 Khử Đệ quy

Có một số giải thuật Đệ quy thuộc loại tính toán đơn giản có thể được thay thế bởi một giải thuật khác không tự gọi nó, sự thay thế đó được gọi là khử Đệ quy

Tuy nhiên, như vậy không có nghĩa là phải khử Đệ quy bằng mọi giá và cũng không e ngại cũng như ác cảm với việc dùng Đệ quy

6.1 Khử Đệ quy bằng vòng lặp

Quy tắc: Chuyển tham số Đệ quy thành biến đếm của vòng lặp

Phương pháp vòng lặp thường được áp dụng với kiểu Đệ quy đuôi Nếu quan sát thấy hình ảnh “cái đuôi” trong Đệ quy đuôi, ta nhận thấy đó là là một dãy thao tác được lặp đi lặp lại với quy mô

“nhỏ” dần cho đến lúc đạt được trạng thái dừng Đặc tính này cũng tương tự ý niệm của vòng lặp: Lặp lại dãy thao tác cho đến lúc gặp điều kiện dừng Nếu ta có thể chuyển đổi từ quy mô bài toán sang điều kiện thi hành (hoặc điều kiện dừng) của vòng lặp thì ta có thể khử được lời gọi Đệ quy bằng vòng lặp

Ví dụ 1: Tính giai n!

Điều kiện dừng của thật toán Đệ quy là n=2, và đó cũng là điều kiện dừng của vòng lặp

Trang 10

Cách 1: Đệ quy

var n:integer;

Function GT(n:integer): integer;

Begin

if n <=2 then GT:=n

else Gt:=Gt(n-1)*n

End;

Begin

read(n);

write(GT(n));

readln

End

Cách 2: Sử dụng vòng lặp

var n,i:integer;

Gt:longint;

Begin

read(n);

Gt:=1;

for i:=n downto 2 Do Gt:=Gt*i;

write(Gt);

readln

End

Ví dụ 2: Tìm vị trí xuất hiện của số x trong dãy a gồm n số được sắp tăng dần Ta giải bài toán này

bằng tìm kiếm nhị phân

Quy mô bài toán trong trường hợp này chính là chiều dài của dãy số tìm kiếm Nếu chiều dài này bằng 0 thì thuật toán kết thúc Trong thuật toán Đệ quy, điều kiện này được thể hiện bằng biểu thức (left > right) Ta sẽ dùng biểu thức này làm điều kiện dừng của vòng lặp

Program Timkiemnhiphan;

Type Dayso=Array[1 1000] of integer;

Var a:DaySo; n,x,i:integer;

Function Tim(a:dayso): integer;

Var TimThay: Boolean;

left, right, mid: integer;

Begin

Tim:=-1; TimThay:=False; //Ban đầu xem như không tim thấy

left:=1;

right:=n; // tìm trên toàn bộ mảng

While(left<=right) and (timthay=false) Do

Begin

Mid:=(right+left) div 2;

if(a[mid]=x) then Begin

Tim:=mid;

TimThay:=True; // Tìm thấy thì trả về kết quả và thoát ra khỏi vòng lặp End

Else

if x>a[mid] then left:=mid+1 //Thu hẹp dãy số chỉ còn nửa bên phải

else right:=mid-1; //Thu hẹp dãy số chỉ còn nửa bên trái

End;

write('Vi tri gia tri can tim trong day la: ',tim);

Ngày đăng: 05/02/2018, 20:37

TỪ KHÓA LIÊN QUAN

w