栈
下压栈(或简称栈)是一种基于后进先出(LIFO)策略的集合类型。
学习栈可以先从一个经典的例子切入:算术表达式求值。
算术表达式求值
1
| ( 1 + ( ( 2 + 3 ) * ( 4 * 5 ) ) )
|
上述表达式的结果为101,但用 java 语言如何实现这个运算呢?答案之一就是:Dijkstra的双栈算术表达式求值算法。基本思路如下:
- 创建2个栈(一个保存运算符,一个保存操作数)
- 将操作数压入操作数栈
- 将运算符压入运算符栈
- 忽略左括号
- 遇到右括号时,弹出一个运算符,弹出所需数量的操作数,并将运算符和操作数的运算结果压入操作数栈
实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public static void main(String[] args) { Stack<String> ops = new Stack<>(); Stack<Double> vals = new Stack<>();
Scanner sc = new Scanner(System.in); while (sc.hasNext()) { String s = sc.next(); if (s.equals("(")) ; else if (s.equals("+")) ops.add(s); else if (s.equals("-")) ops.add(s); else if (s.equals("*")) ops.add(s); else if (s.equals("/")) ops.add(s); else if (s.equals("sqrt")) ops.add(s); else if (s.equals(")")) { String op = ops.pop(); Double val = vals.pop(); if (op.equals("+")) val = vals.pop() + val; else if (op.equals("-")) val = vals.pop() - val; else if (op.equals("*")) val = vals.pop() * val; else if (op.equals("/")) val = vals.pop()/ val; else if (op.equals("sqrt")) val = Math.sqrt(val); vals.push(val); } else vals.push(Double.parseDouble(s)); } System.out.println(vals.pop()); }
|
了解了栈的一种使用场景后,我们可以尝试自己来实现一个 stack ,其基本的 API 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Stack<Item> implements Iterable<Item> { public Stack() {} public void push(Item item) {} public Item pop() {} public boolean isEmpty() {} public int size() {} }
|
用数组实现动态变容栈
首先我们可以用 java 中最常见的数据类型来做实现——数组。
由于 java 中数组是定长的,所以还需要考虑数组扩容、缩容,我们可以采用新建一个不同长度数组并把数据引用指向旧数组对应位置的方式进行 resize。
此外这里还实现了 Iterable 接口,是因为能够使用 for each 语句进行迭代遍历是集合数据类型的基本操作之一。自己写的 stack 不实现这个细节也是 ok 的,这里就不展开谈了。
实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| import java.util.Iterator;
public class Stack<Item> implements Iterable<Item> { public Item[] items; public int N;
public Stack() { new Stack(16); }
public Stack(int cap) {
items = (Item[]) new Object[cap]; }
private void resize(int max) { Item[] temp = (Item[]) new Object[max]; for (int i=0; i<N; i++) temp[i] = items[i]; items = temp; }
public void push(Item item) { if (N == items.length) resize(2 * items.length); items[N++] = item; }
public Item pop() { Item item = items[--N]; items[N] = null; if (N > 0 && N == items.length / 4) resize(items.length / 2); return item; }
public boolean isEmpty() { return N == 0; }
public int size() { return N; }
@Override public Iterator<Item> iterator() { return new StackIterator(); } class StackIterator implements Iterator<Item> { private int i = 0; @Override public boolean hasNext() { return i < N; }
@Override public Item next() { return items[i++]; } } }
|
链表
可以看到使用数组来表示集合类的抽象数据类型并不那么合适,频繁的扩容缩容都是对资源、性能的一种浪费。那有没有哪种数据结构可以作为更优解呢?答案是:链表。
链表是一种递归的数据结构,它或者为空(null),或者是指向一个结点(node)的引用,该结点含有一个泛型的元素和一个指向另一条链表的引用。
下面是一个单向链表的结点实现:
1 2 3 4
| class Node { Item item; Node next; }
|
用链表实现栈
学会如何构造链表的结点以后,就可以拿来实现栈了。
当然我们还需要知道如何在链表中做到插入、删除数据,如何遍历链表。
这里需要注意的是单向链表可以方便地实现头部插入和尾部插入这两种形式,但是删除比较方便的实现就只有一种:头部删除。考虑到栈的后进先出(LIFO)特点,这里采用头插和头删。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| import java.util.Iterator;
public class Stack<Item> implements Iterable<Item> {
private class Node { Item item; Node next;
Node() {}
Node(Item item) { this.item = item; } }
private Node first; private int N;
public Stack() {}
public void push(Item item) { Node node = new Node(item); node.next = first; first = node; N++; }
public Item pop() { Item item = first.item; first = first.next; N--; return item; }
public boolean isEmpty() { return N == 0; }
public int size() { return N; }
@Override public Iterator<Item> iterator() { return new StackIterator(); }
class StackIterator implements Iterator<Item> { Node current = first;
@Override public boolean hasNext() { return current != null; }
@Override public Item next() { Item item = current.item; current = current.next; return item; } } }
|
队列
队列与栈很相似,要说不同点在于它是一种先进先出(FIFO)的集合类型。它的 API 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Queue<Item> implements Iterable<Item> { public Queue() {} public void enqueue(Item item) {} public Item dequeue() {} public boolean isEmpty() {} public int size() {} }
|
用链表实现队列也很简单,这里使用尾插和头删,与栈相比只需要多管理一个尾结点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| import java.util.Iterator;
public class Queue<Item> implements Iterable<Item> { private class Node { Item item; Node next;
Node() {}
Node(Item item) { this.item = item; } }
private Node first; private Node last; private int N;
public Queue() {}
public void enqueue(Item item) { Node node = new Node(item); if (isEmpty()) first = node; else last.next = node; last = node; N++; }
public Item dequeue() { Item item = first.item; first = first.next; if (--N == 0) last = null; return item; }
public boolean isEmpty() { return N == 0; }
public int size() { return N; }
@Override public Iterator<Item> iterator() { return new QueueIterator(); }
class QueueIterator implements Iterator<Item> { private Node current = first;
@Override public boolean hasNext() { return current != null; }
@Override public Item next() { Item item = current.item; current = current.next; return item; } } }
|
背包
背包是一种不支持从中删除元素的集合数据类型。与队列和栈不同,它不在意元素的处理顺序。
要理解背包的概念,可以想象一个非常喜欢收集弹子球的人,一次一个,并且会不时在所有的弹子球中寻找某一颗拥有某种特点的弹子球。
一个简单的背包 API 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Bag<Item> implements Iterable<Item> { public Bag() {}
public void add(Item item) {}
public boolean isEmpty() {} public int size() {} }
|
这里也用链表实现一下,这里也和栈一样采用头插:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| public class Bag<Item> implements Iterable<Item> { private class Node { Item item; Node next;
Node() {}
Node(Item item) { this.item = item; } } private Node first; private int N;
public Bag() {}
public void add(Item item) { Node node = new Node(item); node.next = first; first = node; N++; }
public boolean isEmpty() { return N == 0; }
public int size() { return N; }
@Override public Iterator<Item> iterator() { return null; } class BagIterator implements Iterator<Item> { private Node cur = first;
@Override public boolean hasNext() { return cur != null; }
@Override public Item next() { Item item = cur.item; cur = cur.next; return item; } } }
|