Tải bản đầy đủ (.docx) (21 trang)

Kỹ thuật đệ quy

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 (200.85 KB, 21 trang )

Chương 1 Kỹ thuật đệ quy

1.1 Kỹ thuật đệ quy
Đệ quy là một thuật toán dùng để đơn giản hóa những bài toán phức tạp bằng cách phân
nhỏ phép toán đó thành nhiều phần đồng dạng. Qua việc giải những bài toán được phân nhỏ
này, những lời giải sẽ được kết hợp lại để giải quyết bài toán lớn hơn.
Một số các ví dụ đệ quy
• Định nghĩa số tự nhiên
o 0 là số tự nhiên
o N là số tự nhiên n-1 là số tự nhiên
• Định nghĩa giai thừa của n
o 0! là 1
o Nếu n>0, n! = n *(n-1)!
Hàm đệ quy : Hàm đệ quy là một hàm trong đó có dùng lời gọi hàm đến chính bản thân nó.
Ví dụ ta có hàm đệ quy như sau:
int Sum(int n)
{
if (n==0)
return 0;
else
return (n+Sum(n-1)); // gọi đệ quy đến chính bản thân hàm sum
}
Khi một hàm đệ quy gọi đến chính nó thì mỗi lần gọi máy sẽ tạo ra tập các biến cục bộ mới
hoàn toàn độc lập với biến cục bộ đã tạo ra trong lần gọi trước. Bao nhiêu lần gọi hàm đệ quy
thì tương ứng với bấy nhiêu lần thoát ra khỏi hàm, mỗi lần ra khỏi hàm thì tập biến cục bộ bị
xóa.
Có một sự tương ứng giữa các lời gọi hàm và lần thoát khỏi hàm theo thứ tự ngược lại: lần
ra khỏi hàm đầu tiên tương ứng với lần gọi hàm cuối cùng.
Ví dụ minh họa hàm đệ quy: tính giai thừa của n (tích của các số từ 1 đến n). Ta có định nghĩa
của giai thừa n như sau: n! = 1.2.3...(n-1).n
hoặc định nghĩa:


n! =



