Mục đích của đề tài Về nội dung kiến thức ngăn xếp đã có rất nhiều tài liệu đề cập đến, trong chuyên đề này tôi chỉ tổng hợp lại các nội dung kiến thức đã có và chủ yếu là đưa vào áp dụn
Trang 1TRƯỜNG THPT CHUYÊN TỈNH LÀO CAI
TỔ TOÁN - TIN
-*@* -CHUYÊN ĐỀ
NGĂN XẾP (STACK)
Trang 2A, MỞ ĐẦU
1 Lý do chọn đề tài
Ngăn xếp và hàng đợi là hai kiểu dữ liệu trừu tượng rất quan trọng và được sử dụng nhiều trong thiết kế thuật toán Về bản chất, ngăn xếp và hàng đợi là danh sách tức là một tập hợp các phần tử cùng kiểu có tính thứ tự Ngăn xếp được sử dụng rất nhiều trong việc giải quyết các bài toán về đồ thị trong các đề thi học sinh giỏi Tuy nhiên trong quá trình giảng dạy tôi thấy học sinh vẫn còn khó khăn trong việc phân tích bài toán để có thể áp dụng được thuật toán và cài đặt giải bài toán Vì vậy tôi chọn chuyên đề này để giúp học sinh có cái nhìn tổng quan hơn về ngăn xếp và ứng dụng của ngăn xếp trong giải các bài toán cụ thể.
2 Mục đích của đề tài
Về nội dung kiến thức ngăn xếp đã có rất nhiều tài liệu đề cập đến, trong chuyên đề này tôi chỉ tổng hợp lại các nội dung kiến thức đã có và chủ yếu là đưa vào áp dụng để giải một số bài toán cụ thế, để làm tài liệu tham khảo cho học sinh và giáo viên trong quá trình học tập và giảng dạy các đội tuyển học sinh giỏi
B NỘI DUNG
1, Khái niệm:
Trang 3Stack là một kiểu danh sách tuyến tính đặc biệt mà
phép bổ sung và loại bỏ luôn thực hiện ở một đầu gọi là
đỉnh (Top) Có thể hình dung nó như cơ cấu của một hộp
chứa đạn súng trường hoặc súng tiểu liên Khi lắp đạn hay lấy đạn ra cũng chỉ ở đầu hộp Viên đạn vừa lắp vào sẽ ở trên đỉnh hộp và viên đạn lắp vào đầu tiên sẽ ở đáy hộp (Bottom) Viên đạn nạp vào sau cùng lại là viên đạn bắn đầu tiên.
Với nguyên tắc hoạt động của stack “vào sau ra trước” nên nó còn được gọi với tên danh sách kiểu LIFO (Last - In - First - Out).
2, Cài đặt stack
Ta cài đặt ngăn xếp như một mảng chứa các phần tử của ngăn xếp và
một biến top để lưu trữ vị trí của phần tử đỉnh trong ngăn xếp.
Trang 4top : integer;
stack : StackType;
3, Các phép xử lý trên stack
a Khởi tạo stack rỗng: top := 0;
b Kiểm tra một stack có rỗng hay không:
if top = 0 then < stack rỗng >
else < stack không rỗng >;
Trang 5d Thủ tục đẩy một phần tử vào stack:
Procedure Push(x : ElementType); { Đẩy phần tử x vào ngănxếp }
biểu thức số học Chẳng hạn như trong lệnh gán x := a b + c; bộ dịch phải
tạo ra các chỉ thị máy tương tự như sau:
Loa a ; nạp giá trị của ô nhớ a vào thanh ghi dự trữ
Mul b ; nhân giá trị của ô nhớ b với giá trị trong thanh ghi dự trữ
Add c ; cộng thêm giá trị của thanh ghi dự trữ với giá trị trong ô nhớ c Sto x ; lưu trữ giá trị trong thanh ghi dự trữ vào ô nhớ x
Trong đa số các ngôn ngữ lập trình, các biểu thức số học được viết như dạng thông thường của toán học, nghĩa là theo kí pháp trung tố (Infix
Trang 6notation): mỗi toán tử 2 ngôi được đặt giữa 2 toán hạng và có thể thêm dấu ngoặc Nhiều bộ dịch trước hết chuyển các biểu thức trung tố này sang kí pháp hậu tố (Postfix notation) hay tiền tố (Prefix notation) ở dạng hậu tố các toán tử đi sau toán hạng và ở dạng tiền tố thì các toán tử đi trước toán hạng.
tự với kí pháp tiền tố).
5, Chuyển biểu thức từ dạng trung tố sang dạng hậu tố
Để minh hoạ ta xét biểu thức trung tố sau đây: 7 + 2 * 3 Khi đọc biểu thức này từ trái sang phải, giá trị 7 được hiển thị ngay lập tức Tiếp theo là toán tử +, nhưng nó được lưu trữ vì toán hạng bên phải của nó chưa được hiển thị, vì vậy nó được đẩy vào ngăn xếp các toán tử:
Đầu ra Ngăn xếp
Trang 77 +
Tiếp theo là toán hạng 2 được đọc và được hiển thị Lúc này nó phải được xác định là toán hạng bên phải của toán tử + hay là toán hạng bên trái của toán tử tiếp theo Để xác định điều này ta so sánh toán tử + ở đỉnh ngăn xếp với toán tử tiếp theo * Bởi vì * được ưu tiên hơn +, toán hạng 2 là toán hạng bên trái của toán tử * Vì vậy ta đẩy * vào ngăn xếp và tìm toán hạng bên phải của nó:
Đầu ra Ngăn xếp
Dấu kết thúc biểu thức cũng chỉ ra rằng toán hạng bên phải của toán
tử còn lại + trong ngăn xếp được tìm ra, vì vậy nó được lấy ra và hiển thị, ta được biểu thức RPN: 7 2 3 * +
Trang 8Các dấu ngoặc trong biểu thức trung tố không gây khó khăn thực sự nào cả Dấu ngoặc bên trái chỉ ra bắt đầu một biểu thức con và khi đọc nó được đẩy vào ngăn xếp Đến khi gặp dấu ngoặc phải, các toán tử được lấy ra
từ ngăn xếp cho đến khi dấu ngoặc trái tương ứng xuất hiện ở đỉnh Lúc này, biểu thức con ban đầu trong các dấu ngoặc đã được chuyển sang dạng RPN,
vì vậy có thể bỏ qua chúng, vì vậy phép chuyển đổi tiếp tục.
Thuật toán chuyển từ dạng trung tố sang dạng RPN:
1.Khởi động một ngăn xếp rỗng các toán tử.
2 While <không xảy ra lỗi và chưa đạt đến kết thúc của biểu thức trung tố> do
a Đọc phần tử x (hằng số, biến số, toán tử số học, các dấu ngoặc trái và
ngoặc phải) tiếp theo trong biểu thức trung tố.
b Nếu phần tử x là:
- Dấu ngoặc trái: đẩy nó vào ngăn xếp.
- Dấu ngoặc phải: lấy ra và hiển thị các phần tử của ngăn xếp cho đến khi dấu ngoặc trái được đọc Nếu ngăn xếp rỗng thì xảy ra lỗi.
- Toán tử: nếu ngăn xếp rỗng hay x được ưu tiên hơn phần tử ở đỉnh ngăn xếp, đẩy x vào ngăn xếp.
Trang 9Nếu khác, lấy ra và hiển thị phần tử ở đỉnh ngăn xếp; Lặp lại việc
so sánh x với phần tử ở đỉnh ngăn xếp (Dấu ngoặc trái được xem
ưu tiên thấp hơn các toán tử).
- Toán hạng: hiển thị nó.
3.Khi đạt đến kết thúc của biểu thức trung tố, lấy ra và hiển thị các phần tử của ngăn xếp cho đến khi ngăn xếp rỗng.
Cài đặt: chương trình này giả sử các toán hạng, toán tử chỉ gồm 1 kí tự và
giả sử biểu thức trung tố là hợp lệ và chỉ kiểm tra rất ít tính đúng đắn của biểu thức trung tố.
Trang 10Function Pop : char;
Trang 14Read_data;
Convert_to_rpn;
readln;
End
Tính giá trị của biểu thức hậu tố
Để minh hoạ cách tính giá trị biểu thức RPN, ta xét biểu thức sau: 1 5 + 8 4
1 , tương ứng với biểu thức infix: (1+5) (8 (4 1)).
Biểu thức này sẽ được đọc từ trái sang phải cho đến khi gặp một toán tử (ở
ví dụ này là: +) Khi đó hai toán hạng cuối cùng được đọc (1 và 5) và kết hợp với toán tử này:
Trang 15và nhận được giá trị 30 cho biểu thức này.
Phương pháp đánh giá các biểu thức RPN đòi hỏi phải lưu trữ các toán hạng cho đến khi có 1 toán tử được đọc từ trái qua phải Lúc đó 2 toán hạng cuối cùng phải được lấy ra và được kết hợp với toán tử này Điều này cho ta nghĩ đến cấu trúc stack cần được sử dụng để lưu trữ các toán hạng Cứ mỗi lần đọc 1 toán tử, hai giá trị sẽ được lấy ra từ đỉnh ngăn xếp để áp dụng toán tử này cho chúng và kết quả sẽ đẩy vào ngăn xếp.
Thuật toán tính giá trị biểu thức RPN
Trang 162.2 Nếu x là toán hạng thì đẩy nó vào ngăn xếp, còn nếu nó là toán tử thì
thực hiện các bước sau:
- Lấy ra từ đỉnh ngăn xếp hai giá trị (nếu ngăn xếp không chứa 2 phần tử, xảy ra lỗi biểu thức không đúng dạng RPN và thuật toán kết thúc).
- áp dụng toán tử trên vào hai giá trị vừa lấy ra.
- Đẩy giá trị kết quả vào ngăn xếp.
3 Khi gặp dấu kết thúc biểu thức, giá trị của nó là giá trị ở đỉnh của ngăn xếp (và nó phải là giá trị duy nhất trong ngăn xếp).
Cài đặt: chương trình sau tính giá trị của biểu thức hậu tố Giả sử biểu thức
hậu tố là hợp lệ, các toán hạng là các số nguyên chỉ gồm 1 ký tự và chỉ gồm các toán tử +, -, *, / Giả thiết phép chia hai số nguyên cho kết quả nguyên giống như phép toán div.
Trang 17top : integer;
stack : array[1 MaxSize] of integer;
Function Pop : integer;
Trang 19readln;
End
I BÀI TẬP
Bài 1: Tính giá trị của biểu thức
File vào EXPRESS.INP
File ra EXPRESS.OUT
File chương trình EXPRESS.PAS
Giới hạn thời gian 1 giây
Cho một biểu thức số học gồm các số nguyên dương với các phép toán cộng, trừ, nhân, chia (viết là +, -, *, /) và các dấu mở ngoặc ‘(‘, đóng ngoặc ‘)’ Kết quả phép chia hai số nguyên cũng là nguyên (giống như phép toán div trong Pascal) Thứ tự ưu tiên các phép toán hiểu như bình thường, nghĩa là biểu thức trong cặp ngoặc ( ) có độ ưu tiên cao nhất, sau đó đến phép nhân và chia, cuối cùng là phép cộng và trừ.
Hãy tính giá trị của một biểu thức cho trước.
Trang 20Dữ liệu: Dòng đầu tiên trong file vào chứa số nguyên dương n là số lượng
biểu thức cần tính Mỗi dòng trong số n dòng tiếp theo chứa một biểu thức
có độ dài không vượt quá 250 ký tự, không có dấu cách trong biểu thức.
Kết quả: Với mỗi biểu thức trong file vào, ghi ra file ra một dòng tương ứng
chứa giá trị của biểu thức.
Giả thiết tất cả các số liệu và kết quả tính toán kể cả kết quả trung gian chỉ nằm trong giới hạn cho phép của kiểu longint.
Chương trình
PROGRAM Tinh_gia_tri_bieu_thuc;
USES crt;
CONST
Trang 22End;
(* - *) PROCEDURE Push(pt:Kpt);
Begin
inc(Top);stack[Top]:=pt;
End;
(* - *) PROCEDURE Pop(var pt:Kpt);
Begin
pt:=stack[Top];dec(Top);
End;
(* - *) FUNCTION UuTien(tt:char):integer;
{ Ham tim do uu tien cua toan tu so hoc hay dau ( }
Trang 23{ Doc tu vi tri thu i cua bieu thuc lay ra toan tu hoac toan hang
hoac dau '(' hoac dau ')' }
Trang 24(* - *) PROCEDURE XulyDau(d:char);
Var
pt:Kpt;
Stop:boolean;
Trang 25Begin
inc(n);rpn[n]:=th;
End;
(* - *) PROCEDURE ConvertToRPN;
Var
Trang 26Var
pt,pt1,pt2:Kpt;
Trang 28End;
(* - *)BEGIN
Bài 2: Optimal Programs
File vào OPTIMAL.INP
File ra OPTIMAL.OUT
File chương trình OPTIMAL.PAS
Giới hạn thời gian 1 giây
Trang 29Như bạn đã biết, viết chương trình thường là việc không dễ dàng Mọi việc thậm trí trở nên khó khăn nếu chương trình của bạn cần được hoàn thành nhanh nhất có thể Và đôi khi cũng có lý do để làm việc đó Rất nhiều chương trình lớn như hệ điều hành hoặc cơ sở dữ liệu gặp phải sự “tắc nghẽn” - các đoạn mã được thực hiện đi và thực hiện lại, và chiếm một phần lớn thời gian chạy ở đây người ta thường phải viết lại đoạn mã đó bằng hợp ngữ (assembly), từ đó thời gian chạy đạt được nhỏ nhất và sẽ rất quan trọng nếu đoạn mã này được thực hiện hàng tỉ lần.
Trong bài toán này, chúng ta xem xét nhiệm vụ tự động sinh ra mã hợp ngữ tối ưu Cho trước một hàm số (như là một dãy các cặp vào/ra), bạn phải tạo ra một chương trình hợp ngữ ngắn nhất để tính hàm số này.
Các chương trình bạn tạo ra sẽ phải chạy trên một stack cơ sở, nó chỉ
hỗ trợ 5 câu lệnh: ADD, SUB, MUL, DIV và DUP Bốn câu lệnh đầu lấy ra 2 phần tử trên đỉnh stack và đẩy vào stack tương ứng tổng, hiệu, tích hoặc thương nguyên của phép chia (giống phép toán div trong Pascal) của chúng Câu lệnh DUP đẩy thêm vào một phần tử giống phần tử trên đỉnh stack Như vậy, nếu các câu lệnh được áp dụng trên một stack với 2 phần tử trên đỉnh là
a và b thì kết quả của stack như sau:
Trang 30Có 3 trường hợp mà stack rơi vào trạng thái lỗi:
Câu lệnh DIV được thực hiện và phần tử trên đỉnh stack là 0.
Các lệnh ADD, SUB, MUL hoặc DIV được thực hiện trong khi stack chỉ chứa 1 phần tử.
Một phép tính sinh ra giá trị có giá trị tuyệt đối lớn hơn 30000.
Dữ liệu: File vào bao gồm các mô tả một dãy các hàm số Mỗi mô tả bắt đầu
với một dòng chứa một số nguyên n (n 10), là số các cặp vào/ra tiếp theo Hai dòng tiếp theo: dòng thứ nhất chứa n số nguyên x1, x2, , xn (tất cả khác
Trang 31nhau) và dòng thứ hai chứa y1, y2, , yn Các số có giá trị tuyệt đối không vượt quá 30000.
Kết thúc file vào bằng một trường hợp kiểm tra bắt đầu với n = 0 Trường
hợp kiểm tra này là không phải xử lý.
Kết quả: Bạn phải tìm chương trình ngắn nhất tính hàm f, sao cho f(xi) = yi
với mọi i 1, , n Điều này nghĩa là chương trình bạn đưa ra có thể không vào trạng thái lỗi nếu thực hiện các dữ liệu vào xi (mặc dù nó có thể rơi vào trạng thái lỗi đối dữ liệu vào khác) Chỉ xem xét các chương trình có nhiều nhất 10 câu lệnh.
Với mỗi một mô tả hàm, đầu tiên ghi ra số thứ tự của mô tả đó Sau đó ghi ra dãy các câu lệnh làm nên chương trình ngắn nhất tính hàm cho trước này Nếu có nhiều hơn một chương trình như vậy, thì hãy đưa ra chương trình nhỏ nhất theo thứ tự sắp xếp từ điển Nếu không có chương trình có tối đa
10 câu lệnh thì in ra dòng chữ “Impossible” Nếu chương trình ngắn nhất
có không câu lệnh thì in ra “Empty Sequence”.
Ghi một dòng trắng sau mỗi trường hợp kiểm tra.
Trang 321 2 3
1 11 20031
200320030
Program 2Impossible
Program 3Empty sequence
Trang 33inp, out : text;
Function Read_data : integer;
Var i : integer;
Begin
d := d + 1;
read(inp, n);
for i := 1 to n do read(inp, a[i]);
for i := 1 to n do read(inp, b[i]);
if i >= best then exit; { cat nhanh }
if top = 1 then { kiem tra phuong an hien tai }
begin
j := 1;
Trang 34while (j <= n) and (b[j] = stack[top, j]) do j := j + 1;
if j > n then { tim thay phuong an tot hon } begin
if (j <> 3) and (top < 2) then continue;
if (j = 3) and (top = max) then continue;
Trang 355 : { SUB }
for k := 1 to n do s[k] := stack[top - 1, k] - stack[top, k];
end;
Trang 37assign(inp, fi); reset(inp);
assign(out, fo); rewrite(out);
d := 0;
repeat
if Read_data = 0 then break;
Xu_ly;
Trang 38Ghi_kq;
until false;
close(inp); close(out);
End
Bài 3: Hệ thống gián điệp
Một hệ thống gián điệp có N điệp viên được đánh số từ 1 đến N Hệ thống này được tổ chức theo dạng cây Tức là mỗi điệp viên có 1 người lãnh đạo trực tiếp nhưng có thể có nhiều cấp dưới trực tiếp Số 1 là lãnh đạo cao nhất và không có cấp trên Các điệp viên này liên lạc với nhau qua cấp trên hoặc cấp dưới trực tiếp của mình hoặc qua một dãy các điệp viên Để bắt được điệp viên thứ i mất một số tiền là ai Khi một điệp viên bị bắt thì toàn
bộ các mối liên lạc của điệp viên này bị cắt đứt Hệ thống bị vô hiệu hóa khi
không có quá 2 điệp viên liên lạc được với nhau Nhiệm vụ của bạn là bắt
một số điệp viên để vô hiệu hóa hệ thống gián điệp này và chi phí bỏ ra là ít nhất.
Trang 392 9
2 2 2 2 2 2 2 2 2
1 1 2 2 2 3 3 3
4 2
Trang 40mat=array [-3000 3000] of longint;
Var head,ke,next,a,pa,stack,kq,giu:mat;
f:array [1 3000,0 2] of longint;free:array [1 3000] of boolean;
Trang 41Begin
Trang 42end;
j:=next[j];
Trang 44else If tg=f[v,1]then Trace(v,1)
else Trace(v,2); end
else If x=1 then
Trace(v,0)
else If x=2 then
Begin
Trang 45If v=giu[u] then Trace(v,1)
else Trace(v,0); end;
If res=f[1,0] then Trace(1,0)
else If res=f[1,1] then Trace(1,1)
else Trace(1,2);
Writeln(fo,m);
For i:=1 to m do Write(fo,kq[i],' '); end;
Trang 46HTcó 1 bảng chữ nhật N dòng và M cột chứa các chữ cái in thường 2
ô được gọi là kề nhau nếu chúng có chung 1 cạnh.
HD có K xâu ký tự và hỏi HT rằng trong bảng chữ nhật kia có đường
đi nào chứa xâu ký tự này không? Một đường đi bắt đầu xuất phát từ 1 ô bất
kỳ, di chuyển sang các ô kềvà một ô có thể đi lại nhiều lần.
INPUT: HIDDEN.INP
- Dòng 1: N và M
Trang 47- N dòng tiếp theo, mỗi dòng ghi M ký tự chữ cái in thường
Trang 48mat=array [0 50,0 50,0 50] of boolean;Var free:mat;
a:array [1 50] of string;
s:string;
qx,qy,qz:array [1 200000] of longint; n,m,q,f,r:longint;
Trang 49For i:=1 to n do Readln(fi,a[i]); end;
Trang 50
free[u,v,z+1]:=false;
Push(u,v,z+1);
Trang 51end;
end;
Procedure Inkq;
Begin
Trang 52- Mỗi người phải thuộc một nhóm
- Mỗi nhóm phải có ít nhất một người
- Mỗi người trong nhóm phải biết hết những người còn lại trong nhóm
- Sự chênh lệch về số lượng người giữa hai nhóm càng ít càng tốt
Yêu cầu: Cho biết thông tin quan hệ của mỗi nguời, hãy chỉ ra cách chia
nhóm hoặc cho biết không tồn tại cách chia
Trang 53INPUT: GROUP.INP
- Dòng đầu tiên chứa số nguyên N, 1<N100
- Dòng thứ i trong số N dòng tiếp theo ghi danh sách những người mà người thứ i biết được dưới dạng dãy số nguyên kết thúc bằng số 0, các
số cách nhau ít nhất một dấu cách.
OUTPUT: GROUP.OUT
- Dòng thứ nhất ghi hai số nguyên K1 và K2 - số người trong nhóm 1
và số người trong nhóm 2.
- Dòng thứ 2: K1 số nguyên, xác định những người trong nhóm 1
- Dòng thứ 3: K2 số nguyên, xác định những người trong nhóm 2
- Trong trường hợp không tồn tại cách chia: file kết quả chỉ chứa 1 dòng với số -1
- Các số trên một dòng ghi cách nhau ít nhất một dấu cách