栈的定义
栈是限定仅在表尾进行插入和删除操作的线性表。
把允许插入和删除的一端称之为栈顶,另一端为栈底。不含任何数据元素的栈称之为空栈。栈又被称作后进先出(Last In First Out)的线性表,简称LIFO结构。
栈的插入操作称之为入栈。栈的删除操作称之为出栈。
栈的结构
既然栈是线性表的特例,线性表分为顺序存储和链式存储,那么栈的结构也分为顺序存储结构和链式存储结构。简称顺序栈和链栈。
顺序栈
栈的顺序存储其实也是线性表顺序存储的简化,我们简称为顺序栈。
结构
private static final int MAXSIZE = 5;
static class SqStack {
int top; // 栈顶元素
int[] data;
public SqStack() {
this.top = -1;
this.data = new int[MAXSIZE];
}
}
入栈Push
插入元素elementData为新的栈顶元素
static void push(SqStack s, int elementData) throws Exception {
if(s.top == MAXSIZE -1) {
throw new Exception("栈满");
}
s.top ++; // 栈顶指针增加1
s.data[s.top] = elementData; // 将新插入元素赋值到栈顶空间
}
出栈Pop
若栈不空,删除S栈顶元素,返回结果
static Integer pop(SqStack s) throws Exception {
if(s.top == -1) {
throw new Exception("栈是空的");
}
return s.data[s.top--];
}
顺序栈的出栈和入栈都没涉及到循环语句,时间复杂度均为O(1)
链栈
栈的链式存储结构,称之为链栈。
结构
static class Node{
int data;
Node next;
public Node() { }
}
static class LinkStack {
Node top;
int count;
public LinkStack() {
this.count = 0;
this.top = new Node();
}
}
进栈
对于链栈的push操作,假设元素值为e,新结点s,top为栈顶指针。
static void push(LinkStack linkStack, Integer element) {
Node node = new Node();
node.data = element;
node.next = linkStack.top;
linkStack.top = node;
linkStack.count ++;
}
出栈
对于链栈的pop操作,需出栈的结点为ai,top为栈顶指针。
static Integer pop(LinkStack linkStack) throws Exception {
if(linkStack == null || linkStack.count == 0) {
throw new Exception("栈是空的");
}
Node remove = linkStack.top;
linkStack.top = remove.next;
linkStack.count --;
return remove.data;
}
链栈的出栈push和入栈pop都没涉及到循环语句,时间复杂度均为O(1)
栈的应用 --递归
把一个直接调用自己或通过一系列的调用语句间接调用自己的函数,称作递归函数。
分解问题是进栈(或者说压栈)的过程,解决问题是一个出栈的过程。
斐波那契数列实现
假如我们需要打印前40位斐波那契数列数。
传统方法
int[] a = new int[40];
a[0] = 1;
a[1] = 1;
System.out.print(a[0] + " ");
System.out.print(a[1] + " ");
for (int i=2; i<40; i++) {
a[i] = a[i-1] + a[i-2];
System.out.print(a[i] + " ");
}
递归
for (int i=0; i<40; i++) {
System.out.print(Fbi(i) + " ");
}
static int Fbi(int n) {
if(n < 2) {
return n == 0? 0:1;
}
return Fbi(n-1) + Fbi(n-2);
}
递归代码看着干净很多,但是理解就有点费脑子了。
递归和栈的关系
我们已经看到递归是如何执行它的前行和退回阶段的。递归过程退回的顺序是它前行顺序的逆序。在退回过程中,可能要执行某些动作,包括恢复在前行过程中存储起来的某些数据。这种存储某些数据,并在后面又以存储的逆序恢复这些数据,以提供之后使用的需求,显然很符合栈这样的数据结构。
简单的说,就是在前行阶段,对于每一层递归, 函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。