实现一个泛型的双端队列和随机化队列,用数组和链表的方式实现基本数据结构,主要介绍了泛型和迭代器。java
Dequeue. 实现一个双端队列,它是栈和队列的升级版,支持首尾两端的插入和删除。Deque的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) // insert the item at the front public void addLast(Item item) // insert the item at the end public Item removeFirst() // delete and return the item at the front public Item removeLast() // delete and return the item at 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 }
deque的操做实现必须是常数时间,使用空间是当前元素个数,迭代器的实现next()和hasNext()操做也是常数时间和常数空间,具体的要求以下。数据结构
因此Deque采用Linked-list实现方式。dom
结点定义成:spa
private class Node { private Item item; private Node prev; private Node next; }
双端队列使用Linked-List实现,那么使用一个哨兵sentinel来辅助实现Deque,这在Programming Tricks and Common Pitfalls中有讲到,每一个Assignment中checklist的内容对于理解和实现有很大帮助,那么咱们能够这样设计:设计
使用哨兵指向deque的队首元素,而队尾元素指向哨兵,如今咱们来分析每一个方法实现。指针
Deque(): 初始Deque没有元素,元素个数为0,那么哨兵的prev和next也都指向自身。code
addFirst(): 队首添加元素时候,就是简单的链表操做在sentinel和第一个Node之间插入一个新的Node,并记得把元素个数加1.orm
addLast(): 同理blog
removeFirst(): 首先要判断,deque是否为空,可否支持删除操做,能够的话,删除首元素,更新第二个元素和sentinel之间的关系,而后元素个数减1
removeLast(): 同理
isEmpty()和size(): 用一直维护元素个数变量来进行操做
迭代器Iterator的操做也十分简单了, 咱们只须要获取sentinel哨兵,而后遍历就能够实现。hasNext()直到下一个元素又走回了sentinel哨兵,那么咱们就已经遍历完了全部元素。
Randomized queue. 随机化队列也和栈和队列十分类似,区别在于它的remove操做是随机删除队列中的一个元素。API以下:
public class RandomizedQueue<Item> implements Iterable<Item> { public RandomizedQueue() // construct an empty randomized queue public boolean isEmpty() // is the queue empty? public int size() // return the number of items on the queue public void enqueue(Item item) // add the item public Item dequeue() // delete and return a random item public Item sample() // return (but do not delete) a random item public Iterator<Item> iterator() // return an independent iterator over items in random order public static void main(String[] args) // unit testing }
时间和空间复杂度的要求看上面那个表格,咱们使用Array数组的实现方式。注意一下java不能建立泛型数组,用cast强制转换来实现,但会致使warning
Item[] a = (Item[]) new Object[1];
Randomized queue和通常的queue基本操做都是同样,因为随机出队,那入队元素也不必定按照正常的队列来使用,咱们只须要把队列的元素维护在连续起始开始的一段就能够了。
那么咱们只须要使用一个tail尾指针,当插入元素的时候,把元素直接插入在队尾:
public void enqueue(Item item) { if (item == null) throw new java.lang.NullPointerException("can't add a null item"); if (N == q.length) resize(2*q.length); q[tail++] = item; N++; }
当出队的时候,随即一个1-元素个数之间的下标,删除这个元素,那么这个位置空出来怎么办?把队尾元素填充上去,而后队尾置为空。
在入队和出队的时候,要进行resize操做维护空间大小,resize看PPT上面有讲解。
public Item dequeue() { if (isEmpty()) throw new java.util.NoSuchElementException("underflow"); int index = StdRandom.uniform(N); Item item = q[index]; //because random, just simply put q[tail-1] to q[index] q[index] = q[--tail]; q[tail] = null; N--; if (N > 0 && N == q.length/4) resize(q.length/2); return item; }
迭代器的操做,不能需改原来的元素,须要从新申请空间,随机化的出队思考起来也很简单,咱们使用Elementary Sort中介绍的Shuffle的方法来对元素从新组合一下
for (int i = 0; i < N; i++) { int r = StdRandom.uniform(i+1); Item tmp = array[i]; array[i] = array[r]; array[r] = tmp; }
Subset client. 从n个string中随机输出k个,n中的全部元素每一个最多只能输出一次。
要求使用Deque或者RandomizedQueue空间复杂度最多为N,时间复杂度为N线性。
使用RandomizedQueue实现的话,初始把全部N个元素都加入到队列中,而后dequeue()其中n-k个,而后输出剩下的k个元素就是结果了。
使用Deque随机的把N个元素加入队首和队尾,接下来随机的在队首和队尾删除n-k元素,而后输出剩下的k个元素就能够获得结果了。