If we implement an ordered list as an array, inserting a new element may require shifting a large fraction of the old elements, in order to create a "hole" for the new element in the right place. In this discussion we're going to look at a scheme to prevent or defer some of this work, by prepositioning holes in the array so that new elements can fill them.
We are going to define a new generic class HoleyList<T>
,
which will implement L&C's interface OrderedListADT<T>
(though we won't deal with all the required methods here). An object of type
HoleyList<T>
will store elements of type T
in an array of type T [ ]
, but the array will contain both
elements and null
entries called "empty slots". The empty slots
may occur between real entries. The class also has a field int size
for the number of real elements being stored.
Given an array of length n, full of T
elements, we construct a
HoleyList
by creating an array of length 2n, then placing the
elements in the even-numbered positions -- the first in entry 0, the
second in entry 2, and so on, so that the last real element goes in entry 2n -
2 and entry 2n - 1 (like all other odd-numbered entries) is null
.
Removing an element from a HoleyList
is simple -- we replace
its entry with null
and don't move any other elements or
otherwise change the array at all. The add
operation is more
interesting. To add a new element y
, we first find the largest
element x
that is less than y
in the natural order.
(There is a special case if there is no such element, but it is quite similar.)
We want to put y
in the slot immediately after x
:
x
is empty, we just put
y
there and we are done.
x
and that slot each forward by
one entry, making an empty slot right after x
into which we put
y
. Note that the array is not circular -- if we reach the end
without finding an empty slot, we give up.
x
at all, we resize the
array. This means we make a new array twice as big as the number of elements
we are storing, and copy all the real entries of the old array, in
order, into the even-numbered entries of the new one. Thus the new array is
exactly half full -- it is not necessarily twice the size of the old
array. We then insert y
into the new empty slot immediately
after x
.
Question 1: Trace the following code given the description of the
HoleyList
class above. Remember that these entries are
String
objects, not Dog
objects, and that the natural
order on String
objects is just ordinary dictionary order.
Describe the entire array after each operation.
String [ ] someDogs = {"Ace", "Biscuit", "Cardie"};
HoleyList<String> holey = new HoleyList<String>(someDogs);
holey.add ("Balto");
holey.add ("Buck");
holey.add ("Bella");
holey.remove ("Ace");
holey.add ("Duncan");
holey.add ("Abigail");
holey.add ("Aaron");
Question 2: Write a constructor public HoleyList
(T [ ] input)
for HoleyList
. You may (or may
not) find it convenient to write a method copyToEvens (T [ ]
oldArray,
T [ ] newArray)
that you can also use later. For the
constructor, you may assume that the input array is in the natural
order and contains no empty slots.
Question 3: Write the add
and
remove
methods for HoleyList
. You may find
it convenient to write a find
method first. Remember
that remove
takes a T
parameter. It throws
an
EmptyCollectionException
if the list is empty, and an
ElementNotFoundException
if the parameter is not equal to
any element in the list.
Question 4: In our array implementations of stacks, queues,
and deques, the add
operation "took O(1) time, except for
resizing." But the add
operation of an
ArrayList
, in either L&C's version or the library
version, takes O(n) time in the worst case even without resizing. Is
the add
operation of our new class "O(1) in the worst
case, except for resizing"? Justify your answer.
Last modified 27 October 2011