栈(Stack)
1.定义
栈又名堆栈,是数据暂时存储的地方。它一种只能在顶端进行插入和删除操作的特殊线性表,它按照先进后出的原则存储数据,先进的数据被压入栈底,最后的数据在栈顶,需要读取数据的时候从栈顶开始弹出数据。
栈具有记忆作用,对栈的插入和删除操作中,不需要改变栈底指针。
栈中允许进行插入和删除操作的一端称为栈顶(top),另一端为栈底(bottom);栈底固定,而栈顶浮动;栈中元素个数为零时称为空栈。插入一般称为进栈(push),删除则称为退栈(pop)。栈也称为后进先出表。
栈的基本操作:出栈pop减一,入栈push加一。
例:设有一个空栈,栈顶指针是1000H(十六进制数,下同,且设每个入栈元素需要1个存储空间)。现有输入序列a,b,c,d,e,经过PUSH,PUSH,POP,PUSH,POP,PUSH后,栈顶指针是(1002)。
2. 顺序栈
栈的顺序存储结构是利用内存中的一片起始位置确定的连续存储区域来存放栈中的所有元素,另外引入一个栈顶指示变量top指示栈顶的准确位置。
采用顺序存储结构的栈称为顺序栈,即顺序栈是地址连续的。
下面的顺序栈和链式栈是借鉴大佬的,来源:https://www.cnblogs.com/fzz9/p/8167546.html
顺序栈示意图:
顺序栈的实现:定义栈数组
package curri;
public class SqStack<T> {
//用数组表示栈元素
private T data[];
//栈空间大小(常量)
private int maxSize;
//栈顶指针(指向栈顶元素)
private int top;
@SuppressWarnings("unchecked")
public SqStack(int maxSize) {
this.maxSize = maxSize;
//泛型数组不能直接通过new创建,需要使用Object来创建
this.data = (T[]) new Object[maxSize];
//有的书中使用0,但这样会占用内存。
this.top = -1;
}
//判断是否为空栈
public boolean isNull(){
boolean flag = this.top<=-1?true:false;
return flag;
}
//判断是否栈满
public boolean isFull(){
boolean flag=this.top==this.maxSize-1?true:false;
return flag;
}
//压栈
public boolean push(T value){
if(isFull()){
return false;
}else{
//栈顶指针加1并赋值
data[++top] = value;
return true;
}
}
public T pop(){
if(isNull()){
return null;
}else{
//取出栈顶元素
T value = data[top];
//栈顶指针-1;
--top;
return value;
}
}
}
测试:
package curri;
import org.junit.Test;
public class Stack {
@Test
public void fun(){
//1.初始化栈
SqStack<Character> stack = new SqStack<Character>(10);
//2.查看状态
System.out.println("栈是否为空:"+stack.isNull());
System.out.println("栈是否为满:"+stack.isFull());
//3.压栈
stack.push('a');
stack.push('b');
stack.push('c');
//4.依次弹栈
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack.pop());
}
}
3. 链式栈
链式栈中的元素以Node的形式存储,节点Node中存有此节点存于栈中的元素以及指向下个节点的指针。链式栈的数据成员只用指向栈顶节点的指针”top_node”。对于链式栈而言,**使用了链表来实现栈,链表中的元素存储在不连续的地址,**由于是动态申请内存,所以可以从非常小的内存空间开始,另外当某个项不使用时也可以将内存返还给系统。
链式栈示意图:
链式栈的实现:定义栈数组
package curri;
public class LinkStack<T> {
//栈顶结点
private LinkNode<T> top;
//初始化1
public LinkStack() {
this.top = new LinkNode<T>();
}
public void initStack(){
this.top.setData(null);
this.top.setNext(null);
}
//是否栈空
public boolean isNull(){
boolean flag = top.getNext()==null?true:false;
return flag;
}
//压栈
public void push(LinkNode<T> node){
if(isNull()){
//栈空,即第一次插入
top.setNext(node);
//该句可以省略,首次插入的元素都为栈底元素
node.setNext(null);
}else{
node.setNext(top.getNext());
top.setNext(node);
}
}
//弹栈
public LinkNode<T> pop(){
if(isNull()){
return null;
}else{
//取出删除结点
LinkNode<T> delNode = top.getNext();
top.setNext(top.getNext().getNext());
return delNode;
}
}
}
结点:
package curri;
public class LinkNode<T> {
private T data;
private LinkNode<T> next;
//初始化1
public LinkNode() {
this.data = null;
this.next = null;
}
//初始化2
public LinkNode(T data){
super();
this.data = data;
this.next = null;
}
public T getData(){
return data;
}
public void setData(T data){
this.data = data;
}
public LinkNode<T> getNext(){
return next;
}
public void setNext(LinkNode<T> next){
this.next = next;
}
}
测试
package test;
import org.junit.Test;
import curri.LinkNode;
import curri.LinkStack;
public class LinkedTest {
@Test
public void fun(){
LinkStack<Character> ls = new LinkStack<Character>();
System.out.println("栈是否为空:"+ls.isNull());
//依次入栈
ls.push(new LinkNode<Character>('a'));
ls.push(new LinkNode<Character>('b'));
ls.push(new LinkNode<Character>('c'));
//依次弹栈
System.out.println("弹栈顺序:");
System.out.println(ls.pop().getData());
System.out.println(ls.pop().getData());
System.out.println(ls.pop().getData());
}
}
4. 栈的应用
局部变量是分配在栈上的,new出来的对象时分配在堆上的。所以函数内局部变量、函数内局部指针变量是分配在栈上的,而函数内动态申请的对象时分配在堆上的。
在调用函数时,入参及返回地址使用了栈。因为在函数调用过程中形成嵌套时,则应使最后被调用的函数被最先返回,后进先出。所以递归过程或函数调用时,处理参数及返回地址要用栈数据结构。
栈的常见应用:浏览器历史记录,Android中的最近任务,Activity的启动模式,CPU中栈的实现,Word自动保存,解析计算式,解析xml/json。
如:解析XML时,需要校验节点是否闭合,如必须有之对应,用栈数据结构实现比较好。
5. 存储结构
存储结构是数据的逻辑结构用计算机语言的实现,常见的存储结构有:顺序存储,链式存储,索引存储以及散列存储。其中散列所形成的存储结构叫做散列表(又叫哈希表),因此哈希表也是一种存储结构。栈只是一种抽象数据类型,是一种逻辑结构,栈逻辑结构对应的顺序存储结构为顺序栈,对应的链式存储结构为链栈,循环队列是顺序存储结构,链表时线性表的链式存储结构。
例1:设栈的顺序存储空间为 S(1:m) ,初始状态为 top=0 。现经过一系列正常的入栈与退栈操作后, top=m+1 ,则栈中的元素个数为( 不可能 )。
释:顺序栈,存储空间为S(1:m),栈顶指针初始状态为top=0,即栈空状态;当栈内有一个元素时,top=1;当处于栈满时,top=m。所以栈顶指针的取值范围是[0,m]之间的整数,因此top=m+1是不可能事件。
应注意的是,如果用一维数组来作为顺序栈的存储结构,则存储空间为S[0,m-1]。此时栈顶指针为top=-1,即栈空状态;当栈内有一个元素时,top=0;当处于栈满状态时,top=m-1。可知,当栈非空时,栈顶指针与数组下表相对应。栈顶指针top的取值范围是[-1,m-1]之间的整数。
6. 相关例题
1.一个栈的进栈序列是 a , b , c , d , e ,则栈的不可能的输出序列是()。
A、edcba
B、decba
C、dceab
D、abcde
对于出栈序列的每一个元素,该元素后比该元素先入栈的一定按照降序排列。
A.abcde进栈,先进后出,则出栈为edcba。
B.abcd进栈,d先出栈,e进栈,然后e出栈,cba依次出栈。
C.错误。abcd进栈,d出栈,c出栈。e进栈后再出栈,但此时栈内元素有ab,a在栈底,b在栈顶。因此b应该先出栈。正确顺序应该为dceba。
D.a进栈后出栈,b进栈后出栈,而后的元素均进栈后立即出栈,顺序合理。
2. 一个栈的入栈序列为1,2,3,…,n ,其出栈序列是 p 1 ,p 2 ,p 3 ,…p n 。若p 2 = 3,则 p 3 可能取值的个数是(n-1)。
当n=3时,p1=1或2。
P1=1,p2=3时:push,pop(此时p1=1),push,push,pop(此时p2=3),pop(此时p3=2)。
P1=2,p2=3时:push,push,pop(此时p1=2),push,pop(此时p2=3),pop(此时p3=1)。
此时可以看到p3有1和2两种情况。
当n>3时,p1=1/2/4,因为p2=3,所以p1<=4。
如果 p 1 = 1 , 那么 p 3 = 2,4,5,… n (n - 2)个。
如果 p 1 = 4 ,那么 p 3 = 2,5,6,… n (n - 3)个
此时的话我们就可以看到 p 3 的情况有 1,2,4,5,… n (n - 1)个。
综上所述就是 p 3 可能取值的个数是 (n - 1)个。