栈的定义
栈是一种操作受限的数据结构,它只能在表尾进行插入和删除操作,因此对于栈来讲,表尾有特殊的含义,我们称为栈顶(top),表头端称为栈底(bottom), 不含元素的空表称为空栈。栈是一种后进先出(LIFO) 的的线性表。也就是说这种数据结构,对于先进入栈的元素,会比后进入栈的元素更后出来。举个简单的生活中的例子:当有很多人在等电梯的时候,你是第一个进去的,你站到了最里面也就是栈底,但是在如果大家都在同一楼层下的话,你是最后才能出去的。这就是一个典型的栈在生活中的例子。
可能你会问,既然栈是一种操作受限的结构,为什么还有存在的必要呢?我用数组或者链表它不香吗?确实,数组或者链表有时确实可以代替栈,但是要知道数据结构是对某种特定场景的一个抽象。如果它满足栈的特性我们就用这种数据结构,此外,数组或者链表它可能暴露了过多的操作接口,因此在模拟栈的结构的时候存在安全隐患。
栈的操作与实现
对于栈这种数据结构,主要的操作包括:
1、判断栈是否为空
2、返回栈中元素的个数
3、入栈(向栈中插入一个元素)
4、弹栈(从栈中删除一个元素)
栈的实现一般有两种方式:顺序栈和链式栈。顺序栈的存储结构就是利用一组地址连续的内存空间依次存放从栈底到栈顶的元素,同时附设top指针表示栈顶元素在顺寻栈中的位置,由于采用的是地址连续的内存来表示栈结构,因此需要预先为栈分配一个基本容量,然后通过一定的策略来支持扩容。我们在这里基于数组实现一下顺序栈,至于栈的链式实现方式你可以自己实现一下。
// 基于数组实现一个顺序栈
public class SqStack {
private String[] item; // 数组
private int count; // 栈中元素的数量
private int size; // 栈的大小
// 构造方法
public SqStack(int size) {
this.item = new String[size];
this.size = size;
this.count = 0;
}
// 入栈操作
public boolean push(String str) {
// 插入元素
if (count != size) {
item[count] = str;
count ++;
return true;
}
// 如果栈空间满载
// 插入失败
else
return false;
}
// 弹栈操作
public String pop() {
// 如果栈为空则直接返回一个null
if (count == 0)
return null;
// 返回数组中的最后一个元素
String elem = item[count-1];
--count;
return elem;
}
// 获取栈中元素数量
public int getLength() {
return count;
}
}
这里我没有对栈满后的空间进行扩容,实际上栈满后需要对它进行扩容,过程如下:当栈满时,我们就申请一块更大的内存空间,然后将原来数组中的数据copy到新的数组中去,对于栈而言,copy后栈的空间也需要重新调整。其实如果要使用支持动态扩容的栈可以考虑采用链式的结构来实现一个栈,因为前面我们说过链式结构天生就支持扩容机制。
栈的应用
栈的应用非常广泛,之前我在一篇讲Python解释器源码的文章中提到过x86架构中的调用栈,大家可以参考参考:栈在函数调用中的应用。由于栈具有后进先出的特点,因此在程序设计中应用很广泛,例如:数制的转换、括号匹配、表达式求值等等。
表达式求值是程序设计语言编译中的一个基本问题,它是栈的应用的典型例子,这里介绍一种简单直观的算法:算符优先法。
要把一个表达式翻译成正确求值的一个机器指令序列,就需要能够正确地解释表达式,例如下面的表达式: 4 + 2 * 3 - 10 / 5,我们人来看很直观就能看出先后顺序,但计算机不知道,那么计算是怎样来计算的呢?算符优先法就是根据算术四则运算法则来规定对表达式的编译执行的。对于任何一个表达式都是由操作数、运算符和界限符组成的。为了实现这个算法,我们采用如下策略:
使用两个工作栈,一个称做OPTR,用来寄存运算符;另一个称为OPND,用以寄存操作数和结果。1、首先将OPND操作数栈置为空栈;2、依次读入表达式中的字符,若是操作数就进OPND栈,若是运算符则和OPTR栈顶元素比较优先级,如果当前运算符比栈顶元素的运算符优先级高,就将当前运算符压入OPTR栈,如果比栈顶运算符优先级低或者相同,就从OPTR栈顶中弹出运算符,并从OPND栈中弹出两个操作数做运算然后将结果压入OPND栈中,继续比较,以此往复直到计算完整个表达式。我们用图来模拟 4 + 2 *3 - 10 / 5 的过程。
十进制数N和其他d进制数的转换是计算机实现计算的基本问题,解决的方法也很多,但是其中一个简单算法为:N = (N div d)* d + N mod d(div 为整除运算,mod为求余运算),例如(1384)的十进制转化为2504八进制,过程如下:
// N N div 8 N mod 8
// 1348 168 4
// 168 21 0
// 21 2 5
// 2 0 2
现在我们要实现一个算法,对于任意一个非负的十进制整数,输出等值的八进制数值,由于上述过程是从低位到高位顺序生成的八进制的各个数位,而输出则需要从高位到低位进行着呢更好相反,因此假如我们将计算得到的数位依次进栈,那么在弹栈的时候由于后进先出的特性,不就从高位到低位对齐了吗?