『算法』读书笔记 1.3 背包、队列和栈

Chapter 1

本章结构

1.1Java语法

1.2数据抽象

1.3集合类抽象数据类型:背包 (Bags) 、队列 (Queues) 、栈 (Stacks)

1.4面向操作的抽象数据类型

1.5连通性问题-Case Study: Union - Find ADT

 

本次笔记内容主要有两块:

1.集合类抽象数据类型的两个关键特性:泛型(Generic)可迭代(Iterable)的总结;

2.背包、队列和栈这三种基础集合数据类型的不同特点以及各自的实现分析;

 

泛型和迭代

1.泛型,又叫做参数化类型(parameterized types),可以用来存储任意数据的类型。而在每份API中,类名后的<Item>记号则将Item定义为一个类型参数,它是一个象征性的占位符(placeholder),表示的是用例将会使用的某种具体数据类型。可以将Stack<Item>理解为“某种元素的栈”。Item可被替换为任意引用数据类型。在泛型的帮助下,我们的API可以一次性处理所有类型的数据,即便是那些在未来才被定义的数据类型。

由于某些历史和技术原因,创建泛型数组在Java中是不允许的,需要使用类型转换

  1: Item[] a = (item[]) new Object[cap];

Java will automatically cast a primitive type to a wrapper type.

自动装箱(Autoboxing)- 类型参数必须被实例化为引用类型。Java的封装类型(Wrapper Types – Builtin reference types)包括:如parseInt()的静态方法;继承如toString(); compareTo(); equals()等实例方法。自动将一个原始数据类型转化为一个封装数据类型被称为自动装箱,自动将一个封装数据类型转化为一个原始数据类型被称为自动拆箱。

 

2.迭代:以某种方式访问集合中的所有元素。这种语法应该就是C#里面的foreachc语句。之所以使用迭代语句(Why),主要是代码可以变得简洁清新,而且不依赖于集合数据类型的具体实现。

  1: for (Transaction t : collection)
  2: { StdOut.println(t); }

在这里上面的foreach只是while语句的一种简写:

  1: while (t.hasNext())
  2: {
  3:     String s = t.next();
  4:     StdOut.println(s);
  5: }

在任意可迭代的集合数据类型中,若要实现类的可迭代,需要:

a.在程序的开头加入 import java.util.Iterator;

b.在类的声明中加入继承Java定义的Iterable接口的相关语句:

  1: public interface Iterable<Item>
  2: {
  3:     Iterator<Item> iterator();
  4: }

注:Iterable has a method that returns an Iterator method.

c.在类中实现iterator()方法,它从实现了Iterator接口的类中返回一个对象Iterator<Item>。我们接着在迭代器中实现hasNext()和next()方法。

  1: public Iterator<Item> iterator()
  2: {    return new ReverseArrayIterator();    }
  3: public class ReverseArrayIterator implements Iterator<Item>
  4: {
  5:     private int i = N;
  6:     public boolean hasNext() {  return i > 0;  }
  7:     public    Item next()    {  return a[--i]; }  
  8:     public    void remove()  {                 }
  9: }

注:在两种情况下需要抛异常:如果用例调用了remove()则抛出UnsupportedOperationException,如果用例在调用next()时i为0则抛出NoSuchElementException。

 

背包、队列和栈

1.栈(下压栈)

栈的基本分类

a.定容栈(Fixed-Capacity Stacks):最大容量为cap,若集合变得比数组更大那么用例有可能溢出(Overflow)

b.可调整数组大小栈:为解决大容量数组浪费内存,而小容量又可能造成溢出的问题而生,当发现栈没有多余空间时,会自动将数组长度加倍;同理,在删除栈顶元素时,如果数组太大(栈大小小于数组的四分之一)则将数组长度减半。由此可看出,非空栈的使用率永远在25%~100%之间。

栈还需要考虑的方面

a.避免出现对象游离(Loitering),即保存了一个不需要对象的引用,例如

  1: public String pop()
  2: {  return a[--N];  }

在这里虽然a[N]仍然存在,但是已不被需要。改进方案:

  1: public String pop()
  2: {
  3:     String item = a[--N];
  4:     a[N] = null;
  5:     return item;
  6: }

这样,a[N]则可以被Java垃圾回收机制回收。

 

b.Overflow和Underflow的问题:

overflow可以通过使用resizing array的实现解决;

underflow当栈为空又受到pop方法时可抛出异常;

栈的实现方式:

数据结构

优点

缺点

数组
(顺序存储)

通过索引可以直接访问任意元素
Less waste space.

在初始化时需要知道元素的数量
Every operation takes constant amortized time.(总时间会随着数组元素的增加而增加)

链表
(链式存储)

使用的空间大小和元素数量成正比
Every operation takes constant time in the worst case.(插入删除结点所需的时间和链表的长度无关)
在链表中向序列插入元素或者是从序列中删除元素都更方便。

需要通过引用访问任意元素
Uses extra time and space to deal with the links.
链表编程在调试中可能更加麻烦

