i信息学奥赛

加入QQ群:1025629106,或关注微信公众号:i信息学奥赛,获取更多学习资源。

导航

Posted on 2016-12-10 12:04  shnoip  阅读(470)  评论(0)    收藏  举报

栈是只能在某一端插入和删除的特殊线性表。

用桶堆积物品,先推进来的压在底下,随后一件一件往上堆。取走时,只能从上面一件一件取。堆和取都在顶部进行,底部一般是不动的。

栈就是一种类似桶堆积物品的数据结构,进行删除和插入的一端称栈顶,另一端称栈底。插入称为进栈(PUSH),删除则称为出栈(POP),栈也称为后进先出表(LIFO表)。

一个栈可以用定长为N的数组S来表示,用一个栈指针TOP指向栈项。若TOP=0,表示栈空,TOP=N时,表示栈满。进栈时TOP加1,出栈时TOP减1,当TOP<0时为下溢。栈指针在运算中永远指向栈项。

一、进栈(PUSH)算法
1、若TOP>=n时,则给出溢出信息,作出错处理(进栈前首先检查栈是否已满,满则溢出;不满则运行第2步)
2、置TOP=TOP+1(栈指针加1,指向进栈地址)
3、S(TOP)=X,结束(X为新进栈的元素)

二、出栈(POP)算法
1、若TOP<=0,则给出下溢信息,作出错处理(出栈前先检查是否已为空栈,空则下溢;不空则运行第2步)
2、X=S(TOP),(出栈后的元素赋给X)
3、TOP=TOP-1,结束(栈指针减1,指向栈顶)

进栈、出栈的Pascal实现过程程序:

const n=100;
type stack=array[1..n] of integer;

procedure push(var s:stack; var top,x:integer); //进栈
begin
  if top=n then writeln('overflow')
           else begin top:=top+1;s[top]:=x;end;
end;

procedure pop(var s:stack; var top,y:integer); //出栈
begin
  if top=0 then writeln('underflow')
           else begin y:=s[top];top:=top-1;end;
end;

对于出栈运算中的“下溢”,程序中仅给出了一个标志信息,而在实际应用中,下溢可用来作为控制程序转移的判断标志,是十分有用的。对于入栈运算中的“上溢”,则是一种致命的错误,将使程序无法继续运行,所以要高潮避免。

例1:进制转换
问题描述:读入一个十进制数和n,这个十进制数转换成n进制数

参考程序:
const size=100;
var
  a:array[1..size] of integer;
  n,d,i,j:integer;
begin
  write('Please enter a number(n) base 10:');readln(n);
  write('Please enter a number(d):');readln(d);
  i:=0;
  repeat
    inc(i);
    a[i]:=n mod d;
    n:=n div d;
  until n=0;
  for j:=i downto 1 do write(a[j]:3);
end.

例2:括号的匹配(表达式的合法性检查)
问题描述:
假设一个表达式有英文字母(小写)、运算符(+、-、*、/)和左右小(圆)括号构成,以“@”作为表达式的结束符。请编写一个程序检查表达式中的左右圆括号是否匹配,若匹配,则返回“YES”;否则返回“NO”。假设表达式长度小于255,左圆括号少于20个。

问题分析:
假设输入的字符串存储在c中(var c:string;)
我们可以定义一个栈:
var
  s:array[1..maxn] of char;
  top:integer;
用它来存放表达式中从左往右的左圆括号(maxn=20)

算法的思路为:顺序(从左往右)扫描表达式的每个字符c[i],若是“(”,则让它进栈;若遇到的是“)”,则让栈顶元素出栈;当栈发生下溢或当表达式处理完毕而栈非空时,都表示不匹配,返回“NO”;否则表示匹配,返回“YES”。

参考程序:
const maxn=20;
var c:string;
function judge(c:string):boolean;
var
  s:array[1..maxn] of char;
  top,i:integer;
  ch:char;
begin
  judge:=true;top:=0;
  i:=1;ch:=c[i];
  while ch<>'@' do begin
    if (ch='(') or (ch=')') then
      case ch of
        '(': begin top:=top+1;s[top]:='(';end;
        ')': if top>0 then top:=top-1
                      else begin judge:=false;exit;end;
      end;
    i:=i+1;ch:=c[i];
  end;
  if top<>0 then judge:=false;
end;

begin
  readln(c);
  if judge(c) then writeln('YES')
              else writeln('NO');
end.

例3:编程求一个后缀表达式的值
问题描述:
从键盘读入一个后缀表达式(字符串),只含有0-9组成的运算数及加、减、乘、除(+、-、*、/)四种运算符,每个运算数之间用一个空格隔开,不需要判断给你的表达式是否合法。

算法分析:
后缀表达式的处理过程如下:扫描后缀表达式,凡遇到操作数则将之压进堆栈,遇运算符则从堆栈中弹出两个操作数进行该运算,将运算结束压栈,然后继续扫描,直到后缀表达式被扫描完毕为止,此时栈底元素即为该后缀表达式的值。
如:16-9*(4+3)转换成后缀表达式为:16 9 4 3 +*-
计算结果为-47

参考程序一:
const maxn=20;
var
  stack:array[1..maxn] of integer;
  s:string;
function comp(s:string):integer;
var
  ch:char;
  i,top,x,y,z:integer;
