背包、栈和队列(集合类数据类型的实现)

作者:disappearedgod
时间:2014-4-27

前言

主要根据的是《算法》forth Edition来写的。在后面的时间会逐渐加上一些题目。

正文

许多进出数据结构都和对象的集合有关。具体来说,数据类型的值就是一组对象的集合,所有操作都是关于添加、删除或是访问集合中的对象。
背包、队列和栈的不同之处在于删除或者访问对象的顺序不同。

API

每份API都含有一个无参构造函数、一个向集合中添加单个元素的方法、一个测试集合是否为空的方法和一个返回集合大小的方法。

Bag

public class Bag<Item>                          implements Iterable<Item>
Bag()                                                          create an empty bag
void add(Item item)                                add an item
boolean isEmpty()                                   is the bag empty?
int size()                                                    number of items in the bag

FIFO queue

public class Queue<Item>                   implements Iterable<Item>
Queue()                                                  create an empty queue
void enqueue(Item item)                     add an item
Item dequeue()                                     remove the least recently added item
boolean isEmpty()                                is the queue empty?
int size()                                                 number of items in the queue

Pushdown (LIFO) stack

public class Stack<Item>                   implements Iterable<Item>
Stack()                                                  create an empty stack
void push(Item item)                         add an item
Item pop()                                          remove the most recently added item
boolean isEmpty()                              is the stack empty?
int size()                                               number of items in the stack

介绍

背包

背包是一种不支持从中删除元素的集合数据类型——它的目的就是帮助用例收集元素并迭代遍历所有收集到的元素(用例也可以检查背包是否为空或者获取背包中元素的数量)。



测试用例

public class Stats
{
public static void main(String[] args)
{
Bag<Double> numbers = new Bag<Double>();
while (!StdIn.isEmpty())
numbers.add(StdIn.readDouble());
int N = numbers.size();
double sum = 0.0;
for (double x : numbers)
sum += x;
double mean = sum/N;
sum = 0.0;
for (double x : numbers)
sum += (x - mean)*(x - mean);
double std = Math.sqrt(sum/(N-1));
StdOut.printf("Mean: %.2f\n", mean);
StdOut.printf("Std dev: %.2f\n", std);
}
}

结果
% java Stats
100
99
101
120
98
107
109
81
101
90
Mean: 100.60
Std dev: 10.51


先进先出队列

FIFO队列(or Queue)是一种基于FIFO策略的集合类型。

测试用例
public static int[] readInts(String name)
{
In in = new In(name);
Queue<Integer> q = new Queue<Integer>();
while (!in.isEmpty())
q.enqueue(in.readInt());
int N = q.size();
int[] a = new int[N];
for (int i = 0; i < N; i++)
a[i] = q.dequeue();
return a;
}


下压栈

下压栈(or Stack)是一种基于LIFO策略的集合类型。

测试用例
public class Reverse
{
    public static void main(String[] args)
    {
        Stack<Integer> stack;
        stack = new Stack<Integer>();
        while (!StdIn.isEmpty())
            stack.push(StdIn.readInt());
        for (int i : stack)
            StdOut.println(i);
    }
}

集合类数据类型的实现

定容栈(FixedCapacityStackofStrings)

我们先来看一种表示容量固定的字符串栈的抽象数据类型。这个栈是用数组实现的。(缺点是字符串数组)

定容栈的API

public class FixedCapacityStackOfStrings
FixedCapacityStackOfStrings(int cap)                   create an empty stack of capacity cap
void push(String item)                                            add a string
String pop()                                                            remove the most recently added string
boolean isEmpty()                                                  is the stack empty?
int size()                                                                 number of strings on the stack

测试用例
public static void main(String[] args)
{
    FixedCapacityStackOfStrings s;
    s = new FixedCapacityStackOfStrings(100);
    while (!StdIn.isEmpty())
    {
        String item = StdIn.readString();
        if (!item.equals("-"))
            s.push(item);
        else if (!s.isEmpty()) StdOut.print(s.pop() + " ");
    }
    StdOut.println("(" + s.size() + " left on stack)");
}


使用方法
% more tobe.txt
to be or not to - be - - that - - - is
% java FixedCapacityStackOfStrings < tobe.txt
to be not that or be (2 left on stack)


数据类型的实现

public class FixedCapacityStackOfStrings
{
    private String[] a; // stack entries
    private int N; // size
    public FixedCapacityStackOfStrings(int cap)
    { a = new String[cap]; }
    public boolean isEmpty() { return N == 0; }
    public int size() { return N; }
    public void push(String item)
    { a[N++] = item; }
    public String pop()
    { return a[--N]; }
}

