Tải bản đầy đủ (.pdf) (15 trang)

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

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (542.25 KB, 15 trang )

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.

Edited by: Nguyễn Khắc Nho

Page 1


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)

Edited by: Nguyễn Khắc Nho

Page 2


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
Edited by: Nguyễn Khắc Nho

Page 3


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;
Edited by: Nguyễn Khắc Nho

Page 4


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
Edited by: Nguyễn Khắc Nho

Page 5


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ố).
Edited by: Nguyễn Khắc Nho

Page 6


+ Đư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;
Edited by: Nguyễn Khắc Nho

Page 7


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)
A(0) và B(0) =1
( ) = ( − 1) ∗ ( − 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ỗ
Edited by: Nguyễn Khắc Nho

Page 8


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
Edited by: Nguyễn Khắc Nho

Page 9


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);
Edited by: Nguyễn Khắc Nho

Page 10


End;
Begin
Write('Nhap vao so can tim: '); readln(x);
Write('nhap vao so phan tu cua day: '); readln(n);
For i:=1 to n do Begin
Write('nhap vao phan tu thu ',i,' cua day: ');
readln(a[i]); // Chú ý nhập dãy số là dãy tăng
End;
Tim(a);

readln
End.
So sách Đệ quy và vòng lặp:
Đệ Quy

Vòng Lặp

CẤU TRÚC LẶP

Xây dựng cấu trúc tùy ý, thường là Sử dụng vòng lặp của NNLT: Có
chương trình con. Tự gọi lại chính hai loại là lặp với số lần biết trước
nó để sinh ra vòng lặp.
và lặp với số lần chưa biết trước.

ĐIỂM DỪNG

Khi gặp trường hợp cơ sở của quá Khi điều kiện duy trì vòng lặp
trình Đệ quy.
không còn thỏa mãn.

QUÁ
LẶP

Đi từ trường hợp ban đầu đến Là vòng lặp đơn thuần. Sau mỗi
TRÌNH trường hợp cơ sở. Tại mỗi bước bước giá trị “biến đếm” thay đổ để
lặp độ phức tạp của bài toán giảm đi đến điểm dừng.
dần.

LẶP VÔ HẠN


SO SÁCH
ƯU NHƯỢC
ĐIỂM

Các bước Đệ quy không thể làm Nếu điều kiện duy trì vòng lặp
đơn giản bài toán. Chương trình không bao giờ sai thì sẽ lặp vô hạn
không thể hội tụ về trường hợp cơ
sở.
Ưu: Đệ quy có thể giải quyết tốt
một số bài toán dạng vòng lặp một
cách dễ dàng hơn. Chương trình
thiết kế đơn giản. Dễ đưa ra
phương án tối ưu, giải quyết được
một số vấn đề vòng lặp thông
thường không giải quyết được.

Ưu: Ít tốn bộ nhớ, dễ dàng trong
việc quản lý các vòng lặp. Quá
trình thiết kế vòng lặp cũng đơn
giản hơn rất nhiều so với Đệ quy.

Nhược: Do có nhiều bài toán con Nhược: Nhiều bài toán vòng lặp
được gọi lại khi đã được gọi trước thông thường không giải quyết
đó rồi, nên Đệ quy rất tốn bộ nhớ, được phải nhờ đến Đệ quy.
quản lý không tốt sẽ bị tràn bộ
nhớ.

6.2. Khử Đệ quy bằng stack tự tạo
Quy tắc: Chỉ đưa vào stack những tham số có ý nghĩa trong quá trình Đệ quy
Phương pháp này thường được dùng trong đệ quy nhánh. Khi có lời gọi Đệ quy, chương trình tự

động Push vào stack toàn bộ biến cục bộ và tham số của hàm đệ quy. Dù ta có tiết kiệm bộ nhớ tối
đa, nhưng chương trình vẫn phải sử dụng lượng biến cục bộ nhất định nào đó. Tuy nhiên, những
biến cục bộ này đôi lúc lại không cần thiết trong quá trình Đệ quy. Trong những trường hợp đó,
Edited by: Nguyễn Khắc Nho

Page 11


người ta tạo ra stack riêng và chỉ push vào stack những biến có ý nghĩa trong quá trình Đệ quy.
Ngoài ra, việc tự tạo một stack riêng còn có một lợi điểm nữa là có thể tạo ra được một stack có
kích thước lớn từ đó có thể tăng độ sâu đệ quy; trong khi đó stack do chương trình tự tạo ra thường
có kích thước rất hạn chế và như vậy sẽ hạn chế độ sâu của đệ quy. Xét cho cùng phương pháp
stack tự tạo không phải phương pháp khử đệ quy (tuy rằng nó làm mất đi các lời gọi đệ quy) mà chỉ
là phương án tối ưu sử dụng bộ nhớ để làm tăng độ sâu của đệ quy mà thôi.
Nhìn chung, khử đệ quy bằng stack là một kỹ thuật khó. Cấu trúc của stack và của một thành phần
sẽ biến động tùy theo chương trình đệ quy. Thông thường, khử đệ quy bằng stack tự tạo đòi hỏi ta
phải tiến hành phân tích thật tỉ mỉ chương trình đệ quy.
Các bước thực hiện:




Khởi tạo stack với số phần tử phù hợp.
Đưa bộ tham số đầu vào stack.
Khi Stack không trống:
+ Lấy bộ tham số ra khỏi stack;
+ Xử lý các tác vụ cơ bản ứng với tham số này. Nếu gặp 1 tác vụ đệ quy thì lại đưa bộ
tham số của tác vụ đệ quy tương ứng vào stack

