一 简介
(1)什么是栈?
栈(stack)是一种用于存储数据的简单数据结构。栈一个有序线性表,只能在表的一端(PS:栈顶)执行插人和删除操作。最后插人的元素将被第一个删除。所以,栈也称为后进先出(Last In First Out,LIFO)或先进后出(First In Last Out,FILO)线性表
(2)有关栈的几个基本概念:
- 入栈(push):表示在栈顶插入一个元素
- 出栈(pop):表示从栈中删除栈顶元素
- 下溢(underflow):试图对一个空栈执行出栈操作称为下溢
- 溢出(overflow):试图对一个满栈执行入栈操作称为溢出
(3)有关栈的几种基本操作:
注:为了简单起见,下面有关栈操作的数据类型均为整型
- void push(int data):入栈(将数据data插入到栈中)
- int pop():出栈(删除并返回最后一个插入栈的元素)
- int top():返回最后一个插入栈的元素,但不删除
- int size():返回存储在栈中的元素个数
- boolean isEmpty():返回栈是否是空栈
- boolean isFull():返回是否是满栈
- void deleteStack():删除整个栈
(4)栈的应用:
- 符号匹配
- HTML和XML文件中的标签(tag)匹配
- 实现函数调用(包括递归)
- 文本编辑器中的撤销(undo)序列
- 网页浏览器中已访问页面的历史记录(后退(back)按钮)
- 作为一个算法的辅助数据结构(如:树遍历算法)
- 其他数据结构的组件(如:模拟队列)
二 栈的几种实现方式
栈抽象数据类型有多种实现方式,常见的有以下几种思路:
- 基于简单数组的实现方式
- 基于动态数组的实现方式
- 基于链表的实现方式
(1)基于简单数组的实现:
基于简单数组实现一个栈,其基本思路是这样的:从左至右向数组中添加所有的元素,并定义一个变量用来记录数组当前栈顶(top)元素的下标。当数组存满了栈元素时,执行入栈(插入元素)操作将抛出栈满异常;当对一个没有存储栈元素的数组执行出栈(删除元素)操作将抛出栈空异常
当然,这种实现方式有一个很明显的局限性。那就是:栈的最大空间必须预先声明且不能改变。试图对一个满栈执行入栈操作将会产生异常
(2)基于动态数组的实现:
基于动态数组的实现一个栈,其基本思路跟上面类似。不同的是,这种方式下当数组中存储的元素达到一定量时(如:数组空间的0.75或者整个数组空间),这时我们通常会选择新建一个比原数组空间大一倍的新数组,然后将原数组按照原来的顺序复制进去,接着便可以继续进行入栈操作了
Javapackage cn.zifangsky.stack;
import java.util.EmptyStackException;
/**
* 使用重复倍增数据实现栈结构
* @author zifangsky
*/
public class ArrayStack {
private int top; //栈顶元素位置
private int capacity; //栈的容量
private int[] array; //存储栈的数组
public ArrayStack() {
capacity = 1;
top = -1;
array = new int[capacity];
}
/**
* 返回栈是否是空栈
* @时间复杂度 O(1)
* @return
*/
public boolean isEmpty(){
return (top == -1);
}
/**
* 返回是否是满栈
* @时间复杂度 O(1)
* @return
*/
public boolean isFull(){
return (top == capacity -1);
}
/**
* 入栈
* @时间复杂度 O(1)
* @param data 入栈数据
*/
public void push(int data){
if(isFull()){
doubleStack();
}
array[top+1] = data;
top++;
}
/**
* 出栈:移动栈顶指针并返回数据
* @时间复杂度 O(1)
* @return
*/
public int pop(){
if(isEmpty()){
throw new EmptyStackException();
}else{
int result = array[top];
top--;
return result;
}
}
/**
* 返回最后一个插入栈的元素,但不删除
* @时间复杂度 O(1)
* @return
*/
public int top(){
if(isEmpty()){
throw new EmptyStackException();
}else{
return array[top];
}
}
/**
* 返回存储在栈中的元素个数
* @时间复杂度 O(1)
* @return
*/
public int size(){
return top+1;
}
/**
* 删除整个栈
* @时间复杂度 O(1)
*/
public void deleteStack(){
top = -1;
}
/**
* 将栈的容量扩大两倍
*/
private void doubleStack(){
int[] newArray = new int[capacity * 2];
System.arraycopy(array, 0, newArray, 0, capacity);
capacity = capacity * 2;
array = newArray;
}
}
测试代码如下:
Java @Test
public void testArrayStack(){
ArrayStack arrayStack = new ArrayStack();
arrayStack.push(1);
arrayStack.push(2);
arrayStack.push(3);
arrayStack.push(4);
arrayStack.push(5);
System.out.println(arrayStack.pop());
System.out.println(arrayStack.top());
System.out.println(arrayStack.pop());
System.out.println("剩余栈元素个数: " + arrayStack.size());
}
测试代码输出如下:
5
4
4
剩余栈元素个数: 3
(3)基于链表的实现:
基于链表实现一个栈,其基本思路是这样的:通过在链表的表头插入元素的方式来实现push操作;通过删除链表的表头节点的方式来实现pop操作
Javapackage cn.zifangsky.stack;
import java.util.EmptyStackException;
import cn.zifangsky.linkedlist.SinglyNode;
public class LinkStack {
private SinglyNode headNode;
/**
* 返回栈是否是空栈
* @时间复杂度 O(1)
* @return
*/
public boolean isEmpty(){
return headNode == null ? true : false;
}
/**
* 入栈
* @时间复杂度 O(1)
* @param data 入栈数据
*/
public void push(int data){
if(headNode == null){
headNode = new SinglyNode(data);
}else{
SinglyNode newNode = new SinglyNode(data);
newNode.setNext(headNode);
headNode = newNode;
}
}
/**
* 出栈:移动栈顶指针并返回数据
* @时间复杂度 O(1)
* @return
*/
public int pop(){
if(headNode == null){
throw new EmptyStackException();
}else{
int data = headNode.getData();
headNode = headNode.getNext();
return data;
}
}
/**
* 返回最后一个插入栈的元素,但不删除
* @时间复杂度 O(1)
* @return
*/
public int top(){
if(headNode == null){
throw new EmptyStackException();
}else{
return headNode.getData();
}
}
/**
* 返回存储在栈中的元素个数
* @时间复杂度 O(n)
* @return
*/
public int size(){
if(headNode == null){
return -1;
}else{
int length = 0;
SinglyNode currentNode = headNode;
while (currentNode != null) {
length++;
currentNode = currentNode.getNext();
}
return length;
}
}
/**
* 删除整个栈
* @时间复杂度 O(1)
*/
public void deleteStack(){
headNode = null;
}
}
注:关于这里的SinglyNode(PS:表示单链表的一个节点)的定义可以参考我之前的这篇文章:www.zifangsky.cn/933.html
测试代码如下:
Java @Test
public void testLinkStack(){
LinkStack linkStack = new LinkStack();
linkStack.push(1);
linkStack.push(2);
linkStack.push(3);
linkStack.push(4);
linkStack.push(5);
System.out.println(linkStack.pop());
System.out.println(linkStack.top());
System.out.println(linkStack.pop());
System.out.println("剩余栈元素个数: " + linkStack.size());
}
测试代码输出如下:
5
4
4
剩余栈元素个数: 3
三 基于数组实现和基于链表实现的比较
(1)基于数组实现的栈:
- 各个操作都是常数时间开销
- 每隔一段时间进行的倍增操作的时间开销较大
(2)基于链表实现的栈:
- 栈规模的增加和减小都很容易
- 各个操作都是常数时间开销
- 每个操作都需要使用额外的空间和时间开销来处理指针
好了本篇文章到此结束,下篇文章将继续介绍有关栈的经典问题解析