下压栈(LIFO)详解
写在前面的话:
一枚自学Java和算法的工科妹子。
- 算法学习书目:算法(第四版) Robert Sedgewick
- 算法视频教程:Coursera Algorithms Part1&2
本文是根据《算法(第四版)》的个人总结,如有错误,请批评指正。
一、下压栈的定义
下压栈(简称栈)是一种基于后进先出(LIFO)策略的集合类型。栈只有一个出口,允许新增元素(只能在栈顶上增加)、移出元素(只能移出栈顶元素)等操作。
当用例使用foreach语句迭代遍历栈中的元素时,元素的处理顺序和它们被压人栈中的顺序正好相反。
二、下压栈的实现
1.定容栈(数组实现)
import java.util.Iterator;
import java.util.NoSuchElementException; public class FixedCapacityStack<Item> implements Iterable<Item> {
private Item[] a; // holds the items
private int N; // number of items in stack // create an empty stack with given capacity
public FixedCapacityStack(int capacity) {
a = (Item[]) new Object[capacity]; // no generic array creation
N = 0;
} public boolean isEmpty() { return N == 0; }
public void push(Item item) { a[N++] = item; }
public Item pop() { return a[--N]; }
public Iterator<Item> iterator() { return new ReverseArrayIterator(); } public class ReverseArrayIterator implements Iterator<Item> {
private int i = N-1; public boolean hasNext() {
return i >= 0;
} public Item next() {
if (!hasNext()) throw new NoSuchElementException();
return a[i--];
} public void remove() {
throw new UnsupportedOperationException();
}
} public static void main(String[] args) {
int max = Integer.parseInt(args[0]);
FixedCapacityStack<String> stack = new FixedCapacityStack<String>(max);
while (!StdIn.isEmpty()) {
String item = StdIn.readString();
if (!item.equals("-")) stack.push(item);
else if (stack.isEmpty()) StdOut.println("BAD INPUT");
else StdOut.print(stack.pop() + " ");
}
StdOut.println(); // print what's left on the stack
StdOut.print("Left on stack: ");
for (String s : stack) {
StdOut.print(s + " ");
}
StdOut.println();
}
}
2.动态调整大小的栈(数组实现)
import java.util.Iterator;
import java.util.NoSuchElementException; public class ResizingArrayStack<Item> implements Iterable<Item> {
private Item[] a; // 声明数组
private int n; // 栈中元素数量 public ResizingArrayStack() {
a = (Item[]) new Object[2];
n = 0;
} public boolean isEmpty() {
return n == 0;
} public int size() {
return n;
} // 将栈移动到一个大小为max的新数组
private void resize(int capacity) {
assert capacity >= n; Item[] temp = (Item[]) new Object[capacity];
for (int i = 0; i < n; i++) {
temp[i] = a[i];
}
a = temp;
} public void push(Item item) {
if (n == a.length) resize(2*a.length); // 若数组已满,则调整数组大小为原先的两倍
a[n++] = item;
} public Item pop() {
if (isEmpty()) throw new NoSuchElementException("Stack underflow");
Item item = a[n-1];
a[n-1] = null; // 避免对象游离
n--;
// 若栈的元素个数只有数组大小的1/4,则调整数组大小为原先的1/2
if (n > 0 && n == a.length/4) resize(a.length/2);
return item;
} public Item peek() {
if (isEmpty()) throw new NoSuchElementException("Stack underflow");
return a[n-1];
} public Iterator<Item> iterator() {
return new ReverseArrayIterator();
} private class ReverseArrayIterator implements Iterator<Item> {
private int i; public ReverseArrayIterator() {
i = n-1;
} public boolean hasNext() {
return i >= 0;
} public void remove() {
throw new UnsupportedOperationException();
} public Item next() {
if (!hasNext()) throw new NoSuchElementException();
return a[i--];
}
} public static void main(String[] args) {
ResizingArrayStack<String> stack = new ResizingArrayStack<String>();
while (!StdIn.isEmpty()) {
String item = StdIn.readString();
if (!item.equals("-")) stack.push(item);
else if (!stack.isEmpty()) StdOut.print(stack.pop() + " ");
}
StdOut.println("(" + stack.size() + " left on stack)");
}
}
解释对象游离:
- 对象游离概念:保存了一个不需要的对象的引用。
- 如何避免对象游离:Java的垃圾回收机制是回收所有无法被访问的对象的内存。在我们对pop()的实现中,被弹出的元素的引用依然存在与数组中,使得即使该元素被使用完后也无法将其回收。所以需要将数组中的这个引用覆盖来避免对象游离,只要将被弹出的数组位置的元素值设置为null,就可以覆盖无用的引用,并使系统使用完被弹出的元素后回收它的内存。
3.链表实现
import java.util.Iterator;
import java.util.NoSuchElementException; public class Stack<Item> implements Iterable<Item> {
private Node<Item> first; // 栈顶(最先添加的元素)
private int n; // 元素数量 // 定义链表结点的内部类
private static class Node<Item> {
private Item item;
private Node<Item> next;
} public Stack() {
first = null;
n = 0;
} public boolean isEmpty() {
return first == null;
} public int size() {
return n;
} // 在表头插入节点
public void push(Item item) {
Node<Item> oldfirst = first;
first = new Node<Item>();
first.item = item;
first.next = oldfirst;
n++;
} // 在表头删除节点
public Item pop() {
if (isEmpty()) throw new NoSuchElementException("Stack underflow");
Item item = first.item; // save item to return
first = first.next; // delete first node
n--;
return item; // return the saved item
} public Item peek() {
if (isEmpty()) throw new NoSuchElementException("Stack underflow");
return first.item;
} public String toString() {
StringBuilder s = new StringBuilder();
for (Item item : this) {
s.append(item);
s.append(' ');
}
return s.toString();
} public Iterator<Item> iterator() {
return new ListIterator<Item>(first);
} private class ListIterator<Item> implements Iterator<Item> {
private Node<Item> current; public ListIterator(Node<Item> first) {
current = first;
} public boolean hasNext() {
return current != null;
} public void remove() {
throw new UnsupportedOperationException();
} public Item next() {
if (!hasNext()) throw new NoSuchElementException();
Item item = current.item;
current = current.next;
return item;
}
} public static void main(String[] args) {
Stack<String> stack = new Stack<String>();
while (!StdIn.isEmpty()) {
String item = StdIn.readString();
if (!item.equals("-"))
stack.push(item);
else if (!stack.isEmpty())
StdOut.print(stack.pop() + " ");
}
StdOut.println("(" + stack.size() + " left on stack)");
}
}
三、下压栈不同实现方式的比较--Resizing Array (可调整大小的数组) VS. Linked List(链表)
1.Resizing Array (可调整大小的数组)
- 构造含有N(N是2的幂)个元素的栈,数据结构初识为空,N次连续的push()调用需要访问数组元素的次数:N+(4+8+......+2N)=5N-4,前面的N表示添加N个元素时push()中a[n++] = item访问数组N次,后面(4+8+......+2N)是push()中每次数组长度加倍时,resize()方法初始化数据结构需要访问数组次数的总和。因此,每次操作访问数组的平均次数为常数,但最后一次操作所需的时间是线性的。这是一种均摊分析,将少量昂贵操作的成本通过各种大量廉价操作平摊。但是这种方式在许多场景下不适用,当遇到需要增加(或减少)数组大小时,导致push()压栈(或出栈)的耗时很长;
- 占用总的时间和内存相对较少,数组内存24+xN(x根据Item类型而定,整数4个字节)。
2.Linked List(链表)
- 每次操作(出栈和压栈)都是常数时间,操作的速度平稳;
- 占用总的时间和内存比较大,为了维持连接,每个Stack Node需要40个字节的内存。
四、下压栈的应用
实例:Dijkstra的双栈算数表达式求值算法
- 将操作数压入操作数栈;
- 将运算符压入运算符栈;
- 忽略左括号;
- 在遇到右括号时,弹出一个运算符,弹出所需数量的操作数,并将运算符和操作数的运算结果压入操作数栈。
public class Evaluate {
public static void main(String[] args) {
Stack<String> ops = new Stack<String>();
Stack<Double> vals = new Stack<Double>(); while (!StdIn.isEmpty()) {
String s = StdIn.readString();
if (s.equals("(")) ;
else if (s.equals("+")) ops.push(s);
else if (s.equals("-")) ops.push(s);
else if (s.equals("*")) ops.push(s);
else if (s.equals("/")) ops.push(s);
else if (s.equals("sqrt")) ops.push(s);
else if (s.equals(")")) {
String op = ops.pop();
double v = vals.pop();
if (op.equals("+")) v = vals.pop() + v;
else if (op.equals("-")) v = vals.pop() - v;
else if (op.equals("*")) v = vals.pop() * v;
else if (op.equals("/")) v = vals.pop() / v;
else if (op.equals("sqrt")) v = Math.sqrt(v);
vals.push(v);
}
else vals.push(Double.parseDouble(s));
}
StdOut.println(vals.pop());
}
}
作者: 邹珍珍(Pearl_zhen)
出处: http://www.cnblogs.com/zouzz/
声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出 原文链接 如有问题, 可邮件(zouzhenzhen@seu.edu.cn)咨询.
下压栈(LIFO)详解的更多相关文章
- Java中堆内存和栈内存详解2
Java中堆内存和栈内存详解 Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,ja ...
- Java中堆内存和栈内存详解
Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间 ...
- 【learning】 单调队列与单调栈用法详解
1.单调栈 单调栈是指一个栈内部的元素具有严格单调性的一种数据结构,分为单调递增栈和单调递减栈. 其具有以下两个性质: 1,满足栈底到栈顶的元素具有严格单调性. 2,满足栈的先进后出特性,越靠近栈顶的 ...
- 【基于初学者的SSH】struts2 值栈的详解与struts2标签库+ognl表达式
一:什么是值栈:struts2里面本身提供的一种存储机制,类似于域对象,值栈,可以存值和取值 特点:先进后出,最上面的元素叫做栈顶,也叫压栈. <s:debug></s:debug& ...
- Java中堆内存和栈内存详解【转】
Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间 ...
- Java性能分析之线程栈详解与性能分析
Java性能分析之线程栈详解 Java性能分析迈不过去的一个关键点是线程栈,新的性能班级也讲到了JVM这一块,所以本篇文章对线程栈进行基础知识普及以及如何对线程栈进行性能分析. 基本概念 线程堆栈也称 ...
- 详解JavaScript调用栈、尾递归和手动优化
调用栈(Call Stack) 调用栈(Call Stack)是一个基本的计算机概念,这里引入一个概念:栈帧. 栈帧是指为一个函数调用单独分配的那部分栈空间. 当运行的程序从当前函数调用另外一个函数时 ...
- Java堆、栈和常量池以及相关String详解
一:在JAVA中,有六个不同的地方可以存储数据: 1. 寄存器(register). 这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部.但是寄存器的数量极其有限,所以寄存器由编译器根据 ...
- “全栈2019”Java多线程第三十二章:显式锁Lock等待唤醒机制详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
随机推荐
- 传入class、id name 的函数封装
function chooseDate(idName){ 2 $('#' + idName).click(function(){ //执行函数 4 }); 5 }; 6 //传入的 dataOne 就 ...
- Pycharm 设置
1:显示行号 打上对勾OK 2:设置作者 & 文件编码 3:选择切换Python的版本
- 采用tcp协议和UDP协议实现简单的聊天功能
Date: 2019-06-19 Author: Sun 一. Python3输出带颜色字体 实现过程: 终端的字符颜色是用转义序列控制的,是文本模式下的系统显示功能,和具体的语言无关. 转义 ...
- Redis详解入门篇(转载)
Redis详解入门篇(转载) [本教程目录] 1.redis是什么2.redis的作者3.谁在使用redis4.学会安装redis5.学会启动redis6.使用redis客户端7.redis数据结构 ...
- sublime Text3的使用
sublime text百度百科: Sublime Text 是一个代码编辑器,也是HTML和散文先进的文本编辑器.Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python ...
- HDU 1021 Fibonacci Again( 同余水 )
链接:传送门 题意:现在给出 Fibonacci numbers: F(0) = 7, F(1) = 11, F(n) = F(n-1) + F(n-2) (n>=2).问第 n 项能不能整除 ...
- 训练1-J
把一个偶数拆成两个不同素数的和,有几种拆法呢? Input 输入包含一些正的偶数,其值不会超过10000,个数不会超过500,若遇0,则结束. Output 对应每个偶数,输出其拆成不同素数的个数,每 ...
- 1、Ansible初识简要介绍及安装
1.Ansible简介 1.1 Ansible介绍 Ansible 是一个简单的自动化运维管理工具,基于Python开发,集合了众多运维工具(puppet.cfengine.chef.func.fab ...
- TCP连接的11中状态
1.CLOSED:初始状态,表示TCP连接时关闭状态,没有任何连接. 2.LISTEN:表示服务器端的某个SOCKET处于监听状态,可以接收客户端的连接请求. 3.SYN_RCVD:表示服务器接收到来 ...
- linux基础正则
1.^word #匹配word开头的内容,vi/vim编辑器里^代表一行的开头. 2.word$ #匹配以word结尾的内容,vi/vim编辑器里$代表一行的结尾. 3.^$ #表示空行 4.. #代 ...