栈的基本知识
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
栈的基本知识
[内容提要]
1、栈的概念和特性;
2、栈的存储结构:顺序存储和链式存储;
3、双栈及操作;
4、栈的几种运算(操作)实现;
5、栈的简单应用举例;
[重点难点]
1、栈的特性和应用场合;
2、栈的存储结构;
3、栈的操作实现;
[内容讲授]
1.栈的概念和特性
栈(stack)是一种特殊的线性表。
作为一个简单的例子,可以把食堂里冼净的一摞碗看作一个栈。
在通常情况下,最先冼净的碗总是放在最底下,后冼净的碗总是摞在最顶上。
而在使用时,却是从顶上拿取,也就是说,后冼的先取用,后摞上的先取用。
如果我们把冼净的碗“摞上”称为进栈(压栈),把“取用碗”称为出栈(弹出),那么上例的特点是:后进栈的先出栈。
然而,摞起来的碗实际上是一个线性表,只不过“进栈”和“出栈”都在最顶上进行,或者说,元素的插入和删除操作都是在线性表的一端进行而已。
一般而言,栈是一个线性表,其所有的插入和删除操作均是限定在线性表的一端进行,允许插入和删除的一端称栈顶(Top),不允许插入和删除的一端称栈底(Bottom)。
若给定一个栈S=(a
1
,
a 2,a
3
,……,a
n
),则称a
1
为栈底元素,a
n
为栈顶元素,元素a
i
位于元素a
i-1
之上。
栈中元素按
a 1, a
2
,a
3
,……,a
n
的次序进栈,如果从这个栈中取出所有的元素,则出栈次序为a
n
, a
n-1
,……,
a
1。
也就是说,栈中元素的进出是按后进先出的原则进行,这是栈结构的重要特征。
因此栈又称为后进先出(LIFO—Last In First Out)表。
我们常用一个图来形象地表示栈,其形式如下图:
⑴在使用栈之前,首先需要建立一个空栈,称建栈(栈的初始化);
⑵往栈顶加入一个新元素,称进栈(压栈、入栈);
⑶删除栈顶元素,称出栈(退栈、弹出);
⑷查看当前的栈顶元素,称读栈;{注意与⑶的区别}
⑸在使用栈的过程中,还要不断测试栈是否为空或已满,称为测试栈。
2.栈的存储结构
(1)顺序栈
栈是一种线性表,在计算机中用一维数组作为栈的存储结构最为简单,操作也最为方便。
例如,设一维数组STACK[1..n] 表示一个栈,其中n为栈的容量,即可存放元素的最大个数。
栈的第一个元素,或称栈底元素,是存放在STACK[1]处,第二个元素存放在STACK[2]处,第i个元素存放在STACK[i]处。
另外,由于栈顶元素经常变动,需要设置一个指针变量top,用来指示栈顶当前位置,栈中没有元素即栈空时,令top=0;当top=n时,表示栈满。
如果一个栈已经为空,但用户还继续做出栈(读栈)操作,则会出现栈的“下溢”;如果一个栈已经满了,用户还继续做进栈操作,则会出现栈的“上溢”。
这两种情况统称为栈的溢出。
建栈的操作很简单,只要建立一个一维数组,再把栈顶指针置为零即可。
栈的容量根据具体的应用要求而定,一般要定义的足够大。
比如:
type arraytype= array[1.. n] of integer;
var stack:arraytype;
top:integer;
(2)链式栈
当我们学可指针和链表后,也可以用链表来模拟栈(链式栈),这样可以提高空间利用率,但编程复杂度提高了。
定义方法如下:
type link=^node
node=record
data: integer;
next:link
end;
var hs:link;
(3)记录型栈
由于栈本身和栈顶指针是密不可分的,所以有时我们把他们定义成一个记录来处理。
如:type stack=record
vec:array[1..n] of integer; {n为栈可达到的最大深度}
top:integer;
end;
3.对栈的几种运算的实现方法
(1)建栈
只要把栈顶指针置为零。
即在程序开始时,置top:=0;
(2)测试栈
测试栈顶指针的值,若top=0,则栈空;若top=n,则栈满。
(3)读栈
若top=0,则栈空,无栈顶元素可读,显示出错信息,中止程序;
若top<>0,则回送栈顶元素的值STACK[top]给某个变量。
(4)进栈(push)
将栈顶指针加1后,再把新元素送到栈顶。
注意进栈操作前必须保证栈不能满,否则会溢出。
假设新元素x为整型,栈的最大深度为n。
则,x和n设置为值型参,而栈和栈顶指针要设置成变量型参。
为什么?
procedure push(var stack:arraytype;var top:integer;n:integer;x:integer);
begin
if top=n
then begin wr iteln(‘Stack full!’); halt end
else begin top:=top+1; stack[top]:= x end
end;
(5)出栈(pop)
取得栈顶元素的值给x后,再把栈顶指针top的值减1。
注意出栈操作前必须保证栈非空。
还要注意都用变量型参。
为什么?请问:本来的栈顶元素还在不在,还好不好调用?
procedure pop(var stack:arraytype;var top:integer;var x:integer);
begin
if top=0
then begin writeln(‘Stack empty!’); halt end
else begin x:=stack[top]; top:=top-1 end
end;
* 下面是把以上五个操作修改成链式栈的操作:
(1)置空栈setnull(hs); {不回收结点}
procedure setnull(hs:link);
begin
hs=nil ;
procedure setnull(hs:link); {回收结点}
begin
while hs<>nil do
begin
p:=hs;hs:=hs^.next;
dispose(p)
end;
end;
(2)进栈push(hs,x)
procedure push(var hs:link;x:integer);
begin
new(p);p^.data:=x; {不再判断栈是否满了,有无问题?除非系统无可用内存了,死机} p^.next:=hs;hs:=p; {如何防止以上情况的出现?注意及时释放内存空间}
end;
(3)出栈pop(hs,x)
procedure pop(var hs:link;var x:integer);
begin
if hs=nil
then begin writeln(‘underflow’);halt;end
else begin x:=hs^.data;
p:=hs;
hs:=hs^.next;
dispose(p);{如果不加,可能会怎样?}
end
end;
(4)读取栈顶元素readtop(hs)
function readtop(hs:link):inteter;
begin
if hs=nil then begin writeln(‘underflow’);halt;end
else readtop:=hs^.data {还要不要释放空间?}
end;
(5)判断栈空sempty(hs)
function sempty(hs:link):Boolean;
begin
sempty:=(hs=nil);
* 下面再把以上五个操作修改成记录型栈的操作:
(1)建栈
s.top:=0;
(2)测试栈
s.top=0,则栈空;若s.top=n,则栈满。
(3)读栈
若s.top=0,则栈空,无栈顶元素可读,显示出错信息,中止程序;
若s.top<>0,则回送栈顶元素的值s.vec[s.top]。
(4)进栈
procedure push(var s:stack;x:integer);
begin
if s.top=n
then begin wr iteln(‘Stack full!’); halt end
else begin s.top:=s.top+1; s.vec[s.top]:= x end
end;
(5)出栈
procedure pop(var s:stack;var x:integer);
begin
if s.top=0
then begin writeln(‘Stack empty!’); halt end
else begin x:=s.vec[s.top]; s.top:=s.top-1 end
end;
出栈有时也可用函数实现,如:
function pop(var s:stack):integer;
begin
if s.top=0
then begin writeln(‘Stack empty!’); halt end
else begin pop:=s.vec[s.top]; s.top:=s.top-1 end
end;
4.双栈及操作
在有些应用中需要设置两个容量都很大的栈时(元素类型要一致),为了节省空间,可以使它们共享一维数组空间s[1..t],两个栈的栈底分别设在数组两端,让两个栈彼此迎面“增长”,两个栈顶指针分别为top1,top2。
仅当两个栈的栈顶指针在中间相遇时(top1=top2)才发生“上
type bothstack=record
data:array[1..maxn] of integer;
top1,top2:0..maxn+1;
end;
var
bs:stack;
k:1..2; {k=1表示栈1,k=2表示栈2}
[栈的应用举例]
1、若已知一个栈的入栈顺序是1,2,3,……,n,其输出序列为P1,P2,P3,……,Pn,若P1是n,则Pi是( C )。
A)i B)n-1 C)n-i+1 D)不确定
2、以下哪一个不是栈的基本运算( B )。
A)删除栈顶元素B)删除栈底的元素C)判断栈是否为空 D)将栈置为空栈
3、设栈S和队列Q初始状态为空,元素e
1 ,e
2
,e
3
,e
4
,e
5
,e
6
依次通过栈S,一个元素
出栈后即进入队列Q,若出队的顺序为e
2 ,e
4
,e
3
,e
6
,e
5
,e
1
,则栈S的容量至少
应该为( B )。
A)2 B)3 C)4 D)5
4、设栈S的初始状态为空,现有5个元素组成的序列{1,2,3,4,5},对该序列在S 栈上依次进行如下操作(从序列中的1开始,出栈后不在进栈):进栈,进栈,进栈,出栈,进栈,出栈,进栈,问出栈的元素序列是:_________,栈顶指针的值为______,栈顶元素为:___________________。
解答:出栈序列为{3,4},栈顶指针值为3,栈顶元素为5。
5、设栈S和队列Q初始状态为空,元素e
1 ,e
2
,e
3
,e
4
,e
5
,e
6
依次通过栈S,一个元素
出栈后即进入队列Q,若出队顺序为e
2 ,e
4
,e
3
,e
6
,e
5
,e
1
,则栈S的容量至少应该为
______________。
解答:为3。
6、如下图,有一个无穷大的的栈S,在栈的右边排列着1,2,3,4,5共五个车厢。
其中每个车厢可以
1 2 3
可能的到达出口的车厢排列总数(不必给出每种排列)。
出口← ←
S ↓
解答:9
分别为:32145、32154、32415、32451、32541、34215、34251、34521、35421; 是否类似于用生成法求n 的全排列的问题?
思考:题目意思同上,现在有n 个车厢,第1个到达出口的是i 号车厢,问有多少种可能?
7、 讨论NOIP2003初中组第三那题:栈(Stack.pas )
【问题背景】栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表。
栈有两种最重要的操作,即pop (从栈顶弹出一个元素)和push (将一个元素进栈)。
栈的重要性不言自明,任何一门数据结构的课程都会介绍栈。
宁宁同学在复习栈的基本概念时,想到了一个书上没有讲过的问题,而他自己无法给出答案,所以需要你的帮忙。
【问题描述】 输出序列 尾端 头端 操作数序列
头端
栈A
宁宁考虑的是这样一个问题:一个操作数序列,从1,2,一直到n (图示为1到3的情况),栈A 的深度大于n 。
现在可以进行两种操作,
1.将一个数,从操作数序列的头端移到栈的头端(对应数据结构栈的push 操作)
2. 将一个数,从栈的头端移到输出序列的尾端(对应数据结构栈的pop 操作)
使用这两种操作,由一个操作数序列就可以得到一系列的输出序列,下图所示为由 1 2 3生成序列 2 3 1的过程。
(原始状态如上图所示)
1
2 3
1
2
3
1
3
2
1
1
3 2
2 3
2 3 1
你的程序将对给定的n,计算并输出由操作数序列1,2,…,n经过操作可能得到的输出序列的总数。
【输入格式】
输入文件只含一个整数n(1≤n≤18)
【输出格式】
输出文件只有一行,即可能输出序列的总数目
【输入样例】
3
【输出样例】
5
【算法分析】
因为这道题中的N最大有18,如果用穷举法,时间复杂度约为N!,是显然不行的。
而且本题只要求“可能输出序列的总数目”。
所以,我认为其中一定存在着某种关系。
于是,我就开始着手推出一个公式。
我觉得,这个式子一定是一个递归公式,因为N的情况中包含了N前面的情况。
即对于每个数字,它在某个时刻可能出栈也可能不出栈,而且他会把整个问题一分为二,成为2个相同的子问题。
现在试想:1可以什么时候出栈?可以是第1个出栈,也可以是第2个出栈,……,也可以是最后一个(第n个)出栈。
设输入n时,问题的解为f(n),假设1是第i个出栈的,则在他之前有i-1个数,解为f(i-1);后面还有n-i个数,解为f(n-i)。
再根据乘法原理,方案数为:f(i-1)*f(n-i),其中i从1到n。
即递归公式为:
F(n) = ∑
=
--
n
i
i
n
F
i
F
1
*
1)
(
)
((n>0)
= ∑-
=
-
-
1
1 *
n
i
i
n
F
i
F)
(
)
((n>0)
注意,这个递归公式的边界条件为:F(0)=1。
利用这个公式,我们就可以非常快得算出结果。
【参考程序】
const maxn=18;
var i,j,n:longint;
s:array [0..maxn] of int64; {注意范围}
begin
assign(input,'stack.in');
reset(input);
assign(output,'stack.out');
rewrite(output);
read(n);
close(input);
s[0]:=1;
for i:=1 to maxn do
begin
s[i]:=0;
for j:=0 to i-1 do s[i]:=s[i]+s[j]*s[i-j-1] end;
writeln(s[n]);
close(output)
end.。