1. 程式人生 > >Coursera Algorithms第二周編程任務

Coursera Algorithms第二周編程任務

form ole operation println 論壇 1.0 組合 aci class

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 front
public 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}\)了;
下一步,讀到5N = 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第二周編程任務