3.1. Khái niệm về đệ quy (1/6)
Với phần lập trình viên, để giải quyết bài toán lớn, có
thể sử dụng 1 quá trình dạng đệ quy.
Ta nói một đối tượng là đệ qui nếu đối tượng này bao
gồm chính nó như một bộ phận hoặc đối tượng được
định nghĩa dưới dạng của chính nó.
3.1. Khái niệm về đệ quy (2/6)
Nguyên lý đệ quy cho phép hình thành bài toán từ chính
nó.
Trong tính toán, để giải quyết vấn đề sử dụng hàm đệ quy
(hàm gọi chính nó với tham số thay đổi).
Như vậy, hàm đệ quy là hàm gọi lại chính nó.
Với mỗi bước, hàm thay đổi thông tin đầu vào và cho kết
quả ngày càng gần với mục tiêu của bài toán.
39 trang |
Chia sẻ: thanhle95 | Lượt xem: 647 | Lượt tải: 1
Bạn đang xem trước 20 trang tài liệu Bài giảng Cấu trúc dữ liệu và giải thuật - Bài 3: Đệ quy (Recursion) - Ngô Hữu Phước, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
Lecturer: PhD. Ngo Huu Phuc
Tel: 0438 326 077
Mob: 098 5696 580
Email: ngohuuphuc76@gmail.com
Cấu trúc dữ liệu và giải thuật
Bài 3. Đệ quy (Recursion)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University1
Bài 3. Đệ quy
Nội dung:
Khái niệm về đệ quy.
Ví dụ về đệ quy.
Đệ quy đuôi (Tail Recursion)
Bài toán tháp Hanoi.
Tham khảo:
1. Kyle Loudon – Mastering Algorithms with C
Chapter 3 – Recursion
2. Hanoi Tower (Web page)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University2
3.1. Khái niệm về đệ quy (1/6)
Với phần lập trình viên, để giải quyết bài toán lớn, có
thể sử dụng 1 quá trình dạng đệ quy.
Ta nói một đối tượng là đệ qui nếu đối tượng này bao
gồm chính nó như một bộ phận hoặc đối tượng được
định nghĩa dưới dạng của chính nó.
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University3
3.1. Khái niệm về đệ quy (2/6)
Nguyên lý đệ quy cho phép hình thành bài toán từ chính
nó.
Trong tính toán, để giải quyết vấn đề sử dụng hàm đệ quy
(hàm gọi chính nó với tham số thay đổi).
Như vậy, hàm đệ quy là hàm gọi lại chính nó.
Với mỗi bước, hàm thay đổi thông tin đầu vào và cho kết
quả ngày càng gần với mục tiêu của bài toán.
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University4
3.1. Khái niệm về đệ quy (3/6)
• Giả sử cần tính giai thừa của một số nguyên dương n.
• Giai thừa của n được viết: n!, tích các phần tử từ n đến 1.
• Ví dụ, 5! = (5)(4)(3)(2)(1).
• Có thể thực hiện tính giai thừa bằng vòng lặp thông thường
để tính tích.
• Một cách tiếp cận khác, sử dụng đệ quy, khi đó công thức
tính giai thừa được viết dạng:
n! = (n)(n - 1)(n - 2) . . . (1)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University5
3.1. Khái niệm về đệ quy (4/6)
• Có thể định nghĩa n! theo cách khác, là tích của n và giai
thừa nhỏ hơn.
• Như vậy, ta có n! = n * (n – 1)!.
• Ta xử lý (n - 1)! giống như n!, với tham số nhỏ hơn.
• Ta có, (n - 1)! = (n – 1) * (n - 2)!,
• Tương tự, (n - 2)! = (n – 2) * (n - 3)!, quá trình trên kết thúc
khi n=1.
• Với cách tiếp cận dạng đệ quy, có thể định nghĩa lại cách
tính giai thừa dạng:
n! = iif(n=0, 1, n * (n-1)!)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University6
3.1. Khái niệm về đệ quy (5/6)
Có thể hình dung đệ quy qua 2 bước chính:
quay xuôi (winding) và quay ngược
(unwinding).
Tại bước winding, lời giải bài toán gọi lại chính
nó.
Với bước winding, lời gọi chính nó dừng khi thỏa mãn
điều kiện dừng.
Điều kiện dừng thông thường được định nghĩa là tại
bước đó đã đến bài toán cơ sở, có thể giải trực tiếp
được.
Với mỗi hàm đệ quy cần có ít nhất một điều kiện
dừng, nếu không sẽ lặp vô hạn.
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University7
3.1. Khái niệm về đệ quy (6/6)
Khi bước winding kết thúc, bước unwinding sẽ
được thực hiện, các bài toán nhỏ trên được xem
xét theo thứ tự ngược lại.
Bước này dừng lại khi quá trình đến bài toán gốc. Quá
trình đệ quy kết thúc.
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University8
3.2. Ví dụ về đệ quy (1/6)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University9
Bước Winding
F(4) = 4 * F(3)
F(3) = 3 * F(2)
F(2) = 2 * F(1)
F(1) = 1
Bước UnWinding
F(4) = 4 * 6 = 24
F(3) = 3 * 2 = 6
F(2) = 2 * 1 = 2
F(1) = 1
Ví dụ 1: Tính n!.
F(n) = n* F(n-1) nếu n > 1
F(n) = 1 nếu n = 1 hoặc n = 0
Tính F(4) = ?
Ví dụ 1: Tính n!
Ví dụ 1: Tính n! bằng đệ quy.
#include
#include
long factorial(int);
void main()
{
int n;
printf("Nhap vao mot so: ");
scanf("%d",&n);
printf("Gia tri cua giai thua:
%ld",factorial(n));
getch();
}
@Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University10
long factorial(int n)
{
if((n==0)||(n==1)) return 1;
else return n*factorial(n-1);
}
3.2. Ví dụ về đệ quy (2/6)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University11
Bước Winding
F(a,5) = max(F(a,4),a[5])
F(a,4) = max(F(a,3),a[4])
F(a,3) = max(F(a,2),a[3])
F(a,2) = max(a[1],a[2])
UnWinding Phase
F(a,5) = max(7,1) = 7
F(a,4) = max(5,7) = 7
F(a,3) = max(3,5) = 5
F(a,2) = max(3,2)=3
Ví dụ 2: Tìm max của một dãy số.
F(a,1) = a[1], F(a,2) = max(a[1],a[2])
F(a,n) = max(F(a,n-1),a[n]) if n > 2
Cho a[] = [3,2,5,7,1]. Tính F(a,5) = ?
Ví dụ 2: Tìm max của một dãy số
Ví dụ 2: Tìm max.
#include
#include
int F(int *, int);
int max(int, int);
void main()
{
int n;
int a[100];
printf("Nhap vao so phan tu: ");
scanf("%d",&n);
for(int i=0;i<n;i++)
{
printf("Nhap phan tu a[%d]=",i);
scanf("%d",&a[i]);
}
printf("Gia tri max: %d",F(a,n));
getch(); }
@Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University12
int F(int *a, int n)
{
if (n==1) return a[0];
if (n==2) return max(a[0],a[1]);
if (n>2) return max(F(a,n-1),a[n-1]);
}
int max(int a, int b)
{
if(a>b) return a;
else return b;
}
3.2. Ví dụ về đệ quy (3/6)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University13
Bước Winding
F(a,4) = F(a,3) + a[4]
F(a,3) = F(a,2) + a[3]
F(a,2) = F(a,1) + a[2]
F(a,1) = a[1]
Bước UnWinding
F(a,4) = 11 + 5 = 16
F(a,3) = 8 + 3 = 11
F(a,2) = 1 + 7 = 8
F(a,1) = 1
Ví dụ 3: Tính tổng của một dãy số.
F(a,1) = a[1]
F(a,n) = F(a,n-1)+a[n] if n > 1
Cho a[] = [1,7,3,5], Tính F(a,4) = ?
Ví dụ 3: Tính tổng của một dãy số
Ví dụ 3: Tính tổng một dãy số.
#include
#include
long sum(int*, int);
void main()
{
int n;
int a[100];
printf("Nhap vao so pt: ");
scanf("%d",&n);
for(int i=0;i<n;i++)
{
printf("Nhap pt a[%d]=",i);
scanf("%d",&a[i]);
}
printf("Tong cua cac pt: %ld",sum(a,n));
getch();
}
@Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University14
long sum(int * a, int n)
{
if(n==1) return a[0];
else return sum(a,n-1)+a[n-1];
}
3.2. Ví dụ về đệ quy (4/6)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University15
Bước Winding
F(4) = F(3) + F(2)
F(3) = F(2) + F(1)
F(2) = 1
F(1) = 1
Bước UnWinding
F(4) = 2 + 1 = 3
F(3) = 1 + 1 = 2
F(2) = 1
F(1) = 1
Ví dụ 4: Dãy số Fibonacci.
F(n) = 1 nếu n = 1 hoặc n = 2
F(n) = F(n-1) + F(n-2) nếu n > 2
Tính F(4) = ?
Ví dụ 4: Tính số Fibonacci
Ví dụ 4: Tính số fibonacci.
#include
#include
long fibonacci( int);
void main()
{
int n;
printf("Nhap vao n: ");
scanf("%d",&n);
printf("Fibonacci cua %d: %ld",n,fibonacci(n));
getch();
}
@Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University16
long fibonacci(int n)
{
if((n==1) || (n==2)) return 1;
else return fibonacci(n-1)+fibonacci(n-2);
}
3.2. Ví dụ về đệ quy (5/6)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University17
Bước Winding
F(2,1) = F(F(1,1),0)
F(1,1) = F(F(0,1),0)
F(0,1) = F(1,0)
F(1,0) = 2
Bước UnWinding
F(2,1) = F(3,0) = 4
F(1,1) = F(2,0) = 3
F(0,1) = 2
F(1,0) = 2
Ví dụ 5: Hàm Ackermann.
F(m,0) = m + 1 nếu m >= 0
F(0,n) = F(1,n-1) nếu n > 0
F(m,n) = F(F(m-1,n),n-1) nếu m >= 1 và n > 0
Tính F(2,1) = ?
Ví dụ 5: Hàm Ackermann
Ví dụ 5: Hàm Ackermann
#include
#include
int Ackermann(int, int);
void main()
{
int m, n;
printf("Nhap vao m= ");
scanf("%d",&m);
printf("Nhap vao n= ");
scanf("%d",&n);
printf("Ackermann cua %d va %d:
%d",m,n,Ackermann(m,n));
getch();
}
@Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University18
int Ackermann(int m, int n)
{
if((m>=0) && (n==0)) return m+1;
if((n>0) && (m==0)) return Ackermann(1,n-1);
if((m>=1) && (n>0)) return
Ackermann(Ackermann(m-1,n), n-1);
}
3.2. Ví dụ về đệ quy (6/6)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University19
Ví dụ 6: Pascal Identifier Definition
Character
Character
Digit
Identifiers: Name of Constants, Variables,
Functions, Procedure, Programs, Labels,
3.3. Đệ quy đuôi - Tail Recursion (1/6)
Hàm đệ quy được gọi là đệ quy đuôi - tail recursive – nếu
các lời gọi đệ quy trong hàm là công việc cuối cùng của
quá trình đệ quy.
Trong quá trình đệ quy, các trạng thái của quá trình trước
được lưu cho quá trình sau. Tuy vậy, với đệ quy đuôi, việc
lưu trên là không cần thiết.
Hàm đệ quy đuôi không có bước quay ngược.
Tính chất trên không quá quan trọng vì phần lớn các bộ xử
lý hiện nay thực hiện được điều này tự động.
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University20
3.3. Đệ quy đuôi (2/6)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University21
Bước Winding
F(4,1) = F(3,4)
F(3,4) = F(2,12)
F(2,12) = F(1,24)
F(1,24) = 24
Ví dụ 1: Tính n!
F(n, a) = F(n-1, n*a) nếu n>1
F(n,a) = a nếu n = 1 hoặc n = 0
Tính F(4,1) = ?
Bước UnWinding
Không thực hiện gì
Ví dụ 1: Tính n! bằng đệ quy đuôi
Ví dụ 1: Tính n! bằng đệ quy đuôi.
#include
#include
long factorial_tail(int, int);
void main()
{
int n;
printf("Nhap vao mot so: ");
scanf("%d",&n);
printf("Gia tri cua giai thua:
%ld",factorial_tail(n,1));
getch();
}
@Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University22
long factorial_tail(int n, int a)
{
if (n>1) return factorial_tail(n-1,n*a);
else return a;
}
3.3. Đệ quy đuôi (3/6)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University23
Bước Winding
F(a,5,a[n-1]) = F(a,4,7)
F(a,4,7) = F(a,3,7)
F(a,3,7) =F(a,2,9)
F(a,2,9) = F(a,1,9)
F(a,1,9) = 9
Ví dụ 2: Tìm phần tử Maximum trong chuỗi số.
F(a,1,b) = b,
F(a,n,b) = F(a,n-1,max(b,a[n-1]) nếu n > 1
Cho mảng a[5] = [3,9,5,7,1]. Tính F(a,5,a[5]) = ?
Bước UnWinding
Không thực hiện gì
Ví dụ 2: Tìm max bằng đệ quy đuôi
Ví dụ 2: Tìm max bằng đệ quy đuôi.
#include
#include
int F_tail(int *, int, int);
int max(int, int);
void main()
{
int n;
int a[100];
printf("Nhap vao so phan tu: ");
scanf("%d",&n);
for(int i=0;i<n;i++)
{
printf("Nhap phan tu a[%d]=",i);
scanf("%d",&a[i]);
}
printf("Gia tri max: %d",F_tail(a,n,a[n-1]));
getch(); }
@Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University24
int F_tail(int *a, int n, int b)
{
if (n==1) return b;
else return F_tail(a,n-1,max(b,a[n-2]));
}
int max(int a, int b)
{
if(a>b) return a;
else return b;
}
3.3. Đệ quy đuôi (4/6)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University25
Bước Winding
F(a,4,5) = F(a,3,8)
F(a,3,8) = F(a,2,15)
F(a,2,15) = F(a,1,16)
F(a,1,16) = 16
Ví dụ 3: Tính tổng của chuỗi số.
F(a,1,b) = b
F(a,n,b) = F(a,n-1,b+a[n-1]) nếu n > 1
Cho a[4] = [1,7,3,5]. Tính F(a,4,a[4]) = ?
Bước UnWinding
Không thực hiện gì
Ví dụ 3: Tính tổng bằng đệ quy đuôi
Ví dụ 3: Tính tổng bằng đệ quy đuôi
#include
#include
long sum_tail(int*, int, long);
void main()
{
int n;
int a[100];
printf("Nhap vao so pt: ");
scanf("%d",&n);
for(int i=0;i<n;i++)
{
printf("Nhap pt a[%d]=",i);
scanf("%d",&a[i]);
}
printf("Tong cua cac pt: %ld",sum_tail(a,n-1,a[n-1]));
getch();
}
@Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University26
long sum_tail(int * a, int n, long b)
{
if(n==0)return b;
else return sum_tail(a,n-1,b+a[n-1]);
}
3.3. Đệ quy đuôi (5/6)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University27
Bước Winding
F(4,0,1) = F(3,1,1)
F(3,1,1) = F(2,1,2)
F(2,1,2) = F(1,2,3)
F(1,2,3) = 3
Ví dụ 4: Số Fibonacci.
F(n,a,b) = b nếu n = 1 hoặc n = 2
F(n,a,b) = F(n-1,b,a+b) nếu n > 2
Tính F(4,0,1) = ?
Bước UnWinding
Không thực hiện gì
Ví dụ 4: Tính Fibonacci bằng đệ quy đuôi
Ví dụ 4: Tính Fibonacci bằng đệ quy đuôi.
#include
#include
long fibonacci(int, int, int);
void main()
{
int n;
printf("Nhap vao n: ");
scanf("%d",&n);
printf("Fibonacci cua %d:
%ld",n,fibonacci(n,0,1));
getch();
}
@Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University28
long fibonacci(int n, int a, int b)
{
if (n==1) return b;
else return fibonacci(n-1,b,a+b);
}
3.3. Đệ quy đuôi (6/6)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University29
Bước Winding
F(54,21) = F(21,12)
F(21,12) = F(12,9)
F(12,9) = F(9,3)
F(9,3) = 3
Ví dụ 5: Euler GCD (greatest common divisor) Algorithm
(n > m)
F(n,m) = m nếu n div m = 0
F(n, m) = F(m, n mod m) nếu n mod m 0
Tính F(54,21) = ?
Bước UnWinding
Không thực hiện gì
Ví dụ 5: Tính ước số chung lớn nhất
Ví dụ 5: Hàm Euler GCD
#include
#include
int Ackermann(int, int);
void main()
{
int m, n;
printf("Nhap vao m= ");
scanf("%d",&m);
printf("Nhap vao n= ");
scanf("%d",&n);
printf("Ackermann cua %d va %d:
%d",m,n,Ackermann(m,n));
getch();
}
@Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University30
int Ackermann(int m, int n)
{
if((m>=0) && (n==0)) return m+1;
if((n>0) && (m==0)) return Ackermann(1,n-1);
if((m>=1) && (n>0)) return
Ackermann(Ackermann(m-1,n), n-1);
}
3.4. Tháp Hà nội (1/8)
Bài toán được nhà toán học người Pháp Edouard
Lucas đưa ra vào năm 1883.
Bài toán Tháp Hà nội được giới thiệu trong nhiều
tài liệu về thuật toán. Bên cạnh đó, rất nhiều web
sites cũng đề cập tới bài toán này.
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University31
3.4. Tháp Hà nội (2/8)
Bài toán
Có n chiếc đĩa có kích thước khác nhau và có 3 stacks có
tên A, B, C.
Ban đầu, n đĩa được đặt tại stack A, sao cho không có đĩa
lớn nằm trên đĩa nhỏ.
Nhiệm vụ đặt ra là chuyển tất cả các đĩa ở stack A sang
stack C. Sử dụng stack B làm trung gian trong quá trình
chuyển.
Mỗi lần chuyển 1 đĩa và không có đĩa lớn nằm trên đĩa nhỏ.
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University32
3.4. Tháp Hà nội (3/8)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University33
3.4. Tháp Hà nội (4/8)
Để giải quyết bài toán trên, số bước chuyển đĩa ít
nhất là bao nhiêu?
Để trả lời câu hỏi trên, cần đưa ra cách đệ quy tối ưu
và phân tích chúng.
Có thể phân tích được không trong trường hợp tổng
quát hóa, không chỉ xét 3 đĩa?
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University34
3.4. Tháp Hà nội (5/8)
Giải pháp đệ quy:
Ban đầu, đĩa lớn nhất được đặt tại đáy của stack A.
Giả sử rằng nó được chuyển sang stack C (cách tốt nhất
để tiếp cận bài toán), như vậy n-1 đĩa nhỏ hơn phải được
chuyển sang stack B.
Như vậy, cần chuyển n-1 đĩa nhỏ từ A sang B, sử dụng C
làm stack trung gian.
Sau khi thực hiện được việc trên, cần chuyển n-1 đĩa còn
lại từ B sang C, sử dụng stack A làm stack trung gian.
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University35
3.4. Tháp Hà nội (6/8)
Ký hiệu: "A ==> B" có nghĩa chuyển 1 đĩa trên đỉnh
của A sang B.
Giải thuật:
Function HanoiTower (n, A,B,C)
1. if n < 0 then return
2. HanoiTower (n-1, A,C,B)
3. A ==> B
4. HanoiTower (n-1, C,B,A)
End Function
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University36
3.4. Tháp Hà nội (7/8)
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University37
Phân tích bài toán với n = 2
Tháp Hà nội
#include "stdio.h"
#include "conio.h"
void HanoiTower(int, char, char, char);
void main()
{
int m;
printf("Nhap vao so dia m = ");
scanf("%d",&m);
printf("Ket qua cac buoc \n");
HanoiTower(m,'A','C','B');
getch();
}
@Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University38
void HanoiTower(int m, char a, char b, char c)
{
if(m>0)
{
HanoiTower(m-1,a,c,b);
printf("Chuyen dia %d tu %c sang
%c\n",m,a,b);
HanoiTower(m-1,c,b,a);
}
}
3.4. Tháp Hà nội (8/8)
Thời gian và giới hạn:
Gọi T(n) là số phép chuyển đĩa trong giải thuật để
chuyển n đĩa.
Từ giải thuật, có thể thấy:
T(n) = 0 nếu n < 0
T(n) = 2T(n-1) + 1 nếu n > 0
Giải bài toán trên, ta có:
T(n) = 2n - 1 với mọi n > 0.
Như vậy, T(n) là hữu hạn, và giải thuật sẽ dừng.
@Copyright by PhD. Ngo Huu Phuc, Le Quy Don Technical University39/39