Ví dụ: Cho một hình kín bất kỳ và một điểm (x0, y0) nằm bên trong hình đó, hãy tô màu phần bên

trong hình đó.

Ta giải bài toán này bằng phương pháp tô loang. Nghĩa là từ điểm (x0, y0) ban đầu, ta tô loang ra 4
điểm kế điểm đó là (x0+1, y0), (x0-1, y0), (x0, y0+1), (x0, y0-1). Và từ 4 điểm này ta lại tiếp tục tô 4
điểm kế tiếp nếu điểm đó chưa được tô màu hoặc có màu trùng với màu biên. Quá trình này cứ tiếp
tục cho đến lúc không còn điểm nào để tô. Người ta gọi thuật toán này là thuật toán FloodFill.

Cách 1: Sử dụng thuật toán đệ quy
Edited by: Nguyễn Khắc Nho

Page 12


Program FloodFill;
Var Matran: Array[0..9,0..9] of Byte;
maubien,i,j: byte;
Procedure Inmatran;
Begin
For i:=0 to 9 do
Begin
For j:=0 to 9 do
write(matran[i,j]:3);
writeln;
End;
End;
Procedure Fill(x,y:integer; Mau:integer);
Begin
If (Matran[x,y]<>MauBien) and (Matran[x,y]<>mau) then
Begin
Matran[x,y]:=mau;

if x<9 then Fill(x+1,y,mau);
if x>0 then Fill(x-1,y,mau);
if y<9 then Fill(x,y+1,mau);
if y>0 then Fill(x,y-1,mau);
End;
End;
Begin
maubien:=1; // màu đường biên
Writeln('Ma tran truoc khi FloodFill');
inmatran;
fill(4,3,2);
Writeln('Ma tran sau khi FloodFill');
inmatran;
readln
End.
Cách 2: Sử dụng stack tự tạo:
Giả sử đã có các chương trình con sau: {chúng ta sẽ tham khảo cách tạo và sử dụng ngăn xếp và và
hàng đợi ở chuyên đề “CẤU TRÚC DỮ LIÊU NÂNG CAO”.
+ Thủ tục Push (x,y:integer) cho phép push vào ngăn xếp tọa độ một điểm
+ Thủ tục Pop (var x,y:integer) cho phép lấy ra tọa độ của một điểm ở đầu ngăn xếp.
+ Hàm IsStackEmpty cho biết stack có rỗng hay không.
Thủ tục tô màu như sau:
Procedure Fill(x0,y0:integer; Mau:Byte);
Var x,y:integer;
Begin
Top:=-1;
Push(x0,y0);
While IsStackEmpty=False Do Begin
Pop(x,y);
Edited by: Nguyễn Khắc Nho


Page 13


If (Matran[x,y]<>MauBien) and (Matran[x,y]<>mau) then
Begin
Matran[x,y]:=mau;
if x<9 then Fill(x+1,y,mau);
if x>0 then Fill(x-1,y,mau);
if y<9 then Fill(x,y+1,mau);
if y>0 then Fill(x,y-1,mau);
End;
End;
End;
7. Một số bài toán.
Bài toán 1: Bài toán tính phần tử thứ n của dãy Fibonacci:
Dãy Fibonacci là dãy vô hạn các số tự nhiên bắt đầu bằng hai phần tử 0 và 1, các phần tử sau đó
được thiết lập theo quy tắc mỗi phần tử luôn bằng tổng hai phần tử trước nó. Công thức truy hồi
của dãy Fibonacci là:

Sơ đồ gọi đệ quy với bài toán tính F(6):

Phần cố định: n=0 hoặc n= 1 thì F(n)=n
Phần hạ bậc: F(n)=F(n-1)+F(n-2)
Code:
Program Fibonacci;
Var n:byte;
Function Fi(n:byte):longint;
Begin
if n<=1 then fi:=n

else fi:=fi(n-1)+fi(n-2);
End;
Begin
Write('Nhap vao vao so n: '); readln(n);
write('So fibonacci thu ',n,' la: ',fi(n));
Edited by: Nguyễn Khắc Nho

Page 14


readln
End.
Bài toán 2: Tính S(n) = 1 + 1/2 + 1/3 + ... + 1/n với n>0
Phần cố định: n=1 thì S(n) = 1.
Phần hạ bậc: S(n)=S(n-1)+1/n.
Code:
Var n:integer;
Function TinhS(n:integer): real;
Begin
if n=1 then TinhS:=1
else TinhS:=TinhS(n-1)+1/n
End;
Begin
Write('Nhap vao so n: '); readln(n);
Write('Gia tri cua day so la: ',TinhS(n):4:1);
readln
End.
Bài toán 3: Tính S(n)=1+1.2+1.2.3+…+1.2.3…n với n>0
Phần cố định: n≤ 1 thì S(n)=n;
Phần hạ bậc: S(n)=S(n-1)+ n!

Code:
Var n:byte;
Function Gt(n:byte): longint;
Begin
if n<=2 then gt:=n
else gt:=gt(n-1)*n;
End;
Function TinhS(n:byte):longint;
Begin
if n<=1 then TinhS:=n
else TinhS:=TinhS(n-1)+Gt(n);
End;
Begin
readln(n);
write(TinhS(n));
readln
End.

Edited by: Nguyễn Khắc Nho

Page 15



×