1 Queues Outline and Required Reading: Queues ( 6.2) CSE 2011, Fall 2017 Instructor: N. Vlajic
Queue ADT 2 Queue linear data structure organized according to first-in/first-out (FIFO) principle queue is open at (both) ends - items cannot be added in the middle items enter queue at the rear, and are removed from the front queue items are removed in exactly the same order as they were added to the queue flow through pipe oldest element (first added, first removed) newest element (last added, last removed) front rear A A B A B C B C Examples print server, scheduler in an OS, scheduler in automated on-line systems, packet forwarding in networks
Queue ADT (cont.) 3 Example [ queues in real life ]
Queue ADT: Interface 4 Fundamental Methods public void enqueue(e element); (Transformers) /* insert an element at the rear of the queue */ dequeue (delete) enqueue (insert) public interface Queue<E> { public E dequeue(); /* remove the element at the front of the queue */ Supporting Methods public int size(); (Observers) /* return the # of objects in the queue */ public boolean isempty(); /* return true if the queue is empty */ public E first(); /* get the first/front element without removing it */ }
Queue ADT: Interface 5
Queue ADT: Interface 6 Possible Queue ADT Errors (1) underflow - trying to dequeue() or first() on an empty queue can occur regardless of the underlying data structure employed (2) overflow - trying to enqueue() to an already full queue can occur only if the underlying data structure has a fixed capacity
Java.util.Queue 7
Java.util.Queue (cont.) 8 Java.util.Queue - supports two styles for most operations, which vary in the way that they treat exceptional cases. one throws an exception if operation fails the other returns a special value if operation fails (null or false)
Java.util.Queue (cont.) 9 Subinterface BlockingQueue - used in the case of multi-threaded programming, where one thread produces objects, and the other thread consumes them The producing thread will keep producing new objects and insert them into the queue, until the queue reaches some upper bound on what it can contain. At that point, the producing thread is blocked and remains blocked until the consuming thread takes an object out of the queue If consuming thread tries to take an object out of an empty queue, the consuming thread is blocked until the producing thread puts an object into the queue
Queue ADT: Array-Based Implementation 10 Trivial Array-Based Implementation dequeue let Q[0] be the front of the queue (index of the queue front = 0, at all times) oldest element (index_1) 0 1 2 front rear 0 1 2 front rear move 0 1 2 front rear 0 1 2 front rear enqueue Q[ ] Q[ ] Q[ ] Q[ ] enqueue 1, dequeue 2 elements If we do not shift elements forward, we could soon run out of space! Drawback too costly! on every dequeue, all remaining elements must be moved forward (O(N) run time)
Queue ADT: Array-Based Implementation (cont.) 11 Wrap Around shifting of elements at dequeue avoided Implementation 1 2 circular array 0 3 5 4 f = front index of the cell of Q storing the front sz = size current number of elements in Q when the rear index reaches the end of array start using available locations at the front rules for incrementing r and f in circular array: enqueue: add at (f+sz) mod N AND sz++ dequeue: remove from f AND f = (f + 1) mod N enqueue front 0 1 f 0 1 0 1 rear enqueue 2 3 4 5 sz front rear 2 3 4 5 f front rear 2 3 4 5 f enqueue?! where to add?!
Queue ADT: Array-Based Implementation (cont.) 12 public class CircularArrayQueue<E> implements Queue<E> { public static final int CAPACITY = 1000; private int f, sz; private E[] data; public CircularArrayQueue(int capacity) { f = 0; sz = 0; data = (E[]) new Object[capacity]; } public CircularArrayQueue() { this(capacity); } pubic int size() { return sz; } public boolean isempty() { return (sz == 0); } cont.
Queue ADT: Array-Based Implementation (cont.) 13 Avoided in LL implementation. pubic void enqueue(e e) throws IllegalStateException { if ( sz == data.length) throw new IllegalStateException( Queue is full. ); int avail = (f+sz)%data.length; data[avail] = e sz++; } public E first() { if (isempty()) return null; return data[f]; } front rear 0 1 2 3 f public E dequeue() { if (isempty()) return null; E answer = data[f]; data[f] = null; f = (f+1)%data.length; sz--; return answer; } } enqueue 4 5
Queue ADT: Array-Based Implementation (cont.) 14 Run Time Good! all methods run in constant time (no loops or recursions) Method size isempty front enqueue dequeue Time Space Usage Poor! O(N), where N array size, n current # of elements in the queue, N>n General Note problems arise if attempting to enqueue N or more objects
Queue ADT: Singly Linked List Implementation 15 Instance Variables private Node<E> head; /* reference to the head of the SLL */ private Node<E> tail; /* reference to the tail of the SLL */ private int size; /* current number of elements in the queue */ Choose: front of Q head of SLL, rear of Q tail of SLL. Why?! dequeue front of Q = head cost of removing dequeue front of Q = tail cost of removing O(n) tail = rear of Q cost of adding enqueue head = rear of Q cost of adding enqueue
Queue ADT: Singly Linked List Implementation (cont.) 16 public void enqueue(e element) { Node<N> node = new Node<E>(element, null); if (size == 0) { head = node; } else { tail.setnext(node); } tail = node; size ++; } head tail enqueue head tail head tail head tail
Queue ADT: Singly Linked List Implementation (cont.) 17 public E dequeue() { if (size == 0) return null; E temp = head.getelement(); head = head.getnext(); size --; if (size == 0) tail = null; return temp; } dequeue head head tail tail
Queue ADT: Performance of SLL Implementation 18 Run Time Good! all methods run in constant time Method size isempty front enqueue dequeue Time Space Usage Good! O(n), n current # of elements in the stack General Note no problems with size/overflow
Queue ADT: Array vs. Linked List Implementation 19 Properties of Queues ordered access additions/removals at a cursor frequent resizing Array Implementation + + - Linked List Implementation + + + implementation complexity / cost + - In Linked List implementation: 1) each element must contains reference next 2) for every en/de-queued element, method setnext()/getnext() must be called
Queue ADT: Array vs. Linked List Implementation 20 Example [ selection of optimal data structure] A software engineer employed by a telecommunication company needs to design an application for use in customer service telephone call centre. He is currently deciding on what data structure to use in order to store the callers on hold. The requirements are: he should be able to add a caller to the structure; obtain the number of callers on hold; remove caller that has been waiting the longest when a representative is ready. (In (a) and (b) circle the correct answer; in (c) fill in the empty fields.) (a) His choice of liner data structure Stack ADT Queue ADT should be: (b) His choice of implementation (for the Dynamic Array Linked List ADT selected in (a)) should be:
Queue ADT: Array vs. Linked List Implementation 21 (c) The following methods of the ADT selected in (a) should be used to implement the key operations of the call holder data structure: call holder operation add caller to structure count callers on hold method in ADT used to implement enqueue() size() running time remove next caller to serve dequeue()
Queue ADT: Questions 22 1. Assume we want to implement Queue using a SLL, which has only one reference (e.g.) to the head of SLL. What is the main disadvantage of this implementation? What would be the time complexity of the main queue operations, in this case? 2. Describe how to implement the queue ADT using two stacks. What is the running time of the enqueue() and dequeue() methods? 3. Describe in pseudo-code a linear-time algorithm for reversing a queue Q.
Queue ADT: Answers (cont.) 23 1. The disadvantage of this implementation is enqueue because we only have the pointer to the head. So, in order to enqueue, we need to search for the end of the linked list. Hence T(enqueue()) = Θ(n). 2. On enqueue(): push the element onto stack 1 On dequeue(): pop each element from stack1 and push it onto stack2, until the last element is reached. Temporarily store this last element (temp). Pop everything off of stack2 and push them back onto stack 1. Return temp element. Enqueue takes time, and dequeue takes O(n) time. 3. create new Stack s while not Q.isEmpty() do S.push(Q.dequeue()) while not S.isEmpty() do Q.enqueue(S.pop())