泛型

FixedCapacityStackOfStrings的第一个缺点是它只能处理String对象。如果需要一个double值的栈,你就需要用类似的代码实现另一个类,也就是把所有的String都替换为double。 现在先将String替换为Item。
public class FixedCapacityStack<Item>
Item是一个类型参数,用于表示用例将会使用的某种具体类型的象征性的占位符。可以将FixedCapacityStack<Item>理解为某种元素的栈,这正是我们想要的。

API

FixedCapacityStack(int cap)                       create an empty stack of capacity cap
void push(Item item)                                    add an item
Item pop()                                                    remove the most recently added item
boolean isEmpty()                                       is the stack empty?
int size()                                                      number of items on the stack
测试用例
public static void main(String[] args)
{
	FixedCapacityStack<String> s;
	s = new FixedCapacityStack<String>(100);
	while (!StdIn.isEmpty())
	{
		String item = StdIn.readString();
		if (!item.equals("-"))
			s.push(item);
		else if (!s.isEmpty()) 
			StdOut.print(s.pop() + " ");
	}
	StdOut.println("(" + s.size() + " left on stack)");
}


使用方法
% more tobe.txt
to be or not to - be - - that - - - is
% java FixedCapacityStack < tobe.txt
to be not that or be (2 left on stack)


数据类型的实现

public class FixedCapacityStack<Item>
{
	private Item[] a; // stack entries
	private int N; // size
	public FixedCapacityStack(int cap)
	{ a = (Item[]) new Object[cap]; }
	public boolean isEmpty() { return N == 0; }
	public int size() { return N; }
	public void push(Item item)
	{ a[N++] = item; }
	public Item pop()
	{ return a[--N]; }
}

调整数组大小

选择用数组表示栈内容意味着用例必须预先估计栈的最大容量。在JAVA中,数组一旦创建,其大小是无法改变的,因此栈使用的空间只能是这个最大容量的一部分。选择大容量的用例在栈为空或几乎为空时会浪费大量的内存。

另一方面,如果集合变得比数组更大那么用例有可能溢出。为此,push()方法需要在代码中检测是否已经满,我们的API中也应该含有一个isFull()方法来允许用例检测栈是否已满。

实际上,完成这些目标非常简单。首先,实现一个方法将栈移动到另一个大小不同的数组中:
private void resize(int max)
{ // Move stack of size N <= max to a new array of size max.
	Item[] temp = (Item[]) new Object[max];
	for (int i = 0; i < N; i++)
	temp[i] = a[i];
	a = temp;
}
现在,在push()中,检查数组是否太小。具体来说,我们会通过检查栈大小N和数组大小a.length是否相等来检查数组是否能够容纳新的元素。如果没有多余的空间,则会将长度加倍。然后就可以和从前一样用a[N++]=item插入新元素:
public void push(String item)
{ // Add item to top of stack.
	if (N == a.length) resize(2*a.length);
	a[N++] = item;
}
类似,在pop()中,首先删除站定元素,然后如果数组太大我们就将它的长度减半。只要稍加思考,你就明白正确的检测条件是栈大小是否小于数组的四分之一。在数组长度减半之后,它的状态约为半满,在下次需要改变数组大小之前仍让能够进行多次的push()和pop()操作。
public String pop()
{ // Remove item from top of stack.
	String item = a[--N];
	a[N] = null; // Avoid loitering (see text).
	if (N > 0 && N == a.length/4) resize(a.length/2);
		return item;
}

这样,栈永远不会溢出,使用率也永远不会低于1/4


对象游离
在对被pop()的实现中,被弹出的元素的引用仍韩存在于数组中。这个元素实际上已经是一个永远也不会再被访问的元素,但是JAVA垃圾分类器没有办法知道,除非引用被覆盖。——对象游离
将数组元素这设置为null,这将覆盖无用的引用并使系统可以在用例使用完被弹出的元素后回收它的内存。
迭代

使用JAVA的foreach语句通过迭代遍历并处理集合中的每个元素。例如,
Stack<String> collection = new Stack<String>();
...
for (String s : collection)
StdOut.println(s);
...
这里实际上用到的是Iterator的简写,即:
Iterator<String> i = collection.iterator();
while (i.hasNext())
{
    String s = i.next();
    StdOut.println(s);
}
也就是说,在任意可迭代的集合数据类型中,我们都要实现:
  • 集合数据类型不需实现一个iterator()方法并返回一个Iterator对象
  • Iterator类必须包含两个方法:hasNext(),next()。分别用来:返回一个bool值和返回集合中的一个泛型元素。