数组实现

  1: import java.util.Iterator;
  2: public class ResizingArrayStack<Item> implements Iterable<Item>{
  3: 	private Item[] a = (Item[])new Object[1];
  4: 	private int N = 0;
  5: 	private void resize(int max)
  6: 	{
  7: 		Item[] temp = (Item[])new Object[max];
  8: 		for (int i = 0; i < N; i++)
  9: 			temp[i] = a[i];
 10: 		a = temp;
 11: 	}
 12: 	
 13: 	//implementations of API
 14: 	public boolean isEmpty()
 15: 	{	return N == 0;	}
 16: 	public int size()
 17: 	{	return N;		}
 18: 	public Item pop()
 19: 	{
 20: 		Item temp = a[--N];
 21: 		a[N] = null;
 22: 		if (N > 0 && N == a.length/4) resize(a.length/2);
 23: 		return temp;
 24: 	}
 25: 	public void push(Item item)
 26: 	{
 27: 		if (N == a.length) resize (2*a.length);
 28: 		a[N++] = item;
 29: 	}
 30: 	
 31: 	//implementation of Iterator
 32: 	public Iterator<Item> iterator()
 33: 	{	return new ReverseArrayIterator();	}
 34: 	private class ReverseArrayIterator implements Iterator<Item>
 35: 	{
 36: 		private int i = N;
 37: 		public boolean hasNext()	{	return i > 0;	}
 38: 		public    Item next()		{	return a[i--];	}
 39: 		public    void remove()		{					}
 40: 	}
 41: 	
 42: 	//client code
 43: 	public static void main(String[] args)
 44: 	{
 45: 		ResizingArrayStack<String> s = new ResizingArrayStack<String>();
 46: 		while (!StdIn.isEmpty())
 47: 		{
 48: 			String item = StdIn.readString();
 49: 			if (!item.equals("-"))
 50: 				s.push(item);
 51: 			else if (!s.isEmpty())	StdOut.print(s.pop() + ' ');
 52: 		}
 53: 		StdOut.println("(" + s.size() + " left on stack)");
 54: 	}
 55: }
 56: 

链表实现

  1: 
  2: public class Stack<Item> 
  3: {
  4: 	private Node first;
  5: 	private int N;
  6: 	
  7: 	private class Node
  8: 	{
  9: 		Item item;
 10: 		Node next;
 11: 	}
 12: 	
 13: 	//implementations of Stack's API with Linked-list
 14: 	public boolean isEmpty() 	{	return first == null;	}
 15: 	public     int size()		{	return N;				}
 16: 	
 17: 	public void push(Item item)
 18: 	{
 19: 		Node oldfirst = first;
 20: 		first = new Node();
 21: 		first.item = item;
 22: 		first.next = oldfirst;
 23: 		N++;
 24: 	}
 25: 	public Item pop()
 26: 	{
 27: 		Item item = first.item;
 28: 		first = first.next;
 29: 		N--;
 30: 		return item;
 31: 	}
 32: 	
 33: 	//client
 34: 	public static void main(String[] args)
 35: 	{
 36: 		Stack<String> s = new Stack<String>();
 37: 		
 38: 		while(!StdIn.isEmpty())
 39: 		{
 40: 			String item = StdIn.readString();
 41: 			if (!item.equals("-"))
 42: 				s.push(item);
 43: 			else if (!s.isEmpty())
 44: 				StdOut.print(s.pop() + "");
 45: 		}
 46: 		StdOut.println("(" + s.size() + " left on stack)");
 47: 	}
 48: 
 49: }

链表 - 在集合类的抽象数据类型实现中表示数据的合适选择。是一种递归的数据结构,或者为空,或者是指向一个结点(Node)的引用。结点是一个可能含有任意类型数据的抽象实体。链表表示的是一系列元素。

如何实现链表

首先用一个嵌套类(nested class)来定义结点的抽象数据类型。一个Node对象含有两个实例变量,分别为Item和Node。Item是一个占位符,表示我们希望用链表处理的任意数据类型。Node类型的实例变量显示了这种数据结构的链式本质。同时,Noe作为私有嵌套类,只有包含它的类能够直接访问它的实例变量,因此无需将它的实例变量声明为Public或private。

  1: private class Node
  2: {
  3:     Item item;
  4:     Node nexxt;
  5: }
链表的使用达到了我们的最优设计目标(optimum design goal):
  1. -它可以处理任意数据类型(item);
  2. -所需的空间总是和集合的大小成正比;(A stack with N items uses ~40N bytes)
  3. -操作所需的时间总是和集合的大小无关。

 

2.队列

队列是许多日常现象的自然模型,也是无数应用程序的核心。在应用程序中使用队列的主要原因是在用集合保存元素的同时保存他们的相对顺序:使他们入列顺序和出列顺序相同。

