Đệ quy v các phơng pháp khử đệ quy PGS. TS. Phạm văn ất Khoa Công nghệ thông tin - Trờng ĐH GTVT Tóm tắt: Đệ quy l công cụ mạnh trong tin học để lập trình các bi toán khó. Tuy nhiên các hm đệ quy thờng đòi hỏi bộ nhớ lớn, vì vậy vấn đề khử đệ quy l rất cần thiết. Trong báo cáo ny trình bầy cấu trúc, nguyên lý hoạt động của hm đệ quy v cách xây dựng một hm không đệ quy tơng ứng. So với các phơng pháp khử đệ quy đã biết, thì mô hình trình bầy ở đây đơn giản v dễ dng áp dụng hơn. Summary: The recursive is a powerfull tool for programming difficult problems. However the recursion functions offen demand a larger memory, so the recursive removal is very neccessary. In this paper, we will present the structure and the activity of recursion functions and a method of creating a corresponding non recursion function. This method is simpler and easier to apply compared with known methods. i. Cấu trúc của hm đệ quy Hàm đệ quy gồm 2 phần: + Phần suy biến gồm một dẫy các câu lệnh và không chứa các lời gọi đệ quy. + Phần tổng quát cũng bao gồm nhiều câu lệnh, nhng bao gồm một hoặc nhiều lời gọi đệ quy (gọi tới chính hàm đang xét). Dới đây, sẽ gọi các câu lệnh không chứa lời gọi đệ quy là các phép toán cơ bản. Ví dụ xét hàm sau: void P(x) // x nguyên dơng { if(x==1) // Suy biến - không có lời gọi đệ quy // chỉ gồm các phép toán cơ bản { in 8 // phép toán cơ bản } else // Tổng quát, sẽ có các lời gọi đệ quy { in x 2 // phép toán cơ bản P(x-1) // Lời gọi ĐQ thứ 1 in x // phép toán cơ bản P(x-1) // Lời gọi ĐQ cuối in 1 // phép toán cơ bản } } ii. Nguyên lý hoạt động 2.1. Trớc khi bắt đầu chơng trinh Đặt một dấu hiệu đặc biệt vào ngăn xếp (ví dụ k = 0) làm dấu hiệu kết thúc hàm 2.2. Cách xử lý lời gọi đệ quy 2.2.1. Đặt giá trị hiện tại của đối vào ngăn xếp (để sau này lấy ra dùng). 2.2.2. Đặt một dấu hiệu vào ngăn xếp (để sau này biết lối trở về - trở về sau lời gọi đệ quy nào). 2.2.3. Căn cứ vào tham số lời gọi đệ quy để thay đổi giá trị của đối. 2.2.4. Nhẩy (trở về) đầu hàm. 2.3. Phần suy biến - trở về 2.3.1. Xử lý các phép toán cơ bản. 2.3.2. Lấy giá trị của đối (x) và dấu hiệu (k) từ đỉnh ngăn xép. 2.3.3. Nhẩy tới đoạn chơng trình xử lý trở về (xem 2.4). 2.4. Phần tổng quát - sẽ gặp các lời gọi đệ quy 2.4.1. Xử lý các phép toán cơ bản. 2.4.2. Xử lý lời gọi đệ quy đầu tiên gặp phải. 2.5. Đoạn chơng trình xử lý trở về 2.5.1. Dựa vào giá trị của k để phân biệt 3 trờng hợp. a. Kết thúc hàm (k=0) b. Trở về từ lời gọi đệ quy i (k=i < m) c. Trở về từ lời gọi cuối (k=m) 2.5.2. Cấu trúc của đoạn chơng trình nh sau: if(k==0) Kết thúc hàm else if(k==1) { 1. Các câu lệnh sau lời gọi ĐQ 1 2. Xử lý lời gọi ĐQ 2 } else if(k==2) { 1. Các câu lệnh sau lời gọi ĐQ 2 2. Xử lý lời gọi ĐQ 3 } else if(k==m) // trỏ về từ lời gọi cuối { 1. Các câu lệnh sau lời gọi ĐQ m 2. Lấy giá trị của đối (x) và dấu hiệu (k) từ đỉnh ngăn xép 3. Nhẩy tới đoạn chơng trình xử lý trở về (Goto 2.4.2) } iii. tổ chức hm không đệ quy 3.1. Hỗ trợ Cần tạo một ngăn xếp và xây dựng các phép toán: a. push(x,k) để đa đối x và dấu hiệu k lên ngăn xếp. b. pop(x,k) để lấy đối x và dấu hiệu k từ đỉnh ngăn xếp. 3.2. Cấu trúc của hàm không đệ quy (dùng goto) gồm 4 phần Phần 1. Khởi đầu. Push(0,0) // Đa dấu hiệu đặc biệt lên ngăn xếp làm dấu hiệu kết thúc hàm Phần 2. Phần tổng quát (đặt nhãn là ĐầuHàm). a. Xử lý các phép toán cơ bản b. Khi gặp lời gọi ĐQ đầu tiên thì: + Push(x,1) + Thay đổi x + Chuyển đến Phần 2 (goto ĐầuHàm) Phần 3. Phần suy biến. a. Xử lý các phép toán cơ bản. b. Lấy giá trị của đối (x) và dấu hiệu (k) từ đỉnh ngăn xép. c. Chuyển đến Phần 4 (goto TrởVề) // Có thể bỏ lệnh này. Phần 4. Xử lý trở về (đặt nhãn là TrởVề) a. Nếu k=0 - kết thúc hàm. b. Nếu 0 < k < m (trở về từ lời gọi ĐQ thứ k). + Xử lý các phép toán cơ bản sau lời gọi ĐQ k. + Push(x,k+1). + Thay đổi x. + Chuyển đến Phần 2 (goto ĐầuHàm) c. Nếu k = m (trở về từ lời gọi ĐQ cuối). + Xử lý các phép toán cơ bản sau lời gọi ĐQ m. + Lấy giá trị của đối (x) và dấu hiệu (k) từ đỉnh ngăn xép. + Chuyển đến phần 4 (Goto TrởVề). iv. Ví dụ Dựa theo các chỉ dẫn của mục 3, dễ dàng xây dựng hàm không đệ quy tơng ứng với hàm đệ quy P(n) (xem mục 1) nh sau: // Xây dựng ngăn xếp int s[100], top=0; void push(int x, int k) { s[top++] = x ; s[top++] = k; } void pop(int &x, int &k) { k= s[ top] ; x= s[ top]; } // Hàm không đệ quy void P(int x) { int k; push(0,0); DauHam: if(x>1) { // Tổng quát cout << x*x << "\n"; push(x,1); x = x-1; goto DauHam; } // x=1 - Suy biến cout << x << "\n"; pop(x,k); TroVe: if(k==0) return; else if(k==1) { cout << x << "\n"; push(x,2); x = x-1; goto DauHam; } else if(k==2) // cuoi { cout << 1 << "\n"; pop(x,k); goto TroVe; } } v. Mô hình khử đệ quy tổng quát dùng goto Dựa trên các ý tởng của các mục 3 và 4, có thể phát biểu cách khử đệ quy tổng quát nh sau. 5.1. Hàm đệ quy tổng quát có thể diễn đạt nh sau P(x) : if (S(x)) // suy biến C(x) else // tổng quát { A1(x); P(f1(x)); // Gọi ĐQ 1 A2(x); P(f2(x)); // Gọi ĐQ 2 Am(x); P(fm(x)); // Gọi ĐQ m Am+1(x); } 5.2. Hàm không đệ quy tơng ứng P(x): int k; //khai báo biến k push(0,0) ; // dấu hiệu kết thúc DauHam: if (!S(x) // không suy biến - tổng quát { A1(x); push(x,1);x=f1(x); goto DauHam; } //suy biến C(x); pop(x,k); TroVe: if(k==0) return ; else if(k==1) // trở về từ lời gọi ĐQ 1 { A2(x); push(x,2);x=f2(x); goto DauHam; } else if(k==m-1) // trở về từ lời gọi ĐQ m -1 { Am(x); push(x,m);x=fm(x); goto DauHam; } else if(k==m) // trở về từ lời gọi ĐQ cuối { Am+1(x); pop(x,k); goto TroVe; } vi. Mô hình khử đệ quy tổng quát dùng while Có thể thay các câu lệnh goto và các nhãn trong mô hình ở mục 5, bằng cách dùng while và break (theo phong cách C/C++) nh sau: P(x): int k; //khai báo biến k push(0,0) ; // dấu hiệu kết thúc while(1) { while(!S(x)) // S(x) sai - tổng quát { A1(x); push(x,1);x=f1(x); } // S(x) đúng - suy biến C(x); pop(x,k); while(1) { if(k==0) return ; else if(k==1) // trở về từ lời gọi ĐQ 1 { A2(x); push(x,2);x=f2(x); break; } else if(k==m-1) // trở về từ lời gọi ĐQ m -1 { Am(x); push(x,m);x=fm(x); break ; } else if(k==m) // trở về từ lời gọi ĐQ cuối { Am+1(x); pop(x,k); } } } Ví dụ: Nếu theo mô hình trên, thì hàm trong mục 4 sẽ nh sau: void P(int x) { int k; push(0,0); while(1) { while(x>1) { //Tổng quát cout << x*x << "\n"; push(x,1); x = x-1; } // Suy biến cout << x << "\n"; pop(x,k); while(1) { if(k==0) return; // kết thúc hàm else if(k==1) // trở về từ lời gọi ĐQ cuối { cout << x << "\n"; push(x,2); x = x-1; break; } else if(k==2) // trở về từ lời gọi ĐQ cuối { cout << 1 << "\n"; pop(x,k); } } } } Tài liệu tham khảo [1]. Đỗ Xuân Lôi. Cấu trúc dữ liệu và giải thuật. NXB Giáo dục, 1993. [2]. Robert Sedgewick. Cẩm nang thuật toán (bản dịch). NXB Khoa học và Kỹ thuật, Hà Nội, 1994. [3]. Phạm Văn ất. Kỹ thuật lập trình C cơ sở và nâng cao. NXB Khoa học và Kỹ thuật, Hà Nội, 1999. [4]. Phạm Văn ất. C++ và lập trình hớng đối tợng. NXB Khoa học và Kỹ thuật, Hà Nội, 2000Ă . nhiên các hm đệ quy thờng đòi hỏi bộ nhớ lớn, vì vậy vấn đề khử đệ quy l rất cần thiết. Trong báo cáo ny trình bầy cấu trúc, nguyên lý hoạt động của hm đệ quy v cách xây dựng một hm không đệ quy. TroVe; } } v. Mô hình khử đệ quy tổng quát dùng goto Dựa trên các ý tởng của các mục 3 và 4, có thể phát biểu cách khử đệ quy tổng quát nh sau. 5.1. Hàm đệ quy tổng quát có thể diễn. Đệ quy v các phơng pháp khử đệ quy PGS. TS. Phạm văn ất Khoa Công nghệ thông tin - Trờng ĐH GTVT Tóm tắt: Đệ quy l công cụ mạnh trong tin học để lập trình các bi toán khó.