背包、队列和栈的实现(基于链表)

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();
	}
}
图 1 测试 Stack
img

图 2 显示了图 1 中测试的轨迹。

图 2 Stack 的测试轨迹
img

注:图片中的 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();
	}
}
图 3 测试 Queue
img

图 4 显示了图 3 中测试的轨迹。

图 4 Queue 的测试轨迹
img

背包的实现

用链表实现背包只需要将栈中的 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 背包、队列和栈

posted @ 2022-01-31 23:22  Higurashi-kagome  阅读(55)  评论(0编辑  收藏  举报