背包、队列和栈
- 什么是背包?队列?栈?
- 使用它们的目的?什么时候使用它们?
- 怎么实现它们?(一些问题及其优化方案)
- 链表的性能特点
- 为什么不使用Java内置的栈和队列?
- 拓展(坚持使用窄接口)
1、什么是背包?队列?栈?
它们是三种不同的数据类型。
①、背包:一种不支持从中删除元素的集合数据类型
②、队列(又称“先进先出队列”):一种基于先进先出(FIFO)策略的集合数据类型
③、栈(又称“下压栈”):一种基于后进先出(LIFO)策略的集合数据类型
2、使用它们的目的?什么时候使用它们?
①、背包:目的就是帮助用例收集元素并迭代遍历所有收集到的元素;当元素的处理顺序不重要(即:数的计算顺序和结果无关)时,使用背包。
②、队列:在用集合元素保存元素的同时保存它们的相对顺序,使它们入列的顺序和出列的顺序相同;即当元素的处理顺序和它们被添加到队列中的顺序相同时,使用队列。
③、栈:在用集合保存元素的同时颠倒它们的相对顺序,使它们入列的顺序和出列的顺序相反;即当元素的处理顺序和它们被添加到队列中的顺序正好相反时,使用栈。
3、怎么实现它们?(一些问题及其优化方案)
①、背包
链表实现
import java.util.Iterator;
/**
* 背包的链表实现
*
* @author TinyDolphin
* 2017/5/8 20:40.
*/
public class Bag<Item> implements Iterable<Item> {
//私有嵌套类:只有含有它的类能够直接访问它的实例变量。非静态的嵌套类也被称为内部类
private class Node {
Item item;
Node next;
}
private Node first; //链表的首结点
private int N = 0;
public boolean isEmpty() {
return N == 0; //或first==null
}
private int size() {
return N;
}
/**
* 和Stack的push()方法完全相同
*
* @param item 要插入的值
*/
public void add(Item item) {
Node oldFirst = first;
first = new Node();
first.item = item;
first.next = oldFirst;
N++;
}
@Override
public Iterator<Item> iterator() {
return new ListIterator();
}
private class ListIterator 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;
}
@Override
public void remove() {
}
}
}
②、队列
链表实现
import java.util.Iterator;
/**
* 队列的链表实现
*
* @author TinyDolphin
* 2017/5/8 21:20.
*/
public class Queue<Item> implements Iterable<Item> {
private class Node {
Item item;
Node next;
}
private Node first;//指向最早添加的结点的链接
private Node last; //指向最近添加的结点的链接
private int N; //队列中元素数量
public boolean isEmpty() {
return N == 0; //或first==null
}
private int size() {
return N;
}
public void enqueue(Item item) {
Node oldLast = last;
last = new Node();
last.item = item;
last.next = null;
if (isEmpty()) {
first = last;
} else {
oldLast.next = last;
}
N++;
}
public Item dequeue() {
Item item = first.item;
first = first.next;
if (isEmpty()) {
last = null;
}
N--;
return item;
}
@Override
public Iterator<Item> iterator() {
return new ListIterator();
}
private class ListIterator 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;
}
@Override
public void remove() {
}
}
}
③、栈
数组实现
- ①、该实现中,栈永远不会溢出,使用率也永远不会低于25%(除非栈为空,哪种情况下数组的大小为1;
- ②、此算法十分重要,因为它几乎(但还没有)达到了任意集合类型数据类型的实现的最佳性能:
- i、每项操作的用时都与集合大小无关;
- ii、空间需求总是不超过集合大小乘以一个常数。
import java.util.Iterator;
/**
* 栈的数组实现
* @author TinyDolphin
* 2017/5/8 15:40.
*/
public class ResizingArrayStack<Item> implements Iterable<Item> {
private Item[] a = (Item[]) new Object[1]; //栈元素
private int N = 0; //元素数量
public boolean isEmpty() {
return N == 0;
}
public int size() {
return N;
}
//调整数组大小的方法
private void resize(int max) {
Item[] temp = (Item[]) new Object[max];
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() {
Item item = a[--N];
a[N] = null; //避免出现对象游离(Loitering),即保存了一个不需要对象的引用
//在删除栈顶元素时,如果数组太大(栈大小小于数组的四分之一)则将数组长度减半
if (N > 0 && N == a.length / 4) {
resize(a.length / 2);
}
return item;
}
@Override
public Iterator<Item> iterator() {
return new ReverseArrayIterator();
}
private class ReverseArrayIterator implements Iterator<Item> {
private int i = N;
@Override
public boolean hasNext() {
return i > 0;
}
@Override
public Item next() {
return a[--i];
}
@Override
public void remove() {
//避免在迭代中穿插能够修改数据结构的操作,所以此方法空着。
}
}
}
链表实现
import java.util.Iterator;
/**
* 栈的链表实现
*
* @author TinyDolphin
* 2017/5/8 21:32.
*/
public class Stack<Item> implements Iterable<Item> {
private class Node {
Item item;
Node next;
}
private Node first;
private int N;
public boolean isEmpty() {
return N == 0; //或first==null
}
private int size() {
return N;
}
public void push(Item item) {
Node oldFirst = first;
first = new Node();
first.item = item;
first.next = oldFirst;
N++;
}
public Item pop() {
Item item = first.item;
first = first.next;
N--;
return item;
}
@Override
public Iterator<Item> iterator() {
return new ListIterator();
}
private class ListIterator 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;
}
@Override
public void remove() {
}
}
}
4、链表的性能特点
链表的使用达到了我们的最优设计目标:
- ①、它可以处理任意类型的数据;
- ②、所需的空间总是和集合的大小成正比
- ③、操作所需的时间总是和集合的大小无关。
两种表示数据集合的方式:数组(顺序存储)和链表(链式存储)
5、为什么不使用Java内置的栈和队列?
Java有一个内置的库(java.util.Stack),但你需要栈的时候请不要使用它。
内置的库(java.util.Stack)新增了几个方法(不属于栈的),如获取第 i 个元素、从栈底添加元素(而非栈顶),所以它还可以当作队列使用。尽管拥有了这些额外操作(看起来可能很有用),其实它们是累赘。
为什么呢?因为我使用某种数据类型不仅仅是为了获得我们能够想象的各种操作,也是为了准确地指定我们所需要的操作。
这么做的主要好处:在于系统能够防止我们执行一些意外的操作。
java.util.Stack 的 API 是宽接口的一个典型例子,我们通常会极力避免出现这种情况。
6. 拓展(坚持使用窄接口)
为什么要避免使用宽接口,坚持使用窄接口?例如 java.util.ArrayList 和 java.util.LinkedList
- ①、能够高效实现所有这些操作(设计只含有几个操作的接口显然比设计含有许多操作的接口更简单)
- ②、能够限制用例的行为,这将使用例代码更加易懂。