需要使用Java封装的Iterable<Item>方法。
public interface Iterator<Item>
{
	boolean hasNext();
	Item next();
	void remove();
}


希望避免在迭代中穿插能够修改数据结构的操作。所以,对于ReverseArrayIterator来说,这些方法都只需要一行代码,它们实现在栈类的一个嵌套类中:
private class ReverseArrayIterator implements Iterator<Item>
{
	private int i = N;
	public boolean hasNext() { return i > 0; }
	public Item next() { return a[--i]; }
	public void remove() { }
}

下压栈算法(动态调整数组大小实现)

/*************************************************************************
 *  Compilation:  javac ResizingArrayStack.java
 *  Execution:    java ResizingArrayStack < input.txt
 *  Data files:   http://algs4.cs.princeton.edu/13stacks/tobe.txt
 *  
 *  Stack implementation with a resizing array.
 *
 *  % more tobe.txt 
 *  to be or not to - be - - that - - - is
 *
 *  % java ResizingArrayStack < tobe.txt
 *  to be not that or be (2 left on stack)
 *
 *************************************************************************/

import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 *  The ResizingArrayStack class represents a last-in-first-out (LIFO) stack
 *  of generic items.
 *  It supports the usual push and pop operations, along with methods
 *  for peeking at the top item, testing if the stack is empty, and iterating through
 *  the items in LIFO order.
 *  

* This implementation uses a resizing array, which double the underlying array * when it is full and halves the underlying array when it is one-quarter full. * The push and pop operations take constant amortized time. * The size, peek, and is-empty operations takes * constant time in the worst case. *

