Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 21 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
21
Dung lượng
225,35 KB
Nội dung
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() { int N; printf(“Nhap so dia: “); scanf(“%d”, &N); DichChuyen(N, 1, 2, 3); return 0; } void DichChuyen(int N, int C1, int C2, int C3) { if (N == 1) printf(“%d - > %d”, C1, C2); else { DichChuyen(N-1, C1, C3, C2); DichChuyen(1, C1, C2, C3); DichChuyen(N-1, C3, C2, C1); } } Tìm phần tử lớn nhất trong mảng dùng đệquy : cho mảng a[n], n > 1, hãy tìm phần tử lớn nhất trong mảng a[n]. Ta thử phân tích như sau: ý tưởng là đi từ phần đuôi và so sánh với phần tử cuối cùng của mảng với biến tạm m, chọn ra phần tử lớn nhất ⇒ lưu lại vào m. Bước tiếp theo thực hiện tương tự nhưng lúc này mảng rút ngắn lại còn n-1 phần tử. an m = Max(m, an) Hình 2.5 : Tìm phần tử lớn trong mảng dùng đệquy Hàm đệquy tìm phần tử lớn nhất mô tả như sau: giả sử chỉ số mảng tính từ 1. DeQuyMax(int a[N], int n, int &max)// Gỉa sử n > 0 if ( n ==1) {max = a[1] ; return;} if (max < a[n]) max = a[n]; DeQuyMax(a, n-1, max); Tính tổng các phần tử trong mảng dùng đệ quy: cho dãy a[1:n], gọi hàm Sum là hàm đệquy tính tổng, khi đó tổng của dãy a[1:n] là Sum(a[1:n]) Sum(a[1:n]) = Sum(a[1:n-1]) + a[n] [...]... bài toán khó giải quy t theo hướng không đệquy thì người ta thực hiện quá trình như sau: o Dùng quan niệm đệquyđể tìm giải thuật cho bài toán o Mã hoá giải thuậtđệquy o Khử đệquyđể có một chương trình không đệquy Quá trình trên gọi là khử đệ quy, đôi khi việc khử đệquy cũng không dễ dàng gì, nên nhiều khi cũng phải chấp nhận chương trình đệ quy! 1.4.2 Các trường hợp khử đệquy đơn giản o Hàm... đệquy Thông thường đệquy là phương pháp giúp chúng ta tìm giải thuật cho những bài toán khó Kết quả của giải thuậtđệquy thường rất gọn gàng, dễ hiểu và dễ chuyển thành các chương trình trên các ngôn ngữ lập trình Tuy nhiên, việc xử lý giải thuậtđệquy cũng gây khó khăn cho máy về không gian lưu trữ và thời gian xử lý Vì vậy việc thay thế một chương trình đệquy bằng một chương trình không đệ quy. .. Tổng các phần tử trong mảng Hàm đệquy mô tả bằng mã giả như sau: Sum(int a[], int n) - if ( n == 1) Sum = a[1]; - else Sum = Sum(a, n-1) + a[n]; 1.4 Khử đệquy 1.4.1 Tìm hiểu cơ chế thực hiện hàm đệquy Tại mỗi thời điểm của hàm đệquy được đặc trưng bởi: nội dung các biến và các lệnh cần thực hiện tiếp theo Do đó tại mỗi thời điểm trong tiến trình xử lý của hàm đệquy cần phải lưu trữ cả các trạng... (một hay nhiều biến) P(X): là hàm đệquy phụ thuộc X A(X) và D(X): là các nhóm lệnh không đệquy f(X): là hàm biến đổi x trong lần gọi thứ Pi nếu B(fi(X)) không đúng thì thực hiện lệnh X và gọi Pi+1, ngược lại B(fi(X)) đúng thì thực hiện D(X) và kết thúc quá trình gọi (Pi ko gọi thêm hàm đệquy khác) Ví dụ: Tìm USCLN của hai số dựa vào thuật toán Euclide Giải thuậtđệquy USCLN(m ,n) bằng Euclide như... tính giá trị của dãy dữ liệu mô tả bằng hồi quy: Ví dụ 1: hàm tính giai thừa không đệquy long int GiaiThua( int n) { long int F =1; for (int k = 1; k 0) { temp = m % 2; Push(S, temp); m = m / 2; } while (! Empty(S)) { Pop(S, temp); printf(“%d”, temp); } } Lệnh gọi đệquy với hai lần gọi trực tiếp: Thủ tục đệquy có dạng như sau: P(X) { if C(X) D(X) else { A(X); P(f(X)); B(X); P(g(X)); } } Quá trình thực hiện thủ tục đệquy P(X) như sau: Nếu... + Fibo(0) Fibo(1) = 1 Fibo(3) = Fibo(2) + Fibo(1) Fibo(0) = 1 Fibo(2) = Fibo(1) + Fibo(0) Fibo(1) = 1 Fibo(1) = 1 Fibo(0) = 1 Hình 2.8: Hàm đệquy tính dãy Fibonacci Do đặc điểm của quá trình xử lý một hàm đệ quy: việc thực thi lời gọi đệquy sinh ra lời gọi đệquy mới cho đến khi gặp trường hợp suy biến, do đó cần phải có cơ chế lưu trữ thông tin thoả yêu cầu: o Ở mỗi lần gọi phải lưu trữ thông tin... theo X A(X), B(X) và D(X) : nhóm lệnh không đệquy f(X) : là hàm của X Quá trình thực hiện thủ tục P(X) như sau: Nếu C(X) đúng thì thực hiện D(X) Ngược lại thực hiện A(X), gọi P(f(X)), thực hiện B(X) sau khi hoàn thành P(f(X)) Mỗi lần P(Y) được gọi thì thông tin của B(Y) lại được sinh ra nhưng chưa thực hiện Giả sử quá trình đệquy kết thúc sau k lần gọi đệquy thì chương trình phải thực hiện dãy k thao... chức các chương trình đệquy Thực hiện một chương trình con đệquy theo cách mặc định thường tốn bộ nhớ Do cách tổ chức stack mặc định thích hợp cho mọi trường hợp nên sẽ không tối ưu trong từng trường hợp cụ thể Do đó sẽ tốt khi người lập trình chủ động tạo cấu trúc dữ liệu stack đặc dụng cho từng chương trình đệquy cụ thể Giả sử thủ tục đệquy trực tiếp có cấu trúc như sau : P(X) { if C(X) D(X) ; . niệm đệ quy để tìm giải thuật cho bài toán o Mã hoá giải thuật đệ quy o Khử đệ quy để có một chương trình không đệ quy. Quá trình trên gọi là khử đệ quy, . 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