背包、队列和栈的实现(基于链表)
在 Java 链表中总结了链表及链表的基本操作,弄懂了 Java 链表,给出背包、队列和栈的实现就很简单了。
栈的实现
删除链表尾结点比较麻烦,而添加、删除首结点很方便,所以算法 1 将栈保存为一条链表,将表头作为栈的顶部,实例变量 first 指向栈顶,这样入栈、出栈都很方便。
算法 1 栈的实现(基于链表)
import java.util.Iterator;
import java.util.Scanner;
public class Stack<Item> implements Iterable<Item>
{
private Node first; // 栈顶(最近添加的元素)
private int N; // 元素数量
private class Node
{ // 定义了结点的嵌套类
Item item;
Node next;
}
public boolean isEmpty() { return first == null; } // 或:N == 0
public 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;
}
public Iterator<Item> iterator() { return new StackIterator(); }
private class StackIterator 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;
}
}
public static void main(String[] args)
{
Stack<String> stack = new Stack<String>();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext())
{
String item = scanner.next();
if (!item.equals("-")) stack.push(item);
else if (!stack.isEmpty()) System.out.print(stack.pop() + " ");
}
System.out.println("(" + stack.size() + " left on stack)");
scanner.close();
}
}
图 2 显示了图 1 中测试的轨迹。
注:图片中的 StdIn 和 StdOut 是《算法(第四版)》中的工具库(下同),在这里功能上分别等价于 Java 中的 System.in 和 System.out。
队列的实现
删除链表尾节点比较麻烦,而添加尾结点、删除首节点很容易,所以算法 2 将队列表示为一条从最早插入的元素到最近插入的元素的链表,实例变量 first 指向表头,将表头作为队列开头,方便出列,实例变量 last 指向表尾,将表尾作为队列结尾,方便入列。
也可以像栈那样将表头作为队列结尾、将表尾作为队列开头,但需要的代码更多,因为出列时需要删除尾结点,删除尾结点比删除首结点麻烦。
算法 2 队列的实现(基于链表)
import java.util.Iterator;
import java.util.Scanner;
public class Queue<Item> implements Iterable<Item>
{
private Node first; // 指向最早添加的结点的链接
private Node last; // 指向最近添加的结点的链接
private int N; // 队列中的元素数量
private class Node
{ // 定义了结点的嵌套类
Item item;
Node next;
}
public boolean isEmpty() { return first == null; } // 或: N == 0.
public 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;
}
public Iterator<Item> iterator() { return new QueueIterator(first); }
private class QueueIterator implements Iterator<Item>
{ // 支持先进先出的迭代
private Node current;
public QueueIterator(Node first) { current = first; }
public boolean hasNext() { return current != null; }
public Item next()
{
if (!hasNext()) return null;
Item item = current.item;
current = current.next;
return item;
}
}
public static void main(String[] args)
{
Queue<String> queue = new Queue<String>();
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext())
{
String item = scanner.next();
if (!item.equals("-"))
queue.enqueue(item);
else if (!queue.isEmpty())
System.out.print(queue.dequeue() + " ");
}
System.out.println("(" + queue.size() + " left on queue)");
scanner.close();
}
}
图 4 显示了图 3 中测试的轨迹。
背包的实现
用链表实现背包只需要将栈中的 push() 改名为 add(),并去掉 pop() 的实现,如算法 1.4 所示。
import java.util.Iterator;
public class Bag<Item> implements Iterable<Item>
{
private Node first; //链表的首结点
private int N; // 元素数量
private class Node
{
Item item;
Node next;
}
public boolean isEmpty() { return first == null; } // 或:N == 0
public int size() { return N; }
public void add(Item item)
{ // 和 Stack 的 push() 方法完全相同
Node oldfirst = first;
first = new Node();
first.item = item;
first.next = oldfirst;
}
public Iterator<Item> iterator()
{ return new ListIterator(); }
private class ListIterator 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;
}
}
}
性能
算法和数据结构是相辅相成的。链表的使用使上述背包、队列和栈达到了任意集合类数据类型的最佳性能:
❏ 所需的空间总是和集合的大小成正比;
❏ 操作所需的时间总是和集合的大小无关。
总结自《算法(第四版)》1.3 背包、队列和栈