第一章 栈及其应用
一、 操作栈 栈指针为 top
(1)进栈(push)
新元素x为整型,栈的最大深度为n。
Procedure push(var stack:arraytype;var p:integer;n:integer;x:integer);
begin
if top=n then begin writeln(‘Stack full!’); end
else begin top:=top+1; stack[top]:= x end;
end;
(2)出栈(pop)
取得栈顶元素的值给x后,再把栈顶指针top的值减1。
procedure pop(var stack:arraytype;var top:integer;var x:integer);
begin
if top=0 then begin writeln(‘Stack empty!’); end
else begin x:=stack[top]; top:=top-1 end;
end;
(3)读栈(readtop)
若top=0,则栈空,无栈顶元素可读,显示出错信息,中止程序;
若top<>0,则回送栈顶元素的值STACK[top]给某个变量。
function readtop(stack: arraytype):integer ;
begin
if stack[top]=0 then writeln(‘underflow’)
else readtop:=stack[top];
end;
二、栈与表达式处理
1、栈与匹配
栈在计算机科学领域有着广泛的应用。比如在编译和运行计算机程序的过程中,就需要用栈进行语法检查(如检查begin和end、“(”和“)”等是否匹配)、计算表达式的值、实现过程和函数的递归调用等。下面举例说明栈在这些方面的应用。
例 括号的匹配(表达式的合法性检查)
[问题描述]
假设一个表达式有英文字母(小写)、运算符(+,—,*,/)和左右小(圆)括号构成,以“@”作为表达式的结束符。请编写一个程序检查表达式中的左右圆括号是否匹配,若匹配,则返回“YES”;否则返回“NO”。假设表达式长度小于255,左圆括号少于20个。
[问题分析]
假设输入的字符串存储在c中(var c:string[255])。
我们可以定义一个栈:var s:array[1..maxn] of char;
top:integer;
用它来存放表达式中从左往右的左圆括号(maxn=20)。
算法的思路为:顺序(从左往右)扫描表达式的每个字符c[i],若是“(”,则让它进栈;若遇到的是“)”,则让栈顶元素出栈;当栈发生下溢或当表达式处理完毕而栈非空时,都表示不匹配,返回“NO”;否则表示匹配,返回“YES”。
[参考程序]
program lx5;
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 {main}
assign(input,'match.in');
assign(output,'match.out');
reset(input);
rewrite(output);
readln(c);
if judge(c) then writeln('YES') else writeln('NO');
close(input);
close(output);
end.
2.编程把中缀表达式转换成后缀表达式
[问题描述]
输入一个中缀表达式,编程输出其后缀表达式,要求输出的后缀表达式的运算次序与输入的中缀表达式的运算次序一致。为简单起见,假设输入的中缀表达式由+(加)、-(减)、*(乘)、/(除)四个运算符号以及左右圆括号和大写英文字母组成,其中算术运算符遵守先乘除后加减的运算规则。假设输入的中缀表达式长度不超过80个字符,且都是正确的,即没有语法错误,并且凡出现括号其内部一定有表达式,即括号内部至少有一个运算符号。以下是一个运行实例。
[样例输入]
X+A*(Y-B)-Z/F
[样例输出]
XAYB-*+ZF/-
[参考程序]
program lx6(input,output);
const max=100;
op_set:set of char=['+','-','*','/'];
letter:set of char=['A'..'Z'];
var expression,result:string;
procedure scan(expression:string);
var i,top1,top2:integer;
ovs:array [1..max] of string[max];
ops:array [1..max] of char;
f:array['#'..'/','#'..'/'] of shortint;
begin
f['+','+']:=1; f['+','-']:=1; f['+','*']:=-1; f['+','/']:=-1; f['+','(']:=-1; f['+',')']:=1; f['+','#']:=1;
f['-','+']:=1; f['-','-']:=1; f['-','*']:=-1; f['-','/']:=-1; f['-','(']:=-1; f['-',')']:=1; f['-','#']:=1;
f['*','+']:=1; f['*','-']:=1; f['*','*']:=1; f['*','/']:=1; f['*','(']:=-1; f['*',')']:=1; f['*','#']:=1;
f['/','+']:=1; f['/','-']:=1; f['/','*']:=1; f['/','/']:=1; f['/','(']:=-1; f['/',')']:=1; f['/','#']:=1;
f['(','+']:=-1;f['(','-']:=-1;f['(','*']:=-1; f['(','/']:=-1; f['(','(']:=-1; f['(',')']:=0; f['(','#']:=2;
f[')','+']:=2; f[')','-']:=2; f[')','*']:=2; f[')','/']:=2; f[')','('):=2; f[')',')']:=2; f[')','#']:=2;
f['#','+']:=-1;f['#','-']:=-1;f['#','*']:=-1; f['#','/']:=-1; f['#','(']:=-1; f['#',')']:=2; f['#','#']:=0;
expression:=expression+'#';
ops[1]:='#';
top1:=0;
top2:=1;
for i:=1 to length(expression) do {逐个扫描}
begin
if expression[i] in letter {是字母就进栈}
then begin top1:=top1+1;
ovs[top1]:=expression[i]
end
else begin {运算符}
while f[ops[top2],expression[i]]=1 do {栈顶运算符优先级高于当前运算符}
begin {取栈顶上面的两个元素运算后,再压栈}
ovs[top1-1]:=ovs[top1-1]+ovs[top1]+ops[top2];
top1:=top1-1;
top2:=top2-1
end;
if f[ops[top2],expression[i]]=0
then top2:=top2-1 {优先级相同,则抵消}
else begin top2:=top2+1;{栈顶运算符优先级低于当前运算符,则压栈}
ops[top2]:=expression[i]
end;
end
end;
result:=ovs[1] {返回结果}
end;
begin{main}
write('Input a expression:');
readln(expression);
scan(expression);
writeln('The result is: ',result)
end.
3.编程求一个后缀表达式的值
[问题描述]
从键盘读入一个后缀表达式(字符串),只含有0-9组成的运算数及加(+)、减(—)、乘(*)、除(/)四种运算符。每个运算数之间用一个空格隔开,不需要判断给你的表达式是否合法。以@作为结束标志。
分析:先读入后缀表达式(字符串)。后缀表达式的处理过程如下:扫描后缀表达式,凡遇操作数则将之压进堆栈,遇运算符则从堆栈中弹出两个操作数进行该运算,将运算结果压栈,然后继续扫描,直到后缀表达式被扫描完毕为止,此时栈底元素即为该后缀表达式的值。
程序:
program lx7_1;
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)-ord('0');
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;{while}
comp:=stack[top];
end;
begin{main}
writeln('input a string(@_over):');
readln(s);
writeln('result= ',comp(s));
readln;
end.
[参考程序2]
program lx7_2;
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
i:=i+1; read(a[i]); {将表达式存入数组a}
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)-ord(‘0’);
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]; {继续扫描a数组}
end;
writeln(pop(s))
end.
4.输入一个实数中缀表达式(由0-9组成的运算数、加(+)、减(-)、乘(*)、除(/)四种运算符、左右小括号、小数点组成。注意“-”也可作为负数的标志),判断表达式是否合法,如果不合法,请输出“NO”;否则求出后缀表达式的值并输出。
注意:必须用栈操作,不能直接输出表达式的值;
8*-5是不合法的,必须写成8*(-5);12.和.5也算合法,分别表示12.0和0.5;
[参考程序]
program lx8;
const max=100;
list_1:set of char=['*','/','+','-','.','(',')','0'..'9'];
list_2:set of char=['*','/','+','-'];
number:set of char=['0'..'9'];
zero=0.0000000001;
var sin,tmp:string;
result:real;
procedure outerror;
begin
writeln('No');
halt;
end;
function error(s:string):boolean;
var i,j,k:longint;
f:boolean;
begin
f:=false;
k:=0;
for i:=1 to length(s) do
begin
if not(s[i] in list_1) then f:=true;
if (s[i] in list_2)and(s[i-1]in list_2) then f:=true;
if (s[i]='(')and(s[i-1]=')') then f:=true;
if s[i]='(' then inc(k);
if s[i]=')' then dec(k);
end;
if k<>0 then f:=true;
error:=f;
end;
procedure dealwithnegative(var s:string);
var i,j,k:longint;
tmp:string;
begin
tmp:='';
i:=1;
for i:=1 to length(s) do
begin
if (s[i]='-')and
((i=1)or(s[i-1]='('))then
tmp:=tmp+'0';
tmp:=tmp+s[i];
end;
s:=tmp;
end;
function convert(s:string; var i:longint):real;
var r:real;
tmp:string;
p:longint;
begin
tmp:='';
while (s[i] in number)or(s[i]='.') do
begin
tmp:=tmp+s[i];
inc(i);
end;
i:=i-1;
val(tmp,r,p);
if p<>0 then outerror;
convert:=r;
end;
function calc(r1,r2:real; ch:char):real;
begin
case ch of
'+':calc:=r1+r2;
'-':calc:=r1-r2;
'*':calc:=r1*r2;
'/':begin
if abs(r2)<zero then begin writeln('divided by zero');halt;end;
calc:=r1/r2;
end;
end;
end;
procedure scan(sin:string;var result:real);
var i,j,top1,top2:longint;
ovs:array [1..max] of real;
ops:array [1..max] of char;
f:array['#'..'/','#'..'/'] of longint;
begin
f['+','+']:=1; f['+','-']:=1; f['+','*']:=-1; f['+','/']:=-1; f['+','(']:=-1; f['+',')']:=1;
f['-','+']:=1; f['-','-']:=1; f['-','*']:=-1; f['-','/']:=-1; f['-','(']:=-1; f['-',')']:=1;
f['*','+']:=1; f['*','-']:=1; f['*','*']:=1; f['*','/']:=1; f['*','(']:=-1; f['*',')']:=1;
f['/','+']:=1; f['/','-']:=1; f['/','*']:=1; f['/','/']:=1; f['/','(']:=-1; f['/',')']:=1;
f['(','+']:=-1;f['(','-']:=-1;f['(','*']:=-1; f['(','/']:=-1; f['(','(']:=-1; f['(',')']:=0;
if error(sin) then outerror;
dealwithnegative(sin);
sin:=sin+')';
ops[1]:='(';
top1:=0; top2:=1; i:=1;
while i<=length(sin) do
begin
if (sin[i] in number)or(sin[i]='.')
then begin
top1:=top1+1;
ovs[top1]:=convert(sin,i);
end
else begin
while f[ops[top2],sin[i]]=1 do
begin
ovs[top1-1]:=calc(ovs[top1-1],ovs[top1],ops[top2]);
top1:=top1-1;
top2:=top2-1;
end;
if f[ops[top2],sin[i]]=0
then top2:=top2-1
else begin
top2:=top2+1;
ops[top2]:=sin[i];
end;
end;
inc(i);
end;
result:=ovs[1];
end;
begin {main}
write('Input a expression:');
readln(sin);
scan(sin,result);
writeln('The result is: ',result:0:3);
end.
第二章 队列及其应用
一、操作简单队列
(1)顺序存储的队列数据结构定义如下:
const max=10000;
type arraytype=array[0..max] of elementtype; {max为队列可达到的最大深度}
var Q:arraytype;
front,rear:integer;
(2)进队
procedure addQ(var Q:arraytype;var rear:integer; n:integer; x:elementtype);
begin
if rear=n
then begin writeln(‘Queue full!’); end
else begin rear:=rear+1; Q[rear]:=x end;
end;
(3)出队
procedure deleteQ(var Q:arraytype;var front,rear:integer;var x:elementtype; n:integer);
begin
if front=rear
then begin writeln(‘Queue empty!’); end
else begin front:=front+1; x:=Q[front] end;
end;
二、操作循环队列
(1)进队
procedure addQ(var Q:arraytype;var rear:integer; n:integer; x:elementtype);
var t:integer;
begin
t:=(rear mod n) +1
if t=front
then begin writeln(‘Queue full!’); end
else begin rear:=t; Q[rear]:=x end;
end;
(2)出队
procedure deleteQ(var Q:arraytype; var front:integer; var x:elementtype; n:integer);
begin
if front=rear
then begin writeln(‘Queue empty!’); end
else begin front:=front mod n+1; x:=Q[front] end;
end;
三、 广义表及其的操作
例 广义表
[问题描述]
有一个表L={a1,a2,…,an},其中L为第一个广义表的表名,ai为表元素(1≤i≤n)。当ai为一位十进制整数时,表示为元素;当ai为大写字母时,表示另一个表,但不能循环定义。例如,下列定义是合法的(约定L是第1个表的表名):
L=(3,4,3,4,K,8,0,8,P)
K=(5,5,8,9,9,4)
P=(4,7,8,9)
现在请你编程输入全部广义表(每行一个广义表),输出两行信息,第1行为最大元素值,第2行为全部广义表中数的和。
[问题分析]
广义线性表(简称广义表)是线性表的一种推广。如果允许构成线性表的元素本身又可以是线性表的话,则该线性表即为广义表。由此可见,广义表是一种递归定义的表,允许其元素可以是本身的一个子表。如果需要将广义表的所有元素排成一个线性序列的话,则必须指明第1个广义表的表名。我们可以对广义表进行计算,例如,求出所有元素中的最大元素,或者计算表中所有元素的和。设:
const lmax = 100; {广义表串长的上限}
type tabtype = record {广义表的数据类型}
length: 0..lmax; {表长}
element :array[1..lmax] of char; {表的数据序列}
end;
queue = record {队列的数据类型}
base: array[0..lmax] of char; {队列}
front, rear: 0..lmax; {首尾指针}
end;
var T : array[‘A’.. ‘Z’] of tabtype; {T[ch]即表名为ch的广义表}
q: queue; {队列}
(1)构造广义表T
每一个广义表读入后都存储在一个字符串s中,s中的所有数字和大写字母都看作是表的元素。q队列依次存储广义表L中的字母元素(即表名)。先读入广义表L,将其元素存入T[L]中,表L中出现的字母依次进入队列q;若队列q不空,队首元素ch出队,读入广义表ch,将其元素存入T[ch]中,表ch中出现的字母再依次入队,……直至队列空时为止。
广义表表名的入队运算EnQueue(q,s[i])和出队运算DeQueue(q)如下:
procedure EnQueue(var q: queue; c: char); {表名c从队尾进入}
begin
q.rear := q.rear + 1;
q.base[q.rear] := c;
end;
function DeQueue(var q: queue) :char;
begin
q.front := q.front + 1;
DeQueue := q.base[q.front];
end;
由此得出广义表的构造过程:
for ch:= ‘A’ to ‘Z’ do T[ch].length := 0; {置所有广义表为空}
q.front :=0;q.rear := 0; {队列的首尾指针初始化}
EnQueue(q, ‘L’); {表名L入队}
while q.front <> q.rear do {若队列非空,则一直做}
begin
ch:=DeQueue(q); {取出队首的表名}
write(ch, ‘=’); {输入表名为ch的广义表串}
readln(s);
i:=1; {从广义表串的第1个字符开始取元素}
while s[i]<> ‘(’ do i:=i+1;
while s[i]<> ‘)’ do
begin
s[i]:=upcase(s[i]); {将第i个字符统一改为大写}
if s[i] in [‘A’.. ‘Z’, ‘0’.. ‘9’]
then begin {若第i个字符为广义表元素,则该字符进入广义表}
inc(T[ch].length);
T[ch].element[T[ch].length]:=s[i];
If s[i] in [‘A’.. ‘Z’] then EnQueue(q,s[i]);
{若第i个字符为表名,则入队}
End;
Inc(i); {分析输入串的下一个字符}
End;
End;
通过上述方法,将所有广义表存储在T中。这样就不难计算广义表L的最大值和数和了。
(2)计算广义表L的最大值
设广义表L中的最大元素值为m,m设为全局变量。初始时,m=‘0’。我们从T[L]开始搜索:
i)若表中的第i个元素(T[L].element[i])为数字码,则m与之比较,若m小于该数码,则被取代;
ii)若表中的第i个元素为字母c,则递归地搜索以该字母为表名的广义表T[c]。
依此类推,直至搜索了广义表L的所有元素为止。如下:
function maxnumber(c:char):char; {计算和返回表名为c的广义表T[c]的最大值}
var ch, m: char;
i: integer;
begin
max := ‘0’; {初始化最大数码}
for i:=1 to T[c].length do {搜索广义表T[c]的每一个元素}
begin
ch:=T[c].element[i]; {取出广义表T[c]的第i个元素}
if ch in [‘A’..‘Z’] then m:=maxnumber(ch){若该元素为表名则递归地计算最大数码}
else m:=ch; {若该元素为数码,则记下}
if max<m then max:=m; {若数码m为目前广义表T[c]中的最大数码,则记下}
end;
maxnumber := max; {返回广义表T[c]的最大数码}
end;
显然,主程序可以通过语句:
writeln(‘The max number in table L is: ’, maxnumber(‘L’));
直接计算和输出广义表L的最大值。
(3)计算广义表L的数和
设k为当前数和,初始时,k=0。我们从T[L]开始搜索:
i)若表中的第i个元素(T[L].element[i])为数字码,则该数字码对应的数值累计入k;
ii)若表中的第i个元素为字母c,则递归搜索以该字母为表名的广义表T[c]。
依此类推,直至搜索了广义表L的所有元素后为止。对应的k即为广义表K 中的数和。这一递归计算过程由函数total描述:
function total(c: char):integer; {计算和返回表名为c的广义表T[c]的数和}
var k, i, m: integer;
ch: char;
begin
k:=0; {初始化数和}
for i:=1 to T[c].length do {搜索广义表T[c]的每一个元素}
begin
ch:=T[c].element[i]; {取出广义表T[c]的第i个元素}
if (ch in [‘A’..’Z’]) then m:=total(ch) {若该元素为表名,则递归计算数和}
else m:=ord(ch)–ord(‘0’); {若该元素为数码,则记下}
k:=k+m; {累计数和}
end;
total := k; {返回广义表T[c]的数和}
end;
显然,主程序可以通过语句:
writeln(‘The total is :’, total(‘L’));
直接计算和输出广义表L的数和。
第三章 串
一 KMP算法
[算法描述]
暂无...
[源程序]
procedure get_next(s:string; var next:inttype);
var
j,k:integer;
begin
j:=1;
k:=0;
next[1]:=0;
while j<=length(t) do
if (k=0) or (t[j]=t[k]) then
begin
j:=j+1;
k:=k+1;
next[j]:=k;
end
else k:=next[k];
end;
function index(s,t):integer; //求模式串t在主串s中的位置
var
next:inttype;
i,j:integer;
begin
get_next(t,next);
i:=1;
j:=1;
while (i<=length(s)) and (j<=length(t)) do
if (j=0) or (s[i]=t[j]) then
begin
i:=i+1;
j:=j+1;
end
else j:=next[j];
if j>length(t) then index:=i-length(t)
else index:=0;
end;