栈(也称下压栈,堆栈)是一种基于后进先出(LIFO)策略的集合类型,只允许在固定的一端(栈顶)进行插入和删除操作。在应用程序中使用栈的一个典型原因是在集合中保存元素的同时颠倒它的相对顺序。
抽象数据类型
集合类的抽象数据类型的一个关键特性是能够存储任意类型的数据,我们使用泛型。为了方便的使用foreach语句迭代访问集合中的所有元素,这里实现了Iterable接口。
基于泛型可迭代的栈的API:
public class Stack<Item> implements Iterable<Item>
栈的API | 功能 |
---|---|
void push(Item item) | 添加一个元素(入栈) |
Item pop() | 删除一个元素(出栈) |
Item peek() | 返回栈顶元素 |
boolean isEmpty() | 栈是否为空 |
int size() | 栈的大小 |
栈的创建
栈的实现常见的有两种方式:
- 基于数组的实现(顺序存储)
- 基于链表的实现(链式存储)
1、能动态调整数组大小的实现
存储结构如下图:
实现代码如下:
import java.util.Iterator;
/**
* 能动态调整数组大小的栈
*/
public class ResizingArrayStack<Item> implements Iterable<Item> {
private Item[] a;
private int N=0;
public ResizingArrayStack() {
a = (Item[])new Object[1]; //注意:java不允许创建泛型数组
}
public int size() {return N;}
public boolean isEmpty() {return N == 0;}
//返回栈顶元素
public Item peek() {return a[N-1];}
//调整数组大小
public void resizingArray(int num) {
Item[] temp = (Item[])new Object[num];
for(int i=0;i<N;i++) {
temp[i] = a[i];
}
a = temp;
}
//入栈
public void push(Item item) {
if(N==a.length) {
resizingArray(2*N);//若数组已满将长度加倍
}
a[N++] = item;
}
//出栈
public Item pop() {
Item item = a[--N];
a[N] = null; //注意:避免对象游离
if(N>0 && N==a.length/4) {
resizingArray(a.length/2);//小于数组1/4,将数组减半
}
return item;
}
//实现迭代器
public Iterator<Item> iterator() {
return new StackIterator();
}
//Iterable接口在java.lang中,但Iterator在java.util中
private class StackIterator implements Iterator<Item> {
private int num = N;
public boolean hasNext() {
return num > 0;
}
public Item next() {
return a[--num];
}
}
}
注意:
- 泛型中的内容必须为对象,不能为基础数据类型。如:item不能为int,但可使用其包装类Integer,来使泛型能够处理基础数据类型。
Stack<Integer> stack = new Stack<Integer>();
stack.push(8);//自动装箱(int-->Integer)
int i = stack.pop(); //自动拆箱(Integer-->int)
java中不能创建泛型的数组,如
Item[] a = new Item[5]
是不允许的,需要用类型转换将Object类型转换为Item类型Item[] a = (Item[])new Object[5]
.在使用泛型时,java会在编译时检查类型的安全性,但会在运行时抛弃所用的这些信息,如创建一个字符串栈的数组,运行时语句右侧就变成了Stack[N];
Stack<String>[] a = (Stack<String>[])new Stack[N];
优点:
- 每项操作的用时都与集合大小无关
- 空间需求总是不超过集合大小乘以一个常数
缺点:push()和pop()操作有时会调整数组大小,这项操作的耗时和栈的大小成正比
2、链表实现
存储结构如下图:
代码实现如下:
import java.util.Iterator;
/**
1. 链表实现的栈
2. @author Gain
*/
public class LinkedStack<Item> implements Iterable<Item> {
private int N = 0;
private Node first = null;//栈顶
//定义结点的嵌套类
private class Node {
Item item;
Node next;
Node(Item item,Node next) {
this.item = item;
this.next = next;
}
}
//返回栈的大小
public int size() {return N;}
//判断栈是否为空
public boolean isEmpty() {return N==0;}
//栈顶元素
public Item peek() {return first.item;}
//出栈
public void push(Item item) {
Node node = new Node(item,first);
first = node;
N++;
}
//入栈
public Item pop() {
Item item = first.item;
first = first.next;
N--;
return item;
}
//迭代器
public Iterator<Item> iterator() {
return new LinkedStackIterator();
}
private class LinkedStackIterator implements Iterator<Item>{
private Node current = first;
public boolean hasNext() {
return current != null;
}
public Item next() {
Item item = current.item;
current = current.next;
return item;
}
}
}
注意:私有嵌套类(内部类Node)的一个特点是只有包含他的类能够直接访问他的实例变量,无需将他的实例变量(item)声明为public或private。即使将变量item声明为private,外部类依然可以通过Node.item
的形式调用。
优点:
- 所需空间和集合的大小成正比
- 操作时间和集合的大小无关
3、测试
代码如下:
public class Test {
public static void main(String[] args) {
int[] a = {1,2,3,4,new Integer(5),6};//测试数组
ResizingArrayStack<Integer> stack1 = new ResizingArrayStack<Integer>();
LinkedStack<Integer> stack2 = new LinkedStack<Integer>();
System.out.print("入栈顺序:");
for(int i=0;i<a.length;i++) {
System.out.print(a[i]+" ");
stack1.push(a[i]);
stack2.push(a[i]);
}
System.out.println();
System.out.print("出栈顺序数组实现:");
//迭代
for (Integer s : stack1) {
System.out.print(s+" ");
}
System.out.print("出栈顺序链表实现:");
for (Integer s : stack2) {
System.out.print(s+" ");
}
}
}
运行效果:
面试题
1、栈的可生成性
用例程序会进行一系列入栈和出栈的混合操作。入栈操作会将整数0-9顺序压入栈中,判断出栈顺序是否正确。
例如:
- 入栈: 0- 1- 2- 3- 4- 5- 6- 7- 8- 9
- 出栈:4- 3 -2- 1 -0- 9 -8 -7- 6 -5 (正确)
- 出栈:4 -6 -8- 7- 5- 3- 2- 9 -0- 1(错误)
代码如下:
/**
* 判断栈的可生成性
* @author Gain
*/
public class StackTest {
LinkedStack<Integer> stack = new LinkedStack<Integer>();
//pushData[]数组表示入栈顺序,判断popData的出栈是否正确
public boolean isGenerated(int pushData[],int popData[]){
for(int i=0,j=0;i<pushData.length;i++){
stack.push(pushData[i]);
//若栈不为空,且栈顶元素等于相应的出栈数组中元素则出栈
while(!stack.isEmpty()&&stack.peek() == popData[j]){
stack.pop();
j++;
}
}
//若最后栈为空,则返回true
if(stack.isEmpty()) return true;
else return false;
}
//测试
public static void main(String[] args) {
int a[]={0,1,2,3,4,5,6,7,8,9};
int a1[]={4,3,2,1,0,9,8,7,6,5};
int a2[]={4,6,8,7,5,3,2,9,0,1};
StackTest test = new StackTest();
System.out.println(test.isGenerated(a,a1));
System.out.println(test.isGenerated(a,a2));
}
}
运行结果:
2、设计一个栈的min方法(返回最小值),要求其时间复杂度为O(1)
思路:加一个辅助栈,用空间换时间。原栈每次有新元素入栈时都和辅助栈的栈顶元素相比较,若新元素小,就把新元素压入辅助栈,否则,将辅助栈的栈顶元素压入辅助栈。
代码如下:
public class Stack {
//原栈
LinkedStack<Integer> stack1 = new LinkedStack<Integer>();
//辅助栈,栈顶永远保存着当前原栈中最小值
LinkedStack<Integer> stack2 = new LinkedStack<Integer>();
public void push(int i){
stack1.push(i);
//新元素和辅助栈栈顶元素,小者压入辅助栈
if(!stack2.isEmpty()&&i>stack2.peek())
stack2.push(stack2.peek());
else stack2.push(i);
}
public int pop() {
int data = stack1.pop();
stack2.pop();//辅助栈一起出栈
return data;
}
//返回最小值
public int min() {
return stack2.peek();
}
//测试
public static void main(String[] args) {
Stack stack = new Stack();
int[] a={8,1,4,2,5,6,3,5,6};
for(int i=0;i<a.length;i++) {
stack.push(a[i]);
}
System.out.println(stack.min());
}
}
运行结果: