Ứng dụng ngăn xếp để loại bỏ đệ qui của chương trình

Một phần của tài liệu bài giảng cấu trúc dữ liệu (Trang 48 - 53)

II. NGĂN XẾP (STACK)

4.Ứng dụng ngăn xếp để loại bỏ đệ qui của chương trình

Nếu một chương trình con đệ qui P(x) được gọi từ chương trình chính ta nói chương trình con được thực hiện ở mức 1. Chương trình con năy gọi chính nó, ta nói nó đi sđu văo mức 2... cho đến một mức k. Rõ răng mức k phải thực hiện xong thì mức k-1 mới được thực hiện tiếp tục, hay ta còn nói lă chương trình con quay về mức k-1.

Trong khi một chương trình con từ mức i đi văo mức i+1 thì câc biến cục bộ của mức i vă địa chỉ của mê lệnh còn dang dở phải được lưu trữ, địa chỉ năy gọi lă địa chỉ trở về. Khi từ mức i+1 quay về mức i câc giâ trị đó được sử dụng. Như vậy những biến cục bộ vă địa chỉ lưu sau được dùng trước. Tính chất năy gợi ý cho ta dùng một ngăn xếp để lưu giữ câc giâ trị cần thiết của mỗi lần gọi tới chương trình con. Mỗi khi lùi về một mức thì câc giâ trị năy được lấy ra để tiếp tục thực hiện mức năy. Ta có thể tóm tắt quâ trình như sau:

Bước 2: Nếu thoả điều kiện ngừng đệ qui thì chuyển sang bước 3. Nếu không thì tính toân từng phần vă quay lại bước 1 (đệ qui tiếp).

Bước 3: Khôi phục lại câc biến cục bộ vă địa chỉ trở về.

Ví dụ sau đđy minh hoạ việc dùng ngăn xếp để loại bỏ chương trình đệ qui của băi toân "thâp Hă Nội" (tower of Hanoi).

Băi toân "thâp Hă Nội" được phât biểu như sau:

Có ba cọc A,B,C. Khởi đầu cọc A có một số đĩa xếp theo thứ tự nhỏ dần lín trín đỉnh. Băi toân đặt ra lă phải chuyển toăn bộ chồng đĩa từ A sang B. Mỗi lần thực hiện chuyển một đĩa từ một cọc sang một cọc khâc vă không được đặt đĩa lớn nằm trín đĩa nhỏ (hình II.10)

Hình II.10: Băi toân thâp Hă Nội

Chương trình con đệ qui để giải băi toân thâp Hă Nội như sau:

void Move(int N, int A, int B, int C)

//n: số đĩa, A,B,C: cọc nguồn , đích vă trung gian {

if (n==1)

printf("Chuyen 1 dia tu %c

sang %c\n",Temp.A,Temp.B); else {

Move(n-1, A,C,B);

//chuyển n-1 đĩa từ cọc nguồn sang cọc trung gian Move(1,A,B,C);

//chuyển 1 đĩa từ cọc nguồn sang cọc đích Move(n-1,C,B,A);

//chuyển n-1 đĩa từ cọc trung gian sang cọc đích }

}

Quâ trình thực hiện chương trình con được minh hoạ với ba đĩa (n=3) như sau:

move(1,A,B,C) Move(2,A,C,B) move(1,A,C,B) move(1,B,C,A) Move(3,A,B,C) Move(1,A,B,C) move(1,C,A,B) Move(2,C,B,A) move(1,C,B,A) move(1,A,B,C) Mức 1 mức 2 mức 3 Để khử đệ qui ta phải nắm nguyín tắc sau đđy:

Mỗi khi chương trình con đệ qui được gọi, ứng với việc đi từ mức i văo mức i+1, ta phải lưu trữ câc biến cục bộ của chương trình con ở bước i văo ngăn xếp. Ta cũng phải lưu "địa chỉ mê lệnh" chưa được thi hănh của chương trình con ở mức i. Tuy nhiín khi lập trình bằng ngôn ngữ cấp cao thì đđy không phải lă địa chỉ ô nhớ chứa mê lệnh của mây mă ta sẽ tổ chức sao cho khi mức i+1 hoăn thănh thì lệnh tiếp theo sẽ được thực hiện lă lệnh đầu tiín chưa được thi hănh trong mức i.

Tập hợp câc biến cục bộ của mỗi lần gọi chương trình con xem như lă một mẩu tin hoạt động (activation record).

Mỗi lần thực hiện chương trình con tại mức i thì phải xoâ mẩu tin lưu câc biến cục bộ ở mức năy trong ngăn xếp.

Như vậy nếu ta tổ chức ngăn xếp hợp lí thì câc giâ trị trong ngăn xếp chẳng những lưu trữ được câc biến cục bộ cho mỗi lần gọi đệ qui, mă còn "điều khiển được thứ tự trở về" của câc chương trình con. Ý tưởng năy thể hiện trong căi đặt khử đệ qui cho băi toân thâp Hă Nội lă: mẩu tin lưu trữ câc biến cục bộ của chương trình con thực hiện sau thì được đưa văo ngăn xếp trước để nó được lấy ra dùng sau.

typedef struct{ int N;

int A, B, C; } ElementType;

// Chương trình con MOVE không đệ qui void Move(ElementType X){

ElementType Temp, Temp1; Stack S;

MakeNull_Stack(&S); Push(X,&S);

do {

Temp=Top(S); //Lay phan tu dau Pop(&S); //Xoa phan tu dau if (Temp.N==1)

printf("Chuyen 1 dia tu %c sang %c\n",Temp.A,Temp.B); else

{

// Luu cho loi goi Move(n-1,C,B,A) Temp1.N=Temp.N-1;

Temp1.A=Temp.C; Temp1.B=Temp.B; Temp1.C=Temp.A; Push(Temp1,&S);

// Luu cho loi goi Move(1,A,B,C) Temp1.N=1;

Temp1.A=Temp.A; Temp1.B=Temp.B; Temp1.C=Temp.C; Push(Temp1,&S);

//Luu cho loi goi Move(n-1,A,C,B) Temp1.N=Temp.N-1;

Temp1.A=Temp.A; Temp1.B=Temp.C; Temp1.C=Temp.B; Push(Temp1,&S); } } while (!Empty_Stack(S)); }

Minh họa cho lời gọi Move(x) với 3 đĩa, tức lă x.N=3. Ngăn xếp khởi đầu:

3,A,B,C

Ngăn xếp sau lần lặp thứ nhất:

2,A,C,B 1,A,B,C 2,C,B,A

Ngăn xếp sau lần lặp thứ hai

1,A,B,C 1,A,C,B 1,B,C,A 1,A,B,C 2,C,B,A

Câc lần lặp 3,4,5,6 thì chương trình con xử lý trường hợp chuyển 1 đĩa (ứng với trường hợp không gọi đệ qui), vì vậy không có mẩu tin năo được thím văo ngăn xếp. Mỗi lần xử lý, phần tử đầu ngăn xếp bị xoâ. Ta sẽ có ngăn xếp như sau.

2,C,B,A

1,C,A,B 1,C,B,A 1,A,B,C

Câc lần lặp tiếp tục chỉ xử lý việc chuyển 1 đĩa (ứng với trường hợp không gọi đệ qui). Chương trình con in ra câc phĩp chuyển vă dẫn đến ngăn xếp rỗng.

Một phần của tài liệu bài giảng cấu trúc dữ liệu (Trang 48 - 53)