数组实现

  1: import java.util.Iterator;
  2: public class ResizingArrayQueue<Item> implements Iterable<Item>	 {
  3: 	
  4: 	private Item[] a = (Item[])new Object[1];
  5: 	private int N = 0;
  6: 	private int head = 0;       //needs two variables to index
  7: 	private int tail = a.length;
  8: 	private void resize(int max)
  9: 	{
 10: 		Item[] temp = (Item[])new Object[max];
 11: 		for (int i = 0; i < N; i++)
 12: 			temp[i] = a[i];
 13: 		a = temp;
 14: 	}
 15: 	
 16: 	//implementations of API
 17: 	public void enqueue(Item item)
 18: 	{
 19: 		if (N == a.length) resize(2*a.length);
 20: 		a[N++] = item;
 21: 		tail++;
 22: 	}
 23: 	public Item dequeue()
 24: 	{
 25: 		Item temp = a[head];
 26: 		if (N > 0 && N == a.length) resize(a.length/4);
 27: 		a[head] = null;
 28: 		head++;
 29: 		return temp;
 30: 	}
 31: 	public boolean isEmpty()
 32: 	{	return N > 0;	}
 33: 	public int size()
 34: 	{	return N;		}
 35: 	
 36: 	//implementations of Iterator
 37: 	public Iterator<Item> iterator()
 38: 	{	return new ForwardArrayIterator();	}
 39: 	private class ForwardArrayIterator implements Iterator<Item>
 40: 	{	//FIFO iteration
 41: 		private int i = head;
 42: 		public boolean hasNext()	{	return i > tail;	}
 43: 		public    Item next()		{	return a[head++];	}
 44: 		public    void remove()		{						}
 45: 	}
 46: 	
 47: 	//client
 48: 	public static void main(String[] args){
 49: 		
 50: 		ResizingArrayQueue<String> q = new ResizingArrayQueue<String>();
 51: 		
 52: 		while(!StdIn.isEmpty())
 53: 		{
 54: 			String item = StdIn.readString();
 55: 		if (!item.equals("-"))
 56: 			q.enqueue(item);
 57: 		else if (!q.isEmpty()) StdOut.print(q.dequeue() + "");
 58: 		}
 59: 		
 60: 		StdOut.println("(" + q.size() + " left on queue)");
 61: 	}
 62: }
 63: 

链表实现

  1: public class Queue<Item> {
  2: 	private Node first;
  3: 	private Node last;
  4: 	private int N;
  5: 	
  6: 	private class Node{
  7: 		Item item;
  8: 		Node next;
  9: 	}
 10: 	
 11: 	//implementations of Queue's API with Linked-list
 12: 	public boolean isEmpty()	{	return N == 0;	}
 13: 	public int size()			{	return N;		}
 14: 	
 15: 	public void enqueue(Item item){
 16: 		Node oldlast = last;
 17: 		last = new Node();
 18: 		last.item = item;
 19: 		last.next = null;
 20: 		if (isEmpty())	first = last;
 21: 		else 			oldlast.next = last;
 22: 		N++;
 23: 	}
 24: 	
 25: 	public Item dequeue(){
 26: 		Item item = first.item;
 27: 		first = first.next;
 28: 		if (isEmpty())	last = null;
 29: 		N--;
 30: 		return item;
 31: 	}
 32: 	
 33: 	//client
 34: 	public static void main(String[] args){
 35: 		
 36: 		Queue<String> q = new Queue<String>();
 37: 		
 38: 		while(!StdIn.isEmpty())
 39: 		{
 40: 			String item = StdIn.readString();
 41: 		if (!item.equals("-"))
 42: 			q.enqueue(item);
 43: 		else if (!q.isEmpty()) StdOut.print(q.dequeue() + "");
 44: 		}
 45: 		
 46: 		StdOut.println("(" + q.size() + " left on queue)");
 47: 	}
 48: }

 

 

 

 

3.背包

背包是一种不支持从中删除元素的集合数据类型

链表实现

  1: import java.util.Iterator;
  2: public class Bag<Item>  implements Iterable<Item>{
  3: 	private Node first;
  4: 	private int N;
  5: 	
  6: 	private class Node{
  7: 		Item item;
  8: 		Node next;
  9: 	}
 10: 	
 11: 	//implementations of Bag's API with Linked-list
 12: 	public boolean isEmpty()	{	return first == null;	}
 13: 	public int size()			{	return N;				}
 14: 	
 15: 	public void add(Item item){
 16: 		Node oldfirst = first;
 17: 		first = new Node();
 18: 		first.item = item;
 19: 		first.next = oldfirst;
 20: 		N++;
 21: 	}
 22: 	
 23: 	//implementations of Iteration
 24: 	public Iterator<Item> iterator(){
 25: 		return new ReverseArrayBag();
 26: 	}
 27: 	public class ReverseArrayBag implements Iterator<Item>{
 28: 		private Node current = first;
 29: 		public boolean hasNext()	{	return current == null;	}
 30: 		public Item next(){
 31: 			Item item = current.item;
 32: 			current = current.next;
 33: 			return item;
 34: 		}
 35: 		public void remove()		{							}
 36: 	}
 37: }

 

最后注意一点:Java本身也提供了一些内置库实现了Stack等数据类型。但是,由于java.util.Stack的API是宽接口(wide interface),我们无法在运行中保证高效,所以尽量避免使用。Don’t use a library until you understand its API!

posted @ 2012-12-22 15:46  Memphis Yip  阅读(1698)  评论(0编辑  收藏  举报