Đệquy v các phơng phápkhửđệ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áocá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ápkhửđệ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 Khoahọcvà 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 Khoahọcvà 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 Khoahọcvà 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. Đệ 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ó 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