Coursera Algorithms第二周編程任務
Programming Assignment 2: Deques and Randomized Queues
Write a generic data type for a deque and a randomized queue. The goal of this assignment is to implement elementary data structures using arrays and linked lists, and to introduce you to generics and iterators.
Algorithms 第二周的編程任務,主要目的是編寫一個雙向鏈表,隨機隊列和一個測試客戶端。
課程給出的兩個數據類型說明(API):
Dequeue. A double-ended queue or deque (pronounced “deck”) is a generalization of a stack and a queue that supports adding and removing items from either the front or the back of the data structure. Create a generic data type Deque
that implements the following API:
public class
Deque<Item> implements Iterable<Item> { public Deque() // construct an empty deque public boolean isEmpty() // is the deque empty? public int size() // return the number of items on the deque public void addFirst(Item item) // add the item to the frontpublic void addLast(Item item) // add the item to the end public Item removeFirst() // remove and return the item from the front public Item removeLast() // remove and return the item from the end public Iterator<Item> iterator() // return an iterator over items in order from front to end public static void main(String[] args) // unit testing (optional) }
Randomized queue. A randomized queue is similar to a stack or queue, except that the item removed is chosen uniformly at random from items in the data structure. Create a generic data type RandomizedQueue
that implements the following API:
public class RandomizedQueue<Item> implements Iterable<Item> { public RandomizedQueue() // construct an empty randomized queue public boolean isEmpty() // is the randomized queue empty? public int size() // return the number of items on the randomized queue public void enqueue(Item item) // add the item public Item dequeue() // remove and return a random item public Item sample() // return a random item (but do not remove it) public Iterator<Item> iterator() // return an independent iterator over items in random order public static void main(String[] args) // unit testing (optional) }
方案
Deque
類需要同時維護指向頭部和尾部的兩個節點,並同為在單向鏈表的基礎上,在內部類Node上添加previous屬性用於指向上一個節點。
實現代碼
import java.util.Iterator;
public class Deque<Item> implements Iterable<Item> {
private Node first = null;
private Node last = null;
private int capacity = 0;
private class Node {
Item item;
Node next;
Node previous;
}
/**
* Construct an empty deque
*/
public Deque() {}
/**
* Check whether the deque is empty.
*
* @return true when the deque is empty, false otherwise.
*/
public boolean isEmpty() {return capacity == 0;}
/**
* Return the number of items on the deque.
*
* @return the number of items on the deque.
*/
public int size() {return capacity;}
/**
* Add the item to the front
*
* @throws java.util.NoSuchElementException when {@code item} is empty.
*/
public void addFirst(Item item)
{
if (item == null) throw new IllegalArgumentException();
Node oldFirst = first;
first = new Node();
first.item = item;
if (capacity == 0)
{
first.next = null;
first.previous = null;
last = first;
}
else {
first.next = oldFirst;
oldFirst.previous = first;
}
++capacity;
}
/**
* Add the item to the end
*
* @throws java.util.NoSuchElementException when {@code item} is empty.
*/
public void addLast(Item item)
{
if (item == null) throw new IllegalArgumentException();
Node oldLast = last;
last = new Node();
last.item = item;
if (capacity == 0)
{
last.previous = null;
last.next = null;
first = last;
}
else {
oldLast.next = last;
last.previous = oldLast;
}
++capacity;
}
/**
* Remove and return the item from the front
*
* @throws java.util.NoSuchElementException when {@code Deque} is empty.
*/
public Item removeFirst()
{
if (isEmpty()) throw new java.util.NoSuchElementException();
Item item = first.item;
first = first.next;
if (capacity == 1)
{
last = first;
}
else
{
first.previous = null;
}
capacity--;
return item;
}
/**
* Remove and return the item from the end
*
* @throws java.util.NoSuchElementException when {@code Deque} is empty.
*/
public Item removeLast()
{
if (isEmpty()) throw new java.util.NoSuchElementException();
Item item = last.item;
last = last.previous;
if (capacity == 1) {
first = last;
}
else
{
last.next = null;
}
capacity--;
return item;
}
/**
* Return an iterator over items in order from front to end.
*
* @return an iterator over items in order from front to end.
*
* @throws java.util.NoSuchElementException when called {@code next()} method if
* there is no next element.
* @throws UnsupportedOperationException when called {@code remove()} method.
*/
@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 void remove() {
throw new java.lang.UnsupportedOperationException();
}
@Override
public Item next()
{
if (!hasNext()) throw new java.util.NoSuchElementException();
Item item = current.item;
current = current.next;
return item;
}
}
}
RandomizedQueue
類因為要返回隨機元素,所以我們采用數組的方法。dequeue
方法隨機選擇一個元素,並將選擇節點指向最後節點,再讓最後節點指向null。
實現代碼
import edu.princeton.cs.algs4.StdRandom;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class RandomizedQueue<Item> implements Iterable<Item> {
private int amount = 0;
private Item[] queue;
/**
* Construct an empty randomized queue.
*/
public RandomizedQueue() {
queue = (Item[]) new Object[2];
}
/**
* Check whether the randomized queue is empty.
*
* @return true when the randomized queue is empty, false otherwise.
*/
public boolean isEmpty() {
return amount == 0;
}
/**
* Return the number of items on the randomized queue.
*
* @return the number of items on the randomized queue.
*/
public int size() {
return amount;
}
/**
* Add the item.
*/
public void enqueue(Item item) {
if (item == null) {
throw new java.lang.IllegalArgumentException();
}
if (amount == queue.length) {
resize(queue.length * 2);
}
queue[amount++] = item;
}
/**
* Remove and return a random item.
*
* @return a random item.
*/
public Item dequeue()
{
if (isEmpty()) {
throw new NoSuchElementException();
}
int randomIndex = StdRandom.uniform(amount);
Item item = queue[randomIndex];
queue[randomIndex] = queue[amount - 1];
queue[--amount] = null;
if (amount > 0 && amount == queue.length / 4) {
resize(queue.length / 2);
}
return item;
}
/**
* Return a random item (but do not remove it).
*
* @return a random item (but do not remove it).
*/
public Item sample() {
if (isEmpty()) {
throw new NoSuchElementException();
}
int randomIndex = StdRandom.uniform(amount);
return queue[randomIndex];
}
/**
* Return an independent iterator over items in random order.
*
* @return an independent iterator over items in random order.
*
* @throws NoSuchElementException when called {@code next()} method if
* there is no next element.
* @throws UnsupportedOperationException when called {@code remove()} method.
*/
public Iterator<Item> iterator() {
return new ListIterator();
}
private void resize(int capacity) {
Item[] temp = (Item[]) new Object[capacity];
System.arraycopy(queue, 0, temp, 0, queue.length);
queue = temp;
}
private class ListIterator implements Iterator<Item> {
private int[] randomIndexes = StdRandom.permutation(amount);
private int i = 0;
@Override
public boolean hasNext() {
return i < amount;
}
@Override
public Item next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return queue[randomIndexes[i++]];
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}
Permutation
類(參考某論壇的樓主):
我最開始的想法是如果知道了k和N,那麽讀每個數的時候就知道他進RandomizedQueue
的概率了;
但問題是input stream讀完之前,我不可能知道N
是多少;而input stream讀完之後,每個數都按自己應有的概率enqueue了;
所以解決問題的方法就是:N
是動態的;每讀一個數,N++
,然後對之前的概率分配進行調整;
以1,2,3,4,5
; k = 3
, N=5
為例:
在queue
長度達到k
之前,照單全收;所以queue
長度達到k
時裏面的元素是:1, 2 ,3
這時候從input stream裏面讀到4
,一共讀了4個數,N
變成了4
;
由於queue
的長度不能超過k
,超過1個也不行,所以我要先從1,2,3
裏面隨機洗出一張牌,再把4放進去;保證queue
的長度一直是k
;
由於dequeue()是隨機的,所以(1,2,4),(1,3,4),(2,3,4)
都是等概的;
問題是沒有(1,2,3)
; 所以有一定概率我不要4
從而得出(1,2,3)
。這個概率是多少呢?
也就是從N
個數裏面挑k
個數,沒有某一個數的概率 \(\frac{C_{N-1}^k}{C_N^k} = \frac{N-k}{N}\)
此時k = 3, N = 4
,(1,2,3)
的概率就是\(\frac{1}{4}\)了;
下一步,讀到5
,N = 5
; 又要面臨是否要把5
弄進去的抉擇,此時的\(\frac{N-k}{N}\)= \(\frac{2}{5}\); 也就是說不要5
的概率是\(\frac{2}{5}\).
由此保證了每個(a,b,c)
的permutation一定是等概的。
這個算法的思路在於:
每次決定要不要一個新數加進來的時候,我都可以保證:如果加進來,然後我可以把包含它的所有組合洗的等概;那麽我只要保證不加他進來的總概率(或加他進來的總概率)是對的即可;
這個不加進來的概率就是 \(\frac{N-k}{N}\)(加進來的概率是\(\frac{k}{N}\)),最後給出實現代碼。
實現代碼
import edu.princeton.cs.algs4.StdIn;
import edu.princeton.cs.algs4.StdOut;
import edu.princeton.cs.algs4.StdRandom;
public class Permutation
{
/**
* Creates one {@code RandomizedQueue} object
* of maximum size at most k.
*
* @param args the command-line arguments
*/
public static void main(String[] args)
{
int k = Integer.parseInt(args[0]);
RandomizedQueue<String> rq = new RandomizedQueue<String>();
double n = 1.0;
while (!StdIn.isEmpty())
{
String s = StdIn.readString();
if(k == 0) {
break;
}
else if (rq.size() < k)
{
rq.enqueue(s);
}
else if (StdRandom.uniform() > ((n-k)/n)) {
rq.dequeue();
rq.enqueue(s);
}
n++;
}
for (String s : rq) StdOut.println(s);
}
}
Coursera Algorithms第二周編程任務