在上一篇中我们进行了表的专项练习,在这篇文章中我们将介绍栈的相关知识点。
目录
基础概念
栈是限定仅在栈顶(即表首)进行插入和删除操作的线性表,也称为后进先出(Last In First Out) 的线性表,简称 LIFO 结构。
栈的内部实现原理其实就是数组或链表的操作,而之所以引入 栈 这个概念,是为了将程序设计问题模型化,利用栈的先进后出特性对特定的一些问题进行简化。(栈是线性表的特例)
允许插入和删除元素的一端称为栈顶,另一端称为栈底,不含任何任何数据元素的栈称为空栈。
顺序栈
当使用线性表的顺序存储结构(即数组)实现栈时,该栈被称为顺序栈
伪代码
//创建一个容量为size的空栈
Stack StackInit(int size)
{
Stack S=malloc(sizeof *S);
S->data=malloc(size *sizeof(StackItem));
S->maxtop=size-1;
S->top=-1;
return S;
}
//判断栈空StackEmpty(S)
int StackEmpty (Stack S)
{
return S->top<0;}//top=-1时为空栈
// 判断栈满StackFull(S)
int StackFull (Stack S)
{
return S->top==S->maxtop;}//top==maxtop时为满栈
//释放空间StackFree(S)
int StackFree (Stack S) {
free(S->data); free(S);}
//返回栈顶元素StackItem StackTop(Stack S)
{
if (StackEmpty(S)) Error("Stack is empty");
else
return S->data[S->top]; //直接返回栈顶top元素即可
}
//入栈操作push(x,S)
void Push (StackItem x, Stack S)
{
if (StackFull(S)) Error("Stack is full");
else S->data[++S->top]=x;}//前置自加
//x为新的栈顶元素,存放在插⼊前的top+1单元
//出栈操作Pop(S)
void Pop (Stack S)
{
if (StackEmpty(S)) Error("Stack is empty ");
else return S->data[S->top--];}//后置自减
//删除栈顶元素x后,新的栈顶位置为删除前的top-1单元
注意点:当使用数组存储栈的时候,设数组大小为M,当栈为空时,栈顶指针值为-1,当栈满时,栈顶指针为M-1。若此时入栈,则上溢(overflow)
链栈
当使用线性表的链式存储结构(单链表)实现栈时,该栈被称为链栈。为维护堆栈的FILO的存储特性,链栈中维护的top指针始终指向栈顶元素。
伪代码
//空栈的创建
Stack StackInit()
{
Stack S=malloc(sizeof *S);
S->top=0;//将top置为空指针,创建一个空栈
return S;
}//o(1)
//检查是否空栈StackEmpty(S)
int StackEmpty (Stack S)
{
//检测top是否为空指针
return S->top==0;
}
//返回栈顶元素
StackItem StackTop(Stack S)
{
if (StackEmpty(S))
Error("Stack is empty");
else return S->top->element;
}
//插入元素
void Push(StackItem x, Stack S)
{
slink p;
if (StackFull(S)) Error("Stack is Full");
p=NewStackNode();//为元素x创建一个新结点
p->element=x;
p->next=S->top;//修改栈顶结点指针top
S->top=p;//新结点成为新的栈顶指针top
}
//删除元素
StackItem Pop(Stack S)
{
slink p; StackItem x;
if (StackEmpty(S)) Error("Stack is empty");
x=S->top->element;//将栈顶指针top所指元素放入x中
p=S->top;
S->top=p->next;//修改栈顶指针使其指向栈顶元素的下一个结点
free(p);
return x;
}
顺序栈与链栈的异同点:顺序栈和链栈的时间复杂度均为O(1)
为了使每个栈在算法运行过程中不会溢出,通常要为每个栈预置一个较大的栈空间,但它的优势是存取时定位很方便。
链栈要求每个元素都要配套一个指向下个结点的指针域,增大了内存开销,但好处是栈的长度无限,因此,如果栈的使用过程中元素变化不可预料,有时很小,有时很大,那么最好使用链栈。反之,如果它的变化在可控范围内,则建议使用顺序栈。
接下来让我们进行栈的相关练习。
判断题
1.若一个栈的输入序列为1,2,3,…,N,输出序列的第一个元素是i,则第j个输出元素是j−i−1。(错)
解析:不确定,如果输出的第一个是1,则第二个可以是任何数。
2.栈结构不会出现溢出现象。(错)
链式存储的栈结构不会溢出,但顺序存储的栈结构会溢出,因为它一开始就定义了栈的长度。
3.若一个栈的输入序列为{1, 2, 3, 4, 5},则不可能得到{3, 4, 1, 2, 5}这样的出栈序列。(对)
结合题目且1比2先入栈,所以出栈的顺序肯定是2在1前面,所以不可能得到{3, 4, 1, 2, 5}
4.两个栈共享一片连续空间,可以将两个栈的栈底分别设在这片空间的两端。(对)
选择题
1.若采用带头、尾指针的单向链表表示一个堆栈,那么该堆栈的栈顶指针top应该如何设置?
A.随便哪端作为top都可以
B.链表头、尾都不适合作为top
C.将链表头设为top
D.将链表尾设为top
选C,解析:栈顶为表首,即链表头。
2.给定有限符号集 S , in 和 out 均为 S 中所有元素的任意排列。 对于初始为空的栈 ST, 下列叙述中,正确的是:(选A)
A.若 in 是 ST 的入栈序列,out 是对应 in 的出栈序列, 则 in 与 out 可能互为倒序
B.若 out 是 ST 的出栈序列,则不能判断 in 是否为其可能的入栈序列
C.若 in 是 ST 的入栈序列,out 是对应 in 的出栈序列, 则 in 与 out 一定不同
D.若 in 是 ST 的入栈序列, 则不能判断 out 是否为其可能的出栈序列
3.将5个字母
ooops
按此顺序入栈,则有多少种不同的出栈顺序可以仍然得到ooops
?(选A)
A.5
B.3
C.1
D.6
解析:
- o1进o1出、o2进o2出、o3进o3出
- o1进o2进、o2出o1出、o3进o3出
- o1进o2进、o2出o3进、o3出o1出
- o1、o2、o3进,o3、o2、o1出
- o1进o1出、o2进o3进、o3出o2出
4.下列关于栈的叙述中,错误的是:
1. 采用非递归方式重写递归程序时必须使用栈
2. 函数调用时,系统要用栈保存必要的信息
3. 只要确定了入栈次序,即可确定出栈次序
4. 栈是一种受限的线性表,允许在其两端进行操作
解析:计算斐波拉契数列迭代实现只需要一个循环即可实现,故1错
2对。3错,不做过多解释。4错,栈是一种受限的线性表,只允许在一端进行操作。
5.若一个栈的入栈序列为1、2、3、…、N,其输出序列为p1、p2、p3、…、pN。若p1=N,则pi为:()
解析:栈的特点是后进先出,入栈序列是 1,2,3,…,n,输出对应的应该是 n,n-1,n-2,…,1,所以答案是 n - i + 1。
6.给定一个堆栈的入栈序列为{ 1, 2, ⋯, n },出栈序列为{ p1, p2, ⋯, pn }。如果p2=n,则存在多少种不同的出栈序列?
解析:在n出栈之前,前n-1个元素都可以第一个出栈,所以答案为n-1。
7.若元素a、b、c、d、e、f依次进栈,允许进栈、退栈操作交替进行,但不允许连续三次进行退栈工作,则不可能得到的出栈序列是?(D)
A.c b d a e f
B.b c a e f d
C.d c e b f a
D.a f e d c b
解析: 假设选项成立,推得其操作即可。
8.在作进栈运算时,应先判别栈是否(① );在作退栈运算时应先判别栈是否(② )。当栈中元素为n个,作进栈运算时发生上溢,则说明该栈的最大容量为(③ )。
①: A. 空 B. 满 C. 上溢 D. 下溢
②: A. 空 B. 满 C. 上溢 D. 下溢
③: A. n-1 B. n C. n+1 D. n/2
A. ① B ② A ③ A
B. ① C ② D ③ B
C. ① B ② A ③ B
D. ① B ② B ③ A
答案选C,解析:在作进栈运算时,应先判别栈是否满。在作退栈运算时应先判别栈是否空。当栈中元素为n个,作进栈运算时发生上溢,则说明该栈的最大容量为n。
9.若一个栈的入栈序列为1、2、3、…、N,输出序列的第一个元素是i,则第j个输出元素是:
A.不确定
B.i−j
C.i−j−1
D.j−i−1
选A,解析:i如果是1,则第j个输出的元素可以是2~N任何一个。
10.设一个堆栈的入栈顺序是1、2、3、4、5。若第一个出栈的元素是4,则最后一个出栈的元素必定是:
A.3
B.1或者5
C.5
D.1
选B,解析:第一个出栈的元素是4,说明这时候5还没入栈,所以下一步可能是入栈或者出栈。
11.检查表达式中的括号是否匹配的问题需要借助________来解决。
A.有向无环图
B.队列
C.二叉搜索树
D.堆栈
选D,解析:具体解析可看后面的编程题。
12.若借助堆栈将中缀表达式
a+b*c+(d*e+f)*g
转换为后缀表达式,当读入f
时,堆栈里的内容是什么(按堆栈自底向上顺序)?
A.+(*+
B.abcde
C.++(+
D.+(+
-
当输入的是操作数时,直接输出a;
-
遇到操作符,如果栈为空入栈,
+
入栈 -
当输入的是操作数时,直接输出b;
-
栈顶元素
+
的优先级小于*
的优先级,*
压栈; -
当输入的是操作数时,直接输出c;
-
当输入的是运算符,栈顶元素
*
的优先级大于+
的优先级,*
出栈;循环,栈顶元素+
的优先级等于+
的优先级,+
出栈,+
入栈; -
当输入的是开括号时,把它压栈;
-
当输入的是操作数时,直接输出d;
-
当输入的是运算符,因为栈顶是开括号,
*
压栈; -
当输入的是操作数时,直接输出e;
-
当输入的是运算符,栈顶元素
*
的优先级大于+
的优先级,*
出栈;+
入栈;
当输入的是f停止,此时栈中+(+
总而言之
如果在外面的符号比栈顶符号优先级要高,则入栈
如果外面的小于等于栈顶的,则栈顶出栈;接着再把这个外面的和第二个栈元素比较
13.设有一顺序栈S,元素s1,s2,s3,s4,s5,s6依次进栈,如果6个元素出栈的顺序是s2,s3,s