ArrayQueue: An Array-Based Queue
Explore the ArrayQueue data structure that efficiently implements a FIFO queue using a circular array and modular arithmetic. Learn how to add and remove elements in constant time, manage resizing, and maintain proper queue order with practical Python examples.
We'll cover the following...
ArrayQueue operations
In this lesson, we present the ArrayQueue data structure, which implements a FIFO (first-in-first-out) queue; elements are removed (using the remove() operation) from the queue in the same order they are added (using the add(x) operation).
Notice that an ArrayStack is not a good choice for an implementation of a FIFO queue because we must choose one end of the list upon which to add elements and then remove elements from the other end. One of the two operations must work on the head of the list, which involves calling add(i, x) or remove(i) with a value of i = 0. This gives a running time proportional to .
To obtain an efficient array-based implementation of a queue, we first notice that the problem would be easy if we had an infinite array . We could maintain one index j that keeps track of the next element to remove and an integer n that counts the number of elements in the queue. The queue elements would always be stored in
Initially, both j and n would be set to . To add an element, we would place it in a[j + n] and increment n. To remove an element, we would remove it from a[j], increment j, and decrement n.
Of course, the problem with this solution is that it requires an infinite array. An ArrayQueue simulates this by using a finite array and modular arithmetic. This is the kind of arithmetic used when we are talking about the time of day. For example plus five hours gives . Formally, we say that
We read the latter part of this equation as “ is congruent to modulo ”. We can also treat mod as a binary operator, so that
More generally, for an integer and positive integer , is the unique integer such that for some integer . Less formally, the value is the remainder we get when we divide a by . In many programming languages, including Java, the mod operator is represented using the symbol
Note: is sometimes referred to as the brain-dead mod operator, since it does not correctly implement the mathematical mod operator when the first argument is negative.
Modular arithmetic is useful for simulating an infinite array, since i mod a.length always gives a value in the range . Using modular arithmetic we can store the queue elements at array locations
This treats the array a like a circular array in which array indices larger than a.length − 1 “wrap around” to the beginning of the array.
The only remaining thing to worry about is taking care that the number of elements in the ArrayQueue does not exceed the size of .
Visual demonstration
A sequence of add(x) and remove() operations on an ArrayQueue is illustrated below. A sequence of add(x) and remove(i) operations on an ArrayQueue. Arrows denote elements being copied. Operations that result in a call to _resize() are marked with an asterisk.
To implement add(x), we first check if a is full and, if necessary, call _resize() to increase the size of . Next, we store in a[(j + n) % a.length] and increment .
To implement remove(), we first store a[j] so that we can return it later. Next, we decrement and increment (modulo a.length) by setting j = (j + 1) mod a.length. Finally, we return the stored value of a[j]. If necessary, we may call _resize() to decrease the size of .
Finally, the _resize() operation is very similar to the _resize() operation of ArrayStack. It allocates a new array, , of size and copies
onto
and sets j = 0.
Summary
The following theorem summarizes the performance of the ArrayQueue data structure:
Theorem: An ArrayQueue implements the (FIFO) Queue interface. Ignoring the cost of calls to _resize(), an ArrayQueue supports the operations add(x) and remove() in time per operation. Furthermore, beginning with an empty ArrayQueue, any sequence of add(i, x) and remove(i) operations results in a total of time spent during all calls to _resize().
Explanation
Let’s start our explanation from the start of main() in the main.py file.
- Line 37: Here, two integer variables,
mandn, are defined and initialized with the values10000and50, respectively.mrepresents the maximum number of integers that can be added to the queue, andnrepresents the maximum size of the queue. - Line 39: It creates a queue
qof typeArrayQueuethat stores typeIntegerobjects. - Line 41–47: It adds integers to the queue one at a time, from
0up to9999. After each addition, it checks whether the queue size is greater than the maximum allowed sizen. If it is, the oldest element is removed from the queue, and an assertion checks if the removed element is equal toi - n. This check verifies that the queue has maintained the correct order of elements, and the assert statement will throw an exception if this is not the case. - Line 49–51: It removes the elements that are in range of
q.size()while iterating theforloop.