* For additional documentation, see Section 1.3 of * Algorithms, 4th Edition by Robert Sedgewick and Kevin Wayne. * * @author Robert Sedgewick * @author Kevin Wayne */ public class ResizingArrayStack implements Iterable { private Item[] a; // array of items private int N; // number of elements on stack /** * Initializes an empty stack. */ public ResizingArrayStack() { a = (Item[]) new Object[2]; } /** * Is this stack empty? * @return true if this stack is empty; false otherwise */ public boolean isEmpty() { return N == 0; } /** * Returns the number of items in the stack. * @return the number of items in the stack */ public int size() { return N; } // resize the underlying array holding the elements private void resize(int capacity) { assert capacity >= N; Item[] temp = (Item[]) new Object[capacity]; for (int i = 0; i < N; i++) { temp[i] = a[i]; } a = temp; } /** * Adds the item to this stack. * @param item the item to add */ public void push(Item item) { if (N == a.length) resize(2*a.length); // double size of array if necessary a[N++] = item; // add item } /** * Removes and returns the item most recently added to this stack. * @return the item most recently added * @throws java.util.NoSuchElementException if this stack is empty */ public Item pop() { if (isEmpty()) throw new NoSuchElementException("Stack underflow"); Item item = a[N-1]; a[N-1] = null; // to avoid loitering N--; // shrink size of array if necessary if (N > 0 && N == a.length/4) resize(a.length/2); return item; } /** * Returns (but does not remove) the item most recently added to this stack. * @return the item most recently added to this stack * @throws java.util.NoSuchElementException if this stack is empty */ public Item peek() { if (isEmpty()) throw new NoSuchElementException("Stack underflow"); return a[N-1]; } /** * Returns an iterator to this stack that iterates through the items in LIFO order. * @return an iterator to this stack that iterates through the items in LIFO order. */ public Iterator iterator() { return new ReverseArrayIterator(); } // an iterator, doesn't implement remove() since it's optional private class ReverseArrayIterator implements Iterator { private int i; public ReverseArrayIterator() { i = N; } public boolean hasNext() { return i > 0; } public void remove() { throw new UnsupportedOperationException(); } public Item next() { if (!hasNext()) throw new NoSuchElementException(); return a[--i]; } } /** * Unit tests the Stack data type. */ public static void main(String[] args) { ResizingArrayStack s = new ResizingArrayStack (); while (!StdIn.isEmpty()) { String item = StdIn.readString(); if (!item.equals("-")) s.push(item); else if (!s.isEmpty()) StdOut.print(s.pop() + " "); } StdOut.println("(" + s.size() + " left on stack)"); } }


链表

链表是非java直接支持的数据结构

定义

链表是一种递归的数据结构,它或者为空(null),或者是指向一个结点(node)的引用,该结点含有一个泛型的元素和一个指向另一条链表的引用。
解析:这个定义中,结点是一个可能含有任意类型数据的抽象实体。

结点记录

首先用一个嵌套类来定义结点的抽象数据类型:
private class Node
{
    Item item;
    Node next;
}
一个Node对象含有两个实例变量,类型分别为Item(参数类型)和Node。我们会在需要使用Node类的类中定义并将它标记为private,因为他不是为用例准备的。

和任意数据类型一样,通过new Node()触发(无参的)构造函数来创建一个Node类型的对象。调用的结果是一个指向Node对象的引用,他的实力变换均被初始化为null。
Item是一个占位符,表示我们希望用链表处理任意数据类型(我们将会使用Java的泛型使之表示任意引用类型);Node类型的实例变量现实了这种数据结构的链式本质。

举例

如果first是一个指向某个Node对象的变量,我们可以使用first.item和first.node访问它的实例变量。这种类型有时候被称为记录。他们实现的不是抽象数据类型因为我们会直接使用其实例变量。但是在我们的实现中,Node和他的用例代码都被封装在相同的类中,且无法被该类的用例访问,所以我们任然能够享受数据抽象的好处。
链表表示的是一列元素

在表头插入结点


从表头删除结点




其他位置的插入和删除


遍历

for (Node x = first; x != null; x = x.next)
{
    // Process x.item.
}

栈的实现

public class Stack<Item> implements Iterable<Item>
{
	private Node first; // top of stack (most recently added node)
	private int N; // number of items
	private class Node
	{ // nested class to define nodes
		Item item;
		Node next;
	}
	public boolean isEmpty() { return first == null; } // Or: N == 0.
	public int size() { return N; }
	public void push(Item item)
	{ // Add item to top of stack.
		Node oldfirst = first;
		first = new Node();
		first.item = item;
		first.next = oldfirst;
		N++;
	}
	public Item pop()
	{ // Remove item from top of stack.
		Item item = first.item;
		first = first.next;
		N--;
		return item;
	}	
}
测试代码:
public static void main(String[] args)
{ // Create a stack and push/pop strings as directed on StdIn.
	Stack<String> s = new Stack<String>();
	while (!StdIn.isEmpty())
	{
		String item = StdIn.readString();
		if (!item.equals("-"))
			s.push(item);
		else if (!s.isEmpty()) StdOut.print(s.pop() + " ");
	}
	StdOut.println("(" + s.size() + " left on stack)");
}


队列的实现

public class Queue<Item> implements Iterable<Item>
{
	private Node first; // link to least recently added node
	private Node last; // link to most recently added node
	private int N; // number of items on the queue
	private class Node
	{ // nested class to define nodes
		Item item;
		Node next;
	}
	public boolean isEmpty() { return first == null; } // Or: N == 0.
	public int size() { return N; }
	public void enqueue(Item item)
	{ // Add item to the end of the list.
		Node oldlast = last;
		last = new Node();
		last.item = item;
		last.next = null;
		if (isEmpty()) first = last;
		else oldlast.next = last;
		N++;
	}
	public Item dequeue()
	{ // Remove item from the beginning of the list.
		Item item = first.item;
		first = first.next;
		if (isEmpty()) last = null;
		N--;
		return item;
	}
}
测试代码
public static void main(String[] args)
{ // Create a queue and enqueue/dequeue strings.
	Queue<String> q = new Queue<String>();
	while (!StdIn.isEmpty())
	{
		String item = StdIn.readString();
		if (!item.equals("-"))
		q.enqueue(item);
		else if (!q.isEmpty()) StdOut.print(q.dequeue() + " ");
	}
	StdOut.println("(" + q.size() + " left on queue)");
}



背包的实现

import java.util.Iterator;
public class Bag<Item> implements Iterable<Item>
{
	private Node first; // first node in list
	private class Node
	{
		Item item;
		Node next;
	}
	public void add(Item item)
	{ // same as push() in Stack
		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 void remove() { }
			public Item next()
			{
			Item item = current.item;
			current = current.next;
			return item;
		}
	}
}

总结

我们现在拥有两种表示对象集合的方式——数组和链表。Java内置了数组,链表也很容易使用Java标准方法实现。两者都非常基础,常常被称为顺序存储和链式存储。


后记

代码来源

  1. http://algs4.cs.princeton.edu/code/




























评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值