第三节 栈
“栈”是一种先进后出(First In Last Out)或后进先出(Last In First Out)的数据结构。日常生活中也常能见到它的实例,如压入弹夹的子弹,最先压进去的子弹最后射出,而最后压入的子弹则最先发射出来。
“栈”是一种只能在一端进行插入和删除的特殊的线性表,进行插入和删除的一端称为“栈顶”,而不动的一端称为栈底(如下图)。插入的操作也称为进栈(PUSH),删除的操作也称为出栈(POP)。
出栈 ←─┐ ┌──进栈
│ │ ↓│
├───┤
栈顶→│ an │
├───┤
│ ... │
├───┤
│ a2 │
├───┤
栈底→│ a1 │
└───┘
例1、算术表达式的处理:由键盘输入一个算术表达式(含有+,-,*,/,(,)运算),且运算结果为整数),编一程序求该算术表达式的值。
分析:表达式的运算是程序设计中一个基本的问题,利用栈求解是一种简单易行的方法,下面介绍的是算符优先法。
比如:4+2*3-10/5
我们都知道,四则运算的法则是:(1)先乘除后加减,同级运算从左到右;(2)有括号先算括号。
因此上例的结果是8。
算符优先法需要两个栈:一个是操作数栈,用来存放操作数,记为SN;另一个是操作符栈用来存放运算符,记为SP。具体处理方法如下:
(1)将SN,SP置为空栈;
(2)从左开始扫描表达式,若是操作数压入SN中;若是操作符则与SP的栈顶操作符比较优先级有两种可能:
(a)优先级小于栈顶算符,此时从SN中弹出两个操作数,从SP弹出一个操作符,实施运算,结果压入SN的栈顶。
(b)优先级大于栈顶算符,此时将操作符压入SP中。
(3)重复操作(2)直至表达式扫描完毕,这时SP应为空栈,而SN只有一个操作数,即为最后的结果。
为了方便起见,可以将#作为表达式的结束标志,初始化时在SP的栈底压入#,并将其优先级规定为最低。
下面给出的是计算4+2*3-10/5#的示意图
步骤 SN SP 读入字符 │ 说明
─────────────────┼──────────
1 空 # 4 │ 将4压入SN
2 4 # + │ 将+压入SP
3 4 # + 2 │ 将2压入SN
4 4 2 # + * │ 将*压入SP
5 4 2 # + * 3 │ 将3压入SN
6 4 2 3 # + * - │ -的优先级小于*,因此将SN中的3,2弹出,
│ 将SP中的*弹出,将2*3的结果压入SN中
7 4 6 # + - │ -的优先级小于其左边的+,因此将SN中的
│ 4,6弹出,将SP中的+弹出,将4+6的结果压
│ 入SN中
8 10 # - │ -压入SP中
9 10 # - 10 │ 10压入SN中
10 10 10 # - / │ 将/压入SP中
11 10 10 # - / 5 │ 将5压入SN中
12 10 10 5 # - / # │ #优先级小于/,故SN中的10,5弹出,SP中
│ 的/弹出,将10/5的结果压入SN中
13 10 2 # - # │ #优先级小于-,故SN中的10,2弹出,SP中
│ 的-弹出,将10-2的结果压入SN中
14 8 # # │ #与#相遇,运算结束,SN中的8是最后计算
│ 的结果
解:Pascal程序:
Program lt7_3_1;
uses crt;
const number:set of char=['0’..'9'];
op:set of char=['+','-','*','/','(',')'];
var expr:string;
sp:array[1..100] of char;
sn:array[1..100] of integer;
t,tp,n,tn:integer;
function can_cal(ch:char):boolean;
begin
if (ch='#') or (ch=')') or ((sp[tp] in ['*','/']) and (ch in ['+','-']))
then can_cal:=true else can_cal:=false;
end;
procedure cal;
begin
case sp[tp] of
'+':sn[tn-1]:=sn[tn-1]+sn[tn];
'-':sn[tn-1]:=sn[tn-1]-sn[tn];
'*':sn[tn-1]:=sn[tn-1]*sn[tn];
'/':sn[tn-1]:=sn[tn-1] div sn[tn];
end;
dec(tn);dec(tp);
end;
begin
clrscr;
write('Expression : ');readln(expr);
write(expr+'=');
expr:=expr+'#';
tn:=0;tp:=1;sp[1]:='#';t:=1;
repeat
if expr[t] in number then
begin
n:=0;
repeat
n:=n*10+ord(expr[t])-48;
inc(t);
until not (expr[t] in number);
inc(tn);sn[tn]:=n;
end
else begin
if (expr[t]='(') or not can_cal(expr[t]) then
begin
inc(tp);sp[tp]:=expr[t];inc(t);
end
else if expr[t]=')' then
begin
while sp[tp]<>'(' do cal;
dec(tp);inc(t);
end
else cal;
end;
until (expr[t]='#') and (sp[tp]='#');
writeln(sn[1]);
end.
练习三
1、假设一个算术表达式中可包含三种括号:圆括号“(”和“)”;方括号“[”和“]”以及花括号“{”和“}”,且这三种括号可按任意的次序嵌套使用,试利用栈的运算,判别给定的表达式中所含括号是否正确配对出现。
分析:如果括号只有一种,比如说是“(”和“)”,则判断是否正确匹配可以这样进行:
用一个计数器T来记录左括号与右括号比较的情况,初始时T=0,表达式从左向右扫描,如果遇到左括号则T←T+1,遇到右括号则T←T-1,中间如果出现T<0的情况,说明右括号的个数大于左括号的个数,匹配出错;扫描结束时,如果T<>0,说明左右括号的个数不相等,匹配出错。
但本题的括号有三种,这里用栈可以较好地解决,方法如下:
(1)将“(”,“[”,“{”定为左括号,“)”,“]”,“}”定为右括号,建一个栈来存放左括号,初始时,栈为空;
(2)从左向右扫描表达式,如果是左括号,则将其压入栈中;如果是右括号则
(a)栈顶是与该右括号匹配的左括号,则将它们消去,重复(2)
(b)栈顶的左括号与该右括号不匹配,则匹配错误;
(3)扫描结束时,若栈非空,则匹配错误。
2、计算四个数:由键盘输入正整数A,B,C,D(1<=A,B,C,D<=10);然后按如下形式排列:
A□B□C□D=24;在四个数字之间的□处填上加,减,乘,除四种运算之一(不含括号),输出所有满足条件的计算式子。若不能满足条件,则打印“NORESULT!“。
分析:A,B,C,D四个数的位置已经固定,因此只需将+,-,*,/四种运算符依次放入三个□中,穷举所有可能的情况,再计算表达式的值即可。
数组是大家非常熟悉的,我们可以把它看成是一个长度固定的线性表。本节在研究数组的一些处理方法的同时,还将介绍一些特殊的数组的存储和处理。
例一、矩阵填数:给出N*N的矩阵,要求用程序填入下列形式的数:
25 24 23 22 21
20 19 18 17 16
15 14 13 12 11
10 9 8 7 6
5 4 3 2 1
解:Pascal程序:
Program lt7_4_1;
uses crt;
const max=10;
var a:array[1..max,1..max] of integer;
n:integer;
procedure work1;
var i,j:integer;
begin
for i:=n downto 1 do
for j:=n downto 1 do
a[i,j]:=(n-i)*n+(n-j+1);
end;
procedure print;
var i,j:integer;
begin
writeln;
for i:=1 to n do
begin
for j:=1 to n do
write(a[i,j]:5);
writeln;
end;
end;
begin
clrscr;
write('N=');readln(n);
work;
print;
end.
例二、在一个矩阵(N*N)中,若上三角中的元素全部为零,如下图所示:
为了节省空间,可用一个一维数组来表示这个矩阵。如右图 1 0 0
可表示成为:(1 2 3 3 0 4),在此种方法下,编程完成两个 2 3 0
矩阵的加法。 3 0 4
Program lt7_4_2;
uses crt;
const max=1000;
var a,b,c:array[1..max] of integer;
n:integer;
procedure read_data;
var i:integer;
begin
write('N=');readln(n);
write('A : ');
for i:=1 to n*(n+1) div 2 do
read(a[i]);
write('B : ');
for i:=1 to n*(n+1) div 2 do
read(b[i]);
end;
procedure add;
var i:integer;
begin
for i:=1 to n*(n+1) div 2 do
c[i]:=a[i]+b[i];
end;
procedure print;
var i,j,t:integer;
begin
t:=1;
for i:=1 to n do
begin
for j:=1 to n do
if j>i then write('0':5)
else begin write(c[t]:5);inc(t);end;
writeln;
end;
end;
begin
clrscr;
read_data;
add;print;
writeln('Press any key to exit...');
repeat until keypressed;
end.
分析:本题应弄清楚下三角形矩阵中压缩存储及矩阵的加法运算法则。
练习四
1、数组的鞍点:己知数组X(M,N)中的所有元素为互不相等的整数,编一个程序,先从每一行元素中找出最小的数,然后再从这些最小的数中找出最大的数。打印出这最大的数和它在数组中的下标。
2、稀疏矩阵:所谓的稀疏矩阵是指矩阵中的非0元素很少的一种矩阵,对于这种矩阵,可以用一个三元组表来存储非0元素,从而达到压缩存储,节省空间的目的。具体方法如下:假设v是稀疏矩阵A中第i行第j列的一个非0元素,那么该非0元素可以用一个三元组(i,j,v)表示;请编一个程序,从文件中读入一个用三元组表表示的稀疏矩阵,然后输出该稀疏矩阵的转置矩阵。(文件第一行为:M,N,K,分别表示稀疏矩阵的行和列以及非0元素的个数,以下K+1行是K个三元组)
3、蛇形填数:给一个N*N矩阵按下列方式填数。
1 3 4 10 11
2 5 9 12 19
6 8 13 18 20
7 14 17 21 24 15 16 22 23 25