简单地回顾一下数据结构中的相关内容java
不支持从中删除元素的数据类型。web
背包不关心元素返回的顺序,经常使用于直接向集合中插入元素,或者遍历已有的元素。算法
背包API数组
Bag() //建立一个空背包 void add(Item item) //添加一个元素 boolean isEmpty() //背包是否为空 int size//背包中元素的数量
First in first out.遵循先进先出数据结构
队列APIsvg
QueueOfStrings() //建立一个空队列 void enqueue(String item) //入队 String dequeue() //出队 boolean isEmpty() //是否为空队 int size() //队大小
队的两种存储结构:(1)队列的顺序存储结构 (2)链队lua
改进“假溢出”:循环队列spa
Last in first out.后进先出指针
栈APIcode
StackOfStrings() //建立空栈 void push(String item) //压栈 String pop() //出栈 boolean isEmpty() //是否为空栈 int size() //栈的大小
栈的两种存储结构:(1)顺序栈 (2)链栈
对象游离(loitering):在栈的数组实现中有对象的引用,但咱们没有真正使用它。因此当栈顶减少时,在数组中仍然有咱们已经出栈的对象的指针(再也不使用)
Java的垃圾收集策略是回收全部没法被访问的对象的内存。在咱们对pop()的实现中,被弹出的元素的引用仍然存在于数组中。这个元素实际上就是个孤儿了,没有谁会再访问它,但Java编译器无法知道这一点,除非该引用被覆盖。这种状况(保存一个不须要的对象的引用)成为游离。在这里,避免对象游离很简单,只需将被弹出的数组元素的值设为null便可,这将覆盖无用的引用,并使系统在使用完被弹出的元素后回收它的内存。
改进空栈溢出:将要出栈的元素对应的s[N]设置为null。
public String pop() { String item = s[--N]; s[N] = null; return item; }
针对顺序存储结构,由数据量来调整数组大小。由于选择用数组表示栈的意味着要先预先估计栈的最大容量。当在栈空或几乎为空时会浪费大量内存。
顺序栈为例。平时,在大小为N的顺序栈栈满时,都会建立一个2N大小的新顺序栈,将旧栈的数据转移到新栈去保存。
**反复倍增法:**当数组填满时,创建一个大小翻倍的新数组,而后将全部元素复制过去。
public ResizingArrayStackOfStrings() { s = new String[1]; } public void push(String item) { if (N == s.length) //栈满 resize(2 * s.length);//在插入元素前,将数组长度调整为两倍 s[N++] = item; } private void resize(int capacity) { String[] copy = new String[capacity]; for (int i = 0; i < N; i++) copy[i] = s[i]; s = copy;//从新设置实例变量 }
利用头插法插入N,数组的访问次数为:N + (2+4+8+…+N) ~ 3N。先访问数组一次 + 对于复制的内容要访问两次数组 ≈ 访问三次。
push() 当数组满时。新建一个大小翻倍的数组
pop() 当数组为半满时,数组s[]的大小减半
可能出现的问题:数组抖动
抖动(thrashing):当恰好反复交替入栈出栈。当数组满了的时候,就会出现反复翻倍、减半、翻倍、减半,而且每一个操做都会新建数组,都要花掉正比与N的时间。
push() 当数组满时。新建一个大小翻倍的数组
pop() 当数组为1/4满时,数组s[]的大小减半
每次处理链表都须要一些额外的时间和空间,相对而言会慢一点。
而数组大小的调整,能分摊每次的操做时间,同时也减小了空间的浪费。
能够用它来存储任意类型的数据
在每份API中,类名后的记号是将Item定义为一个类型参数
public class Stack<Item> { private Node first = null; private class Node { Item item; Node next; } public boolean isEmpty() { return first == null; } public void push(Item item) { Node oldfirst = first; first = new Node(); first.item = item; first.next = oldfirst; } public Item pop() { Item item = first.item; first = first.next; return item; } }
能够将Stack理解为某种元素的栈。在实现Stack时,咱们并不知道的具体类型,但在用例时能够用栈来处理任意类型的数据。
例如:用栈处理String对象
Stack<String> stack = new Stack<String>();
若是没有泛型,咱们就须要写每种数据类型定义不一样的的API。
容许客户端遍历集合中的元素,但没必要让客户端知道咱们用数组仍是链表
foreach语句是while的一种简写,与while语句等价。
Stack<String> collection = new Stack<String>(); //... for(String s:collection) StdOut.println(s); //... Iterator<String> i = collection.iterator(); while(i.hasNext()) { String s = i.next; StdOut.println(s); }
在Java中,咱们使用接口机制来指定一个类所必须实现的方法。对于可迭代的集合数据类型,java已经为咱们定义了所需的接口。
要使一个类可迭代
例:
import java.util.Iterator; public class Stack<Item> implements Iterable<Item> { //... public Iterator<Item> iterator() //接口 { return new ListIterator(); } //命名为Listlterator的内部类实现Iterator接口,而且是泛化(generic)的 private class ListIterator implements Iterator<Item> { private Node current = first; //first是处于表头的元素,咱们要维护迭代器中实例变量current,用current存储当前正在遍历的元素 public boolean hasNext() { return current != null; } //完成遍历后会返回“假”,还没完成就返回真 public void remove() { /* not supported */ } public Item next() //提供要遍历的下一个元素 {//获取下一个元素,如同移除表头同样,取出current,用current引用指向下一个元素,返回item. Item item = current.item; current = current.next; return item; } } }
迭代器:是一个实现了hasNext()和next()方法的类的对象。
如:
public class Stack<Item> implements Iterable<Item> { //… public Iterator<Item> iterator() { return new ReverseArrayIterator(); } private class ReverseArrayIterator implements Iterator<Item> { private int i = N; public boolean hasNext() { return i > 0; } public void remove() { /* not supported */ } public Item next() { return s[--i]; }//出栈 } }
remove()方法为空,但愿避免在迭代中穿插可以修改数据结构的操做。
从左到右处理表达式,维护两个栈。若是看到数值,放在数值栈上;若是看到操做符,放在操做符栈上;遇到左括号略过;遇到右括号,出栈操做符和两个数值,将运算结果入栈。
ppt中给出的代码
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(")"))//右括号,出栈操做符和两个数值,将运算结果入栈 { String op = ops.pop(); if (op.equals("+")) vals.push(vals.pop() + vals.pop()); else if (op.equals("*")) vals.push(vals.pop() * vals.pop()); } else vals.push(Double.parseDouble(s)); } StdOut.println(vals.pop()); } }
改进:运算符压栈前先与栈顶的运算符比较优先级。栈顶的优先级高,则出栈运算符和操做数。栈顶的优先级小,则运算符入栈。