栈
什么是栈
栈是一个有着特殊规则的数据结构。我们了解汉诺塔游戏(不懂的自行询问度娘),汉诺塔游戏里有一个明确的规则,即每次只能移动顶端的一个圆盘。
栈也有这个特点。我们可以将栈视为汉诺塔中的一个柱子,我们往这个柱子上放置圆盘,先放下去的一定是最后才能拿出来的,而最后放下去的一定是最先拿出来的。这也是栈的最重要一个特点----后进先出(LIFO,Last In First Out),也可以说是先进后出(FILO,First In Last Out),我们无论如何只能从一端去操作元素。
栈又叫做堆栈(Stack),这里说明一下不要将它和堆混肴。实际上堆和栈是两个不同的概念,栈是一种只能在一端进行插入和删除的线性数据结构。
一般来说,栈主要有两个操作:一个是进栈(PUSH),又叫做入栈、压栈;另一个是出栈(POP),或者叫做退栈。
其实栈是一种比较简单的数据结构,但是由于其特性,又衍生了不少的相关算法。
栈的存储结构
栈一般使用一段连续的空间进行存储,通常预先分配一个长度,可以简单地使用数组去实现,具体的存储结构如下图所示。
通过上图可以清晰的看到,只有一个方向可以对栈内的元素进行操作,而栈中最下面的一个元素成为栈底,一般是数组的第0个元素,而栈顶是栈内最后放入的元素。
一般而言,定义一个栈需要有一个初始的大小,这就是栈的初始容量。当需要放入的元素大于这个容量时,就需要进行扩容。栈扩容的实现类似于集合的自动增长(扩容)。
栈出入元素的操作如下。例如我们初始化一个长度为10的数组,并向其中放入元素,根据栈的定义,只能从数组的一端放入元素,我们设定这一端为数组中较大下标的方向。我们放入第1个元素,由于栈内没有元素,于是第1个元素就落到了数组的第0个下标的位置上;接着放入第2个元素,第2个元素该放入下标为1的位置上;以此类推,当放入了5个元素时,第5个入栈的元素应该在数组的第4个下标的位置上。现在我们要进行出栈操作,出栈只能从一端操作,我们之前设定只能从数组下标较大的方向操作,因此需要确定数组中下标最大的一个方向中存在栈元素的位置下标是多少。我们一般会在栈中做个计数器来记录这个值。现在栈中有5个元素,所以将数组中的第5个位置也就是下标为4的元素出栈。此时数组中只剩下4个元素了。
下面是栈的实现代码,这里以整型元素为例,在java类的高级语言中,数据类型可以换成对象。
package Stack;
import java.util.Arrays;
public class Stack {
private int size = 0;
private int[] array;
public Stack() {
this(10);//此处通过调用this()调用构造方法
}
public Stack(int init) {
if(init<=0) {
init = 10;
}
array = new int[init];
}
/*
* 入栈
* @param item 入栈元素的值*/
public void push(int item) {
if(size == array.length) {
array = Arrays.copyOf(array,size*2);
}
array[size++]= item;
}
/*
* 获取栈顶元素,但是没有出栈
* @return*/
public int peek() {
if(size == 0) {
throw new IndexOutOfBoundsException("栈里已经空");
}
return array[size-1];
}
/*
* 出栈,同时获取栈顶元素
* @return*/
public int pop() {
int item = peek();
size--;//直接使元素个数减1,不需要真的清除元素,下次入栈会覆盖旧元素的值
return item;
}
/*
* 栈是否满了
* @return*/
public boolean isFull() {
return size == array.length;
}
/*
* 栈是否为空栈
* @return*/
public boolean isEmpty() {
return size == 0;
}
/*栈里面存放了多少个元素*/
public int size() {
return size;
}
}
下面是测试代码
package Stack;
public class StackTest {
public static void main(String[] args) {
Stack stack = new Stack(1);//为了方便看出效果,设定初始数组长度为1
stack.push(1);
stack.push(2);
System.out.println(stack.size());//栈内元素个数为2,当前数组长度也为2 2
stack.push(3);
System.out.println(stack.size());//栈内元素个数为3,当前数组长度为4 4
System.out.println(stack.peek());//获取栈顶元素,为3,但是没有出栈 3
System.out.println(stack.size());//由于上面一行没有出栈,所以元素个数还是3 3
System.out.println(stack.pop());//栈顶元素出栈,返回3 3
System.out.println(stack.pop());//栈顶元素出栈,返回2 2
System.out.println(stack.size());//出了两次栈,当前元素个数为1 1
}
}
栈的特点
栈的特点是显而易见的,只能在一端进行操作,遵循先进后出或者后进先出的原则。
栈的适用场景
- 逆序输出
由于栈具有先进后出的特点,所以逆序输出是其中一个非常简单的应用。首先把所有元素按顺序入栈,然后把所有元素出栈并输出,轻松实现逆序输出。 - 语法检查,符号成对出现
- 数制转换(将十进制的数转换为2~9的任意进制的数)
另一个应用就是用于实现十进制与髂进制的转换规则。