begin
  top:=0;i:=1;ch:=s[i];
  while i<=length(s) do begin
    case ch of
      '0'..'9': begin
                  x:=0;
                  while ch<>' ' do begin
                    x:=x*10+ord(ch)-48;
                    i:=i+1;ch:=s[i];
                  end;
                  top:=top+1;
                  stack[top]:=x;
                end;
      '+': begin x:=stack[top];top:=top-1;y:=stack[top];z:=y+x;stack[top]:=z;end;
      '-': begin x:=stack[top];top:=top-1;y:=stack[top];z:=y-x;stack[top]:=z;end;
      '*': begin x:=stack[top];top:=top-1;y:=stack[top];z:=y*x;stack[top]:=z;end;
      '/': begin x:=stack[top];top:=top-1;y:=stack[top];z:=y div x;stack[top]:=z;end;
    end;
    i:=i+1;ch:=s[i];
  end;
  comp:=stack[top];
end;

begin
  readln(s);
  writeln('result=',comp(s));
end.

 

参考程序二:
type
  stack=record
    data:array[1..100] of real;
    top:0..100;
  end;
var
  s:stack;
  ch:char;
  i:integer;
  x:real;
  a:array[1..30] of char;
function pop(var s:stack):real;  //出栈
begin
  pop:=s.data[s.top];
  s.top:=s.top-1;
end;
procedure push(var s:stack;x:real);  //入栈
begin
  s.top:=s.top+1;
  s.data[s.top]:=x;
end;

begin
  i:=0;
  repeat  //将表达式存入数组a,以“@”作为表达式的结束符
    i:=i+1;read(a[i]);
  until a[i]='@';
  s.top:=0;  //清空栈
  i:=1;  //i为数组a的下标
  ch:=a[i];
  while ch<>'@' do begin
    case ch of
      '0'..'9': begin
                  x:=0;
                  while ch<>' ' do begin x:=x*10+ord(ch)-48;i:=i+1;ch:=a[i];end;
                end;
      '+': x:=pop(s)+pop(s);
      '-': begin x:=pop(s);x:=pop(s)-x;end;
      '*': x:=pop(s)*pop(s);
      '/': begin x:=pop(s);x:=pop(s)/x;end;
    end;
    push(s,x);  //将上面得到的x入栈
    i:=i+1;ch:=a[i];
  end;
  writeln(pop(s));
end.

栈的用途极为广泛,在源程序编译中表达式的计算、过程的嵌套调用和递归调用等都要用到栈。源程序编译中,若要把一个有表达式的赋值语句翻译成正确求值的机器语言,首先应正确地解释表达式,例如:4+8*2-3
一般表达式中会遇到操作数、运算符和语句结束符等,以算术运算符为例,对每种运算赋予一个优先数,如:
运算符:* / + -
优先数:2 2 1 1
(语句结束符“;”的优先数为零)
在运算过程中,优先数高的运算符应先进行运算(但遇到括号时,应另作处理)。按这样的规定,对表达式4+8*2-3自左向右进行运算时,其计算顺序就被唯一地确定下来了。计算机顺序确定后,在对表达式进行编译时,一般设立两个栈,一个称为运算符栈(OPS),另一个称为操作数栈(OVS),以便分别存放表达式中的运算符和操作数。编译程序自左向右扫描表达式直至语句结束,其处理原则是:
1、凡遇到操作数,一律进入操作数栈
2、当遇到运算符时,则将运算符的优先数与运算符栈中的栈顶元素的优先数相比较;若该运算符的优先数大,则进栈;反之,则取出栈项的运算符,并在操作数栈中连续取出两个栈顶元素作为运算对象,并将运算结果存入操作数栈,然后继续比较该运算符与栈顶元素的优先数

对表达式4+8*2-3,当扫描到+号和*号时都要将运算符入栈,接着扫描到-号,其优先数小于*号所以*号退栈,并执行8*2,将结果16再存入操作数栈;再将-号的优先数与运算符栈的栈顶元素+号的优先数相比较,两者相等,所以再将加号退栈,执行4+16,将结果20存入栈,接着由于运算栈已空,所以-号入栈。当扫描到3时,操作数入栈;当扫描到“;”时,其优先数最低,所以-号退栈,并执行20-3,将结果17存入栈,因已扫描到语句结束符,所以表达式的求值结束,结果为17。

例4:模拟计算机处理算术表达式过程,从键盘上输入算术表达式串(只含+、-、*、/运算符,允许含括号),输出算术表达式的值。设输入的且达式串都是合法的。
算法分析:
建立两个栈,一个是操作数栈(number),一个是运算符栈(symbol),根据运算符的优先级对两个栈进行相应的操作。

const
  max=100;
var
  number:array[0..max] of integer;
  symbol:array[1..max] of char;
  s,t:string;
  i,j,p,code:integer;
procedure push;
begin
  inc(p);
  symbol[p]:=s[i];
end;
procedure pop;
begin
  dec(p);
  case symbol[p+1] of
    '+': inc(number[p],number[p+1]);
    '-': dec(number[p],number[p+1]);
    '*': number[p]:=number[p]*number[p+1];
    '/': number[p]:=number[p] div number[p+1];
  end;
end;
function can:boolean;
begin
  can:=true;
  if (s[i] in ['+','-']) and (symbol[p]<>'(') then exit;
  if (s[i] in ['*','/']) and (symbol[p] in ['*','/']) then exit;
  can:=false;
end;
begin
  readln(s);
  s:='('+s+')';
  i:=1;p:=0;
  while i<=length(s) do begin
    while s[i]='(' do begin
      push;inc(i);
    end;
    j:=i;
    repeat
      inc(i);
    until (s[i]<'0') or (s[i]>'9');
    t:=copy(s,j,i-j);
    val(t,number[p],code);
    repeat
      if s[i]=')' then begin
        while symbol[p]<>'(' do pop;
        dec(P);
        number[p]:=number[p+1];
      end else begin
        while can do pop;
        push;
      end;
      inc(i);
    until (i>length(s)) or (s[i-1]<>')');
  end;
  write('result=',number[0]);
end.