≥−
=
1)!.1(
01
nnn
n
Phương pháp thứ nhất là dùng vòng lặp:
long GT(int n)
{
long result = 1;
for(int i=1; i <= n; i++)
result *= i;
return result;
}
Phương pháp thứ hai là dùng hàm đệ quy:
long Giaithua(int n)
{
if (n == 0) return 1;
else return (n*Giaithua(n-1));
}
Phân tích chương trình thực hiện đệ quy:
Giả sử chương trình có lời gọi hàm như sau
long l = Giaithua(5);
n = 5return 5* Giaithua(4)
n = 4return 4* Giaithua(3)
n = 3return 3* Giaithua(2)

n = 2return 2* Giaithua(1)
n = 1return 1* Giaithua(0)
long l = Giaithua(5)
1
2
6
24
120
Giaithua(5)
Giaithua(4)
Giaithua(3)
Giaithua(2)
Giaithua(1)
n = 0return 1
Giaithua(0)
1
Hình 2.1: Gọi đệ quy của hàm giai thừa.
Lưu ý: Hàm đệ quy dùng nhiều vùng nhớ trên ngăn xếp do đó có thể dẫn đến tràn ngăn xếp. Do
đó nếu một bài toán có thể dùng phương pháp lặp (không đệ quy) để giải quyết thì nên sử dụng
cách này.
Phân loại hàm đệ quy:
 Đệ quy trực tiếp : trong một hàm có lời gọi hàm đến chính bản thân hàm đó.
- Đệ quy tuyến tính : thân hàm gọi một lần đến chính nó:
U
n
a, n =1
r + U
n-1
, n>1
double U(int n, double a, double r)

{
if (n == 1)
return a ;
return r + U(n-1, a, r) ;
}
- Đệ quy nhị phân : thân hàm có hai lần gọi chính nó
U
n
1, n =1, 2
U
n-2
+ U
n-1
, n>2
long Fibo(int n)
{
if (n<2 ) return 1 ;
return Fibo(n-1) + Fibo(n-1) ;
}
- Đệ quy phi tuyến : thân hàm gọi nhiều lần đến nó
U
n
n, n < 6
U
n-5
+ U
n-4
U
n-3
+ U

n-2
+ U
n-1
, n>=6
long U( int n)
{
if (n<6) return n;
long S= 0;
for (int i = 5; i>0; i--)
S+= U(n-i);
return S;
}
- Đệ quy hỗ tương: hai hàm đệ quy gọi nhau
U
n
n, n <5
U
n-1
+ G
n-2
, n>=5
G
n
n-3, n <8
U
n-1
+ G
n-2
, n>=8
long G(int n);

long U( int n)
{
if (n<5)
return n;
return U(n-1) + G(n-2);
}
long G(int n)
{
if (n<8)
return n-3;
return U(n-1) + G(n-2);
}
 Đệ quy gián tiếp : trong một hàm có lời gọi hàm đến một hàm khác và bên trong hàm
này lại có lời gọi hàm đến hàm ban đầu. Ví dụ như hàm F
1
gọi hàm F
2
và bên trong hàm
F
2
lại có lời gọi hàm đến F
1
. Đây được gọi là sự đệ quy gián tiếp.
Thông thường những dạng chương trình đệ quy gián tiếp thì khó theo dõi và gỡ rối, nên khi xây
dựng chương trình loại này phải hết sức cẩn thận.
1.2 Xây dựng một chương trình đệ quy
Phương pháp đệ quy thường được áp dụng cho những bài toán phụ thuộc tham số và có các đặc
điểm sau:
1. Bài toán dễ dàng giải quyết trong một số trường hợp riêng ứng với các giá trị đặc biệt nào
đó của tham số. Trường hợp này gọi là suy biến. Ví dụ như khi tính giai thừa thì giai thừa

của 0 là 1.
2. Trong trường hợp tổng quát, bài toán quy về cùng một dạng nhưng giá trị tham số được
thay đổi. Sau một số lần hữu hạn các bước biến đổi đệ quy thì bài toán trở về trường hợp
suy biến. Ví dụ như n! = (n-1)!. n, khi đó n giảm về 0 thì xảy ra trường hợp suy biến.
Các hàm đệ quy thường có dạng tổng quát như sau:
if (Trường hợp đặc biệt, suy biến)
{
// giải theo cách suy biến, trường hợp này đã có lời giải
}
else // trường hợp tổng quát.
{
// gọi đệ quy với giá trị tham số khác (thay đổi tham số)
}
Ví dụ 1: Tính tổng các số nguyên từ 1 đến N.
∑∑∑



=
+−+=+=
2
1
1
1
)1(
N
i
N
i
N

i
iNNiNi
1 1 1 12 2 2 23 3
3 3
Ta phân tích như sau:
+ Trường hợp đặc biệt N=1 thì kết quả là 1
+ Trường hợp khác ta thực hiện đệ quy: N + Tong(N-1).
Ví dụ 2: tìm USCLN của hai số nguyên dương a, b.
+ Trường hợp đặc biệt khi a = b khi đó USCLN(a, b) = a
+ Trường hợp chung a và b khác nhau ta có thể thực hiện đệ quy như sau:
- USCLN(a, b) = USCLN(a-b, b) nếu a>b
- USCLN(a, b) = USCLN(a, b-a) nếu a<b.
Hàm tìm USCLN đệ quy được viết như sau:
int USCLN(int a, int b)
{
if (a==b)
return a;
else if (a>b)
return USCLN(a-b, b);
else
return USCLN(a, b-a);
}
Ví dụ 3: Tính a
n
.
+ Trường hợp đặc biệt n = 0, kết quả là 1
+ Trường hợp khác, kết quả là a * a
(n-1)
.
1.3 Các ví dụ đệ quy

Trong phần này chúng ta sẽ tìm hiểu một số chương trình đệ quy như sau:
 Tháp Hanoi (Tower of Hanoi) :
Cho 3 cột tháp được đặt tên là C
1
, C
2
, và C
3
. Có N đĩa có đường kính giảm dần và được sắp
như hình vẽ. Hãy dịch chuyển N đĩa đó sang cột C
2
, theo nguyên tắc sau: mỗi lần chỉ dịch được
một đĩa, không được để một đĩa có đường kính lớn nằm trên đĩa có đường kính nhỏ. Ta phân
tích cách thực hiện như sau:
Với N = 2: ta có cách làm như sau: chuyển đĩa bé nhất sang C
3
, chuyển đĩa lớn sang C
2
, chuyển
đĩa nhỏ từ C
3
sang C
2
.
C1
C2 C3
1, 2 qua cọc 3 1, 2 qua cọc 2
3 qua cọc 2
B1 B2 B3
C1

C2 C3
C1
C2
C3
C1
C2 C3
C1
C2 C3
C1
C2 C3
C1
C2 C3
C1
C2 C3
C1
C2 C3
B1.1
B1.2
B1.3 B3.1
B3.2
B3.3
Hình 2.2: Minh họa tháp Hanoi với n =2.
Với N = 3: ta thực hiện với giả thiết đã biết cách làm với N-1 đĩa (2 đĩa trong ví dụ N=3):
chuyển đĩa 1 và 2 sang cọc 3, chuyển đĩa 3 sang cọc 2, chuyển hai đĩa 1, 2 từ cọc 3 sang cọc 2.
Hình 2.3: Minh họa trường hợp N = 3.
C
2
C
3
C

1
C1 C2 C3
Trong trường hợp N = 3 như hình 2.3, thực hiện ba bước để đưa 3 đĩa về cọc 2: gồm B1, B2 và B3.
Với B2 thì đơn giản do chuyển 1 đĩa, còn bước B1 và B3 phải di chuyển nhiều hơn 1 đĩa nên chúng
sẽ bao gồm nhiều bước nhỏ trong đó. B1 gồm {B1.1, B1.2, B1.3} và B2 gồm {B2.1, B2.2, B2.3}.
Cuối cùng cách thực hiện theo các bước: B1.1 ⇒ B1.2 ⇒ B1.3 ⇒ B2 ⇒ B3.1 ⇒ B3.1⇒ B3.3.
Hình 2.4: Tháp Hanoi với n = 4.
Chúng ta định nghĩa hàm DichChuyen chuyển N đĩa từ cọc nguồn, sang cọc đích thông qua
một cọc trung gian (cọc thứ 3 còn lại).
Hàm này định nghĩa như sau:
DichChuyen(N, Nguon, Dich, Trung gian);
Với N = 2 ta diễn tả lại như sau:
DichChuyen(1, C1, C3, C2)
DichChuyen(1, C1, C2, C3)
DichChuyen(1,C3, C2, C1)
Với N = 3 ta diễn tả như sau: thông qua dịch chuyển 2 đĩa
DichChuyen(2, C1, C3, C2)
DichChuyen(1, C1, C2, C3)
DichChuyen(2,C3, C2, C1)
Với N tổng quát ta có
DichChuyen(N-1, C1, C3, C2)
DichChuyen(1, C1, C2, C3)
DichChuyen(N-1,C3, C2, C1)
Trường hợp N =1 ta chỉ cần dịch từ cọc nguồn tới cọc đích không cần cọc trung gian.
Đoạn chương trình C/C++ minh họa như sau:
#include <stdio.h>
void DichChuyen(int N, int C1, int C2, int C3);
int main()
{

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×