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 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);
    }
}

猜你喜欢

转载自www.cnblogs.com/revc/p/9226716.html
今日推荐