The queue abstract data type ADT supports the following two fundamental methods: enqueuee: Insert element e at the rear of the queue.. 5.2.2 A Simple Array-Based Queue Implementation W
Trang 1and then fill the array back up again by popping the elements off of the stack In
Code Fragment 5.8, we give a Java implementation of this algorithm Incidentally, this method also illustrates how we can use generic types in a simple application that uses a generic stack In particular, when the elements are popped off the stack
in this example, they are automatically returned as elements of the E type; hence, they can be immediately returned to the input array We show an example use of this method in Code Fragment 5.9
reverses the elements in an array of type E objects,
using a stack declared using the Stack<E> interface
method using two arrays
Trang 25.1.5 Matching Parentheses and HTML Tags
In this subsection, we explore two related applications of stacks, the first of which
is for matching parentheses and grouping symbols in arithmetic expressions
Arithmetic expressions can contain various pairs of grouping symbols, such as
• Parentheses: "(" and ")"
• Braces: "{" and "}"
• Brackets: "[" and "]"
• Floor function symbols: " " and " "
• Ceiling function symbols: " " and " ,"
and each opening symbol must match with its corresponding closing symbol For
example, a left bracket, "[," must match with a corresponding right bracket, "]," as
in the following expression:
[(5 + x) − (y + z)]
Trang 3The following examples further illustrate this concept:
We leave the precise definition of matching of grouping symbols to Exercise R-5.5
An Algorithm for Parentheses Matching
An important problem in processing arithmetic expressions is to make sure their
grouping symbols match up correctly We can use a stack S to perform the
matching of grouping symbols in an arithmetic expression with a single right scan The algorithm tests that left and right symbols match up and also that the left and right symbols are both of the same type
left-to-Suppose we are given a sequence X = x0x1x2…xn−1, where each xi is a token that
can be a grouping symbol, a variable name, an arithmetic operator, or a number
The basic idea behind checking that the grouping symbols in S match correctly, is
to process the tokens in X in order Each time we encounter an opening symbol,
we push that symbol onto S, and each time we encounter a closing symbol, we pop the top symbol from the stack S (assuming S is not empty) and we check that
these two symbols are of the same type If the stack is empty after we have
processed the whole sequence, then the symbols in X match Assuming that the
push and pop operations are implemented to run in constant time, this algorithm
runs in O(n), that is linear, time We give a pseudo-code description of this
algorithm in Code Fragment 5.10
Code Fragment 5.10: Algorithm for matching
grouping symbols in an arithmetic expression
Trang 4Matching Tags in an HTML Document
Another application in which matching is important is in the validation of HTML
documents HTML is the standard format for hyperlinked documents on the
Internet In an HTML document, portions of text are delimited by HTML tags A
simple opening HTML tag has the form "<name>" and the corresponding closing
tag has the form "</name>." Commonly used HTML tags include
• body: document body
• h1: section header
• center: center justify
• p: paragraph
• ol: numbered (ordered) list
• li: list item
Ideally, an HTML document should have matching tags, although most browsers
tolerate a certain number of mismatching tags
We show a sample HTML document and a possible rendering in Figure 5.3
Trang 5Figure 5.3: Illustrating HTML tags (a) An HTML
document; (b) its rendering
Fortunately, more or less the same algorithm as in Code Fragment 5.10 can be
used to match the tags in an HTML document In Code Fragments 5.11 and 5.12,
we give a Java program for matching tags in an HTML document read from
standard input For simplicity, we assume that all tags are the simple opening or
closing tags defined above and that no tags are formed incorrectly
Code Fragment 5.11: A complete Java program for
testing if an HTML document has fully matching tags
(Continues in Code Fragment 5.12 )
Trang 6Code Fragment 5.12: Java program for testing for
matching tags in an HTML document (Continued from
5.11.) Method isHTMLMatched uses a stack to store
the names of the opening tags seen so far, similar to
how the stack was used in Code Fragment 5.10
Method parseHTML uses a Scanner s to extract the
tags from the HTML document, using the pattern
"<[^>]*>," which denotes a string that starts with '<',
followed by zero or more characters that are not '>',
followed by a '>'
Trang 8Another fundamental data structure is the queue It is a close "cousin" of the stack, as
a queue is a collection of objects that are inserted and removed according to the
first-in first-out (FIFO) prfirst-inciple That is, elements can be first-inserted at any time, but only
the element that has been in the queue the longest can be removed at any time
We usually say that elements enter a queue at the rear and are removed from the
front The metaphor for this terminology is a line of people waiting to get on an
amusement park ride People waiting for such a ride enter at the rear of the line and get on the ride from the front of the line
5.2.1 The Queue Abstract Data Type
Formally, the queue abstract data type defines a collection that keeps objects in a sequence, where element access and deletion are restricted to the first element in the
sequence, which is called the front of the queue, and element insertion is restricted
to the end of the sequence, which is called the rear of the queue This restriction
enforces the rule that items are inserted and deleted in a queue according to the first-in first-out (FIFO) principle
The queue abstract data type (ADT) supports the following two fundamental
methods:
enqueue(e): Insert element e at the rear of the queue
dequeue(): Remove and return from the queue the object at the front;
an error occurs if the queue is empty
Additionally, similar to the case with the Stack ADT, the queue ADT includes the following supporting methods:
size(): Return the number of objects in the queue
isEmpty(): Return a Boolean value that indicates whether the queue is empty
front(): Return, but do not remove, the front object in the queue; an error occurs if the queue is empty
Example 5.4: The following table shows a series of queue operations and their effects on an initially empty queue Q of integer objects For simplicity, we use integers instead of integer objects as arguments of the operations
Operation
Output
front ← Q ← rear
Trang 9enqueue(5)
- (5) enqueue(3)
- (5, 3) dequeue( )
5
(3) enqueue(7)
- (3, 7) dequeue( )
3
(7) front( )
7
(7) dequeue( )
7
( ) dequeue( )
"error"
Trang 10true
( ) enqueue(9)
- (9) enqueue(7)
- (9, 7) size()
2
(9, 7) enqueue(3)
- (9, 7, 3) enqueue(5)
- (9, 7, 3, 5) dequeue( )
or to the box office of a theater
Trang 11A Queue Interface in Java
A Java interface for the queue ADT is given in Code Fragment 5.13 This generic interface specifies that objects of arbitrary object types can be inserted into the queue Thus, we don't have to use explicit casting when removing elements Note that the size and isEmpty methods have the same meaning as their
counterparts in the Stack ADT These two methods, as well as the front method,
are known as accessor methods, for they return a value and do not change the
contents of the data structure
Code Fragment 5.13: Interface Queue documented
with comments in Javadoc style
Trang 125.2.2 A Simple Array-Based Queue Implementation
We present a simple realization of a queue by means of an array, Q, of fixed
capacity, storing its elements Since the main rule with the queue ADT is that we insert and delete objects according to the FIFO principle, we must decide how we are going to keep track of the front and rear of the queue
Trang 13One possibility is to adapt the approach we used for the stack implementation,
letting Q[0] be the front of the queue and then letting the queue grow from there
This is not an efficient solution, however, for it requires that we move all the
elements forward one array cell each time we perform a dequeue operation Such an
implementation would therefore take O(n) time to perform the dequeue method, where n is the current number of objects in the queue If we want to achieve
constant time for each queue method, we need a different approach
Using an Array in a Circular Way
To avoid moving objects once they are placed in Q, we define two variables f and
r, which have the following meanings:
• f is an index to the cell of Q storing the first element of the queue (which
is the next candidate to be removed by a dequeue operation), unless the queue is
empty (in which case f = r)
• r is an index to the next available array cell in Q
Initially, we assign f = r = 0, which indicates that the queue is empty Now, when
we remove an element from the front of the queue, we increment f to index the next cell Likewise, when we add an element, we store it in cell Q[r] and
increment r to index the next available cell in Q This scheme allows us to
implement methods front, enqueue, and dequeue in constant time, that is,
O(1) time However, there is still a problem with this approach
Consider, for example, what happens if we repeatedly enqueue and dequeue a
single element N different times We would have f = r = N If we were then to try
to insert the element just one more time, we would get an array-out-of-bounds
error (since the N valid locations in Q are from Q[0] to Q[N − 1]), even though
there is plenty of room in the queue in this case To avoid this problem and be
able to utilize all of the array Q, we let the f and r indices "wrap around" the end
of Q That is, we now view Q as a "circular array" that goes from Q[0] to Q[N − 1] and then immediately back to Q[0] again (See Figure 5.4.)
Figure 5.4: Using array Q in a circular fashion: (a)
the "normal" configuration with f ≤ r; (b) the "wrapped around" configuration with r < f The cells storing
queue elements are highlighted
Trang 14Using the Modulo Operator to Implement a Circular
Array
Implementing this circular view of Q is actually pretty easy Each time we
increment f or r, we compute this increment as "(f + 1) mod N" or "(r + 1) mod
N," respectively
Recall that operator "mod" is the modulo operator, which is computed by taking
the remainder after an integral division For example, 14 divided by 4 is 3 with
remainder 2, so 14 mod 4 = 2 Specifically, given integers x and y such that x ≥ 0
and y > 0, we have x mod y = x − x/y y That is, if r = x mod y, then there is a
nonnegative integer q, such that x = qy + r Java uses "%" to denote the modulo
operator By using the modulo operator, we can view Q as a circular array and
implement each queue method in a constant amount of time (that is, O(1) time)
We describe how to use this approach to implement a queue in Code Fragment
5.14
Code Fragment 5.14: Implementation of a queue
using a circular array The implementation uses the
modulo operator to "wrap" indices around the end of
the array and it also includes two instance variables, f
and r, which index the front of the queue and first
empty cell after the rear of the queue respectively
Trang 15The implementation above contains an important detail, which might be missed at
first Consider the situation that occurs if we enqueue N objects into Q without dequeuing any of them We would have f = r, which is the same condition that
occurs when the queue is empty Hence, we would not be able to tell the
difference between a full queue and an empty one in this case Fortunately, this is not a big problem, and a number of ways for dealing with it exist
The solution we describe here is to insist that Q can never hold more than N − 1
objects This simple rule for handling a full queue takes care of the final problem with our implementation, and leads to the pseudo-coded descriptions of the queue
Trang 16which gives the correct result both in the "normal" configuration (when f ≤ r) and
in the "wrapped around" configuration (when r < f) The Java implementation of a
queue by means of an array is similar to that of a stack, and is left as an exercise (P-5.4)
Table 5.2 shows the running times of methods in a realization of a queue by an array As with our array-based stack implementation, each of the queue methods
in the array realization executes a constant number of statements involving
arithmetic operations, comparisons, and assignments Thus, each method in this
implementation runs in O(1) time
Table 5.2: Performance of a queue realized by an
array The space usage is O(N), where N is the size of
the array, determined at the time the queue is created Note that the space usage is independent from the
number n < N of elements that are actually in the
Trang 17less queue capacity than this, but if we have a good capacity estimate, then the array-based implementation is quite efficient
5.2.3 Implementing a Queue with a Generic Linked List
We can efficiently implement the queue ADT using a generic singly linked list For efficiency reasons, we choose the front of the queue to be at the head of the list, and the rear of the queue to be at the tail of the list In this way, we remove from the head and insert at the tail (Why would it be bad to insert at the head and remove at the tail?) Note that we need to maintain references to both the head and tail nodes of the list Rather than go into every detail of this implementation, we simply give a Java implementation for the fundamental queue methods in Code Fragment 5.15
Code Fragment 5.15: Methods enqueue and
dequeue in the implementation of the queue ADT by means of a singly linked list, using nodes from class Node of Code Fragment 5.6
Trang 18Each of the methods of the singly linked list implementation of the queue ADT runs
in O(1) time We also avoid the need to specify a maximum size for the queue, as
was done in the array-based queue implementation, but this benefit comes at the
expense of increasing the amount of space used per element Still, the methods in
the singly linked list queue implementation are more complicated than we might
like, for we must take extra care in how we deal with special cases where the queue
is empty before an enqueue or where the queue becomes empty after a dequeue
5.2.4 Round Robin Schedulers
A popular use of the queue data structure is to implement a round robin scheduler,
where we iterate through a collection of elements in a circular fashion and "service" each element by performing a given action on it Such a schedule is used, for
example, to fairly allocate a resource that must be shared by a collection of clients
Trang 19For instance, we can use a round robin scheduler to allocate a slice of CPU time to
various applications running concurrently on a computer
We can implement a round robin scheduler using a queue, Q, by repeatedly
performing the following steps (see Figure 5.5):
1 e ← Q.dequeue()
2 Service element e
3 Q.enqueue(e)
Figure 5.5: The three iterative steps for using a
queue to implement a round robin scheduler
The Josephus Problem
In the children's game "hot potato," a group of n children sit in a circle passing an
object, called the "potato," around the circle The potato begins with a starting
child in the circle, and the children continue passing the potato until a leader rings
a bell, at which point the child holding the potato must leave the game after
handing the potato to the next child in the circle After the selected child leaves,
the other children close up the circle This process is then continued until there is
only one child remaining, who is declared the winner If the leader always uses
the strategy of ringing the bell after the potato has been passed k times, for some
fixed value k, then determining the winner for a given list of children is known as
the Josephus problem
Solving the Josephus Problem Using a Queue
Trang 20potato is equivalent to dequeuing an element and immediately enqueuing it again
After this process has been performed k times, we remove the front element by
dequeuing it from the queue and discarding it We show a complete Java program
for solving the Josephus problem using this approach in Code Fragment 5.16,
which describes a solution that runs in O(nk) time (We can solve this problem
faster using techniques beyond the scope of this book.)
Code Fragment 5.16: A complete Java program for
solving the Josephus problem using a queue Class
NodeQueue is shown in Code Fragment 5.15
Trang 215.3 Double-Ended Queues
Consider now a queue-like data structure that supports insertion and deletion at both
the front and the rear of the queue Such an extension of a queue is called a
double-ended queue, or deque, which is usually pronounced "deck" to avoid confusion with
the dequeue method of the regular queue ADT, which is pronounced like the
abbreviation "D.Q."
5.3.1 The Deque Abstract Data Type
The deque abstract data type is richer than both the stack and the queue ADTs The fundamental methods of the deque ADT are as follows:
addFirst(e): Insert a new element e at the beginning of the deque
addLast(e): Insert a new element e at the end of the deque
removeFirst(): Remove and return the first element of the deque; an error occurs if the deque is empty
removeLast(): Remove and return the last element of the deque; an error occurs if the deque is empty
Additionally, the deque ADT may also include the following support methods: getFirst(): Return the first element of the deque; an error occurs if the deque is empty
getLast(): Return the last element of the deque; an error occurs if the deque is empty
size(): Return the number of elements of the deque
isEmpty(): Determine if the deque is empty
Example 5.5: The following table shows a series of operations and their effects
on an initially empty deque D of integer objects For simplicity, we use integers instead of integer objects as arguments of the operations
Operation
Output
Trang 22- (3)addFirst(5)
- (5,3) removeFirst()
5
(3) addLast(7)
- (3,7) removeFirst()
3
(7) removeLast()
7
() removeFirst()
"error"
() isEmpty()
true
()
5.3.2 Implementing a Deque
Trang 23Since the deque requires insertion and removal at both ends of a list, using a singly linked list to implement a deque would be inefficient We can use a doubly linked list, however, to implement a deque efficiently As discussed in Section 3.3,
inserting or removing elements at either end of a doubly linked list is
straightforward to do in O(1) time, if we use sentinel nodes for the header and
trailer
For an insertion of a new element e, we can have access to the node p before the place e should go and the node q after the place e should go To insert a new
element between the two nodes p and q (either or both of which could be sentinels),
we create a new node t, have t's prev and next links respectively refer to p and q, and then have p's next link refer to t, and have q's prev link refer to t
Likewise, to remove an element stored at a node t, we can access the nodes p and q
on either side of t (and these nodes must exist, since we are using sentinels) To remove node t between nodes p and q, we simply have p and q point to each other instead of t We need not change any of the fields in t, for now t can be reclaimed
by the garbage collector, since no one is pointing to t
Table 5.3 shows the running times of methods for a deque implemented with a
doubly linked list Note that every method runs in O(1) time
doubly linked list
Trang 24Thus, a doubly linked list can be used to implement each method of the deque ADT
in constant time We leave the details of implementing the deque ADT efficiently in Java as an exercise (P-5.7)
Incidentally, all of the methods of the deque ADT, as described above, are included
in the java.util.LinkedList<E> class So, if we need to use a deque and would rather not implement one from scratch, we can simply use the built-in
java.util.LinkedList<E> class
In any case, we show a Deque interface in Code Fragment 5.17 and an
implementation of this interface in Code Fragment 5.18
Code Fragment 5.17: Interface Deque documented
with comments in Javadoc style ( Section 1.9.3 ) Note also the use of the generic parameterized type, E, which implies that a deque can contain elements of any
specified class
Trang 26Code Fragment 5.18: Class NodeDeque
implementing the Deque interface, except that we have not shown the class DLNode, which is a generic doubly linked list node, nor have we shown methods getLast, addLast , and removeFirst
Trang 28Suppose an initially empty stack S has performed a total of 25 push operations,
12 top operations, and 10 pop operations, 3 of which generated
StackEmptyExceptions, which were caught and ignored What is the current
size of S?
R-5.2
If we implemented the stack S from the previous problem with an array, as
described in this chapter, then what is the current value of the top instance variable?
Describe the output for the following sequence of queue operations:
enqueue(5), enqueue(3), dequeue(), enqueue(2),
enqueue(8), dequeue(), dequeue(), enqueue(9),
enqueue(1), dequeue(), enqueue(7), enqueue(6),
dequeue(), dequeue(), enqueue(4), dequeue(),
dequeue()
R-5.7
Trang 29Suppose an initially-empty queue Q has performed a total of 32 enqueue
operations, 10 front operations, and 15 dequeue operations, 5 of which generated QueueEmptyExceptions, which were caught and ignored What is the
current size of Q?
R-5.8
If the queue of the previous problem was implemented with an array of capacity
N = 30, as described in the chapter, and it never generated a
FullQueueException, what would be the current values of f and r?
R-5.9
Describe the output for the following sequence of deque ADT operations: addFirst(3), addLast(8), addLast(9), addFirst(5), removeFirst(), remove-Last(), first(), addLast(7), removeFirst(), last(), removeLast()
R-5.10
Suppose you have a deque D containing the numbers (1,2,3,4,5,6,7,8), in this order Suppose further that you have an initially empty queue Q Give a pseudo- code description of a method that uses only D and Q (and no other variables or objects) and results in D storing the elements (1,2,3,5,4,6,7,8), in this order
C-5.2
Give a pseudo-code description for an array-based implementation of the
double-ended queue ADT What is the running time for each operation?
Trang 30Suppose Alice has picked three distinct integers and placed them into a stack S
in random order Write a short, straightline piece of pseudo-code (with no loops
or recursion) that uses only one comparison and only one variable x, yet
guarantees with probability 2/3 that at the end of this code the variable x will
store the largest of Alice's three integers Argue why your method is correct
to n of these cells in our algorithm, which itself runs in O(n) time (not counting the time to initialize A) Show how to use an array-based stack S storing (i, j, k) integer triples to allow us to use the array A without initializing it and still implement our algorithm in O(n) time, even though the initial values in the cells
of A might be total garbage
C-5.7
Describe a nonrecursive algorithm for enumerating all permutations of the
numbers {1,2,…,n}
C-5.8
Postfix notation is an unambiguous way of writing an arithmetic expression
without parentheses It is defined so that if "(exp1 )op(exp2)" is a normal fully
parenthesized expression whose operation is op, then the postfix version of this
is "pexp1 pexp2 op", where pexp 1 is the postfix version of exp1 and pexp2 is the
postfix version of exp
an
2 The postfix version of a single number or variable is just that number or variable So, for example, the postfix version of "((5 + 2) * (8 − 3))/4" is "5 2 + 8 3 − * 4 /" Describe a nonrecursive way of evaluatingexpression in postfix notation
C-5.9
Suppose you have two nonempty stacks S and T and a deque D Describe how
to use D so that S stores all the elements of T below all of its original elements,
with both sets of elements still in their original order
Trang 31holding 3 Describe a sequence of transfer operations that starts from the
initial configuration and results in B holding 4 elements at the end
C-5.11
Alice has two queues, S and T, which can store integers Bob gives Alice 50 odd integers and 50 even integers and insists that she stores all 100 integers in S and
T They then play a game where Bob picks S or T at random and then applies
the round-robin scheduler, described in the chapter, to the chosen queue a random number of times If the number left out of the queue at the end of this game is odd, Bob wins Otherwise, Alice wins How can Alice allocate integers
to queues to optimize her chances of winning? What is her chance of winning?
C-5.12
Suppose Bob has four cows that he wants to take across a bridge, but only one yoke, which can hold up to two cows, side by side, tied to the yoke The yoke is too heavy for him to carry across the bridge, but he can tie (and untie) cows to it
in no time at all Of his four cows, Mazie can cross the bridge in 2 minutes, Daisy can cross it in 4 minutes, Crazy can cross it in 10 minutes, and Lazy can cross it in 20 minutes Of course, when two cows are tied to the yoke, they must
go at the speed of the slower cow Describe how Bob can get all his cows across the bridge in 34 minutes
Projects
P-5.1
Implement the stack ADT with a doubly linked list
P-5.2
Trang 32Implement a program that can input an expression in postfix notation (see
Exercise C-5.8) and output its value
of this ADT using a single array whose capacity is set at some value N that is
assumed to always be larger than the sizes of the red and blue stacks combined
When a share of common stock of some company is sold, the capital gain (or,
sometimes, loss) is the difference between the share's selling price and the price originally paid to buy it This rule is easy to understand for a single share, but if
we sell multiple shares of stock bought over a long period of time, then we must identify the shares actually being sold A standard accounting principle for identifying which shares of a stock were sold in such a case is to use a FIFO protocol—the shares sold are the ones that have been held the longest (indeed, this is the default method built into several personal finance software packages) For example, suppose we buy 100 shares at $20 each on day 1, 20 shares at $24
on day 2, 200 shares at $36 on day 3, and then sell 150 shares on day 4 at $30 each Then applying the FIFO protocol means that of the 150 shares sold, 100 were bought on day 1, 20 were bought on day 2, and 30 were bought on day 3 The capital gain in this case would therefore be 100 · 10 + 20 · 6 + 30 · (−6), or
Trang 33$940 Write a program that takes as input a sequence of transactions of the form
"buy x; share(s) at $y each" or "sell x share(s) at $y
each," assuming that the transactions occur on consecutive days and the
values x and y are integers Given this input sequence, the output should be the
total capital gain (or loss) for the entire sequence, using the FIFO protocol to
identify shares
Chapter Notes
We were introduced to the approach of defining data structures first in terms of their ADTs and then in terms of concrete implementations by the classic books by Aho,
Hopcroft, and Ullman [4 5], which incidentally is where we first saw aproblem
similar to Exercise C-5.6 Exercises C-5.10, C-5.11, and C-5.12 are similar to
interview questions said to be from a well-known software company For further
study of abstract data types, see Liskov and Guttag [69], Cardelli and Wegner [20], or Demurjian [28]
Chapter 6 Lists and Iterators
Trang 36Suppose we have a collection S of n elements stored in a certain linear order, so that
we can refer to the elements in S as first, second, third, and so on Such a collection is
generically referred to as a list or sequence We can uniquely refer to each element e
in S using an integer in the range [0,n − 1] that is equal to the number of elements of S
that precede e in S The index of an element e in S is the number of elements that are
before e in S Hence, the first element in S has index 0 and the last element has index
n − 1 Also, if an element of S has index i, its previous element (if it exists) has index
i − 1, and its next element (if it exists) has index i + 1 This concept of index is related
Trang 37to that of the rank of an element in a list, which is usually defined to be one more
than its index; so the first element is at rank 1, the second is at rank 2, and so on
A sequence that supports access to its elements by their indices is called an array list (or vector, using an older term) Since our index definition is more consistent with the
way arrays are indexed in Java and other programming languages (such as C and C++), we will be referring to the place where an element is stored in an array list as
its "index," not its "rank" (although we may use r to denote this index, if the letter "i"
is being used as a for-loop counter)
This index concept is a simple yet powerful notion, since it can be used to specify where to insert a new element into a list or where to remove an old element
6.1.1 The Array List Abstract Data Type
As an ADT, an array list S has the following methods (besides the standard
size() and isEmpty() methods):
get(i): Return the element of S with index i; an error condition occurs if i < 0 or i > size() − 1
set(i, e): Replace with e and return the element at index i; an error condition occurs if i < 0 or i > size() − 1
add(i, e): Insert a new element e into S to have index i; an error condition occurs if i < 0 or i > size()
remove(i): Remove from S the element at index i; an error condition occurs if i < 0 or i > size() − 1
We do not insist that an array should be used to implement an array list, so that the
element at index 0 is stored at index 0 in the array, although that is one (very
natural) possibility The index definition offers us a way to refer to the "place" where an element is stored in a sequence without having to worry about the exact implementation of that sequence The index of an element may change whenever the sequence is updated, however, as we illustrate in the following example
Example 6.1: We show below some operations on an initially empty array list
S
Operation
Trang 38- (7)add(0,4)
- (4,7) get(1)
7 (4,7) add(2,2)
- (4,7,2) get(3)
"error"
(4,7,2) remove(1)
7 (4,2) add(1,5)
- (4,5,2) add(1,3)
- (4,3,5,2) add(4,9)
-
Trang 39(4,3,5,2,9) get(2)
5 (4,3,5,2,9) set(3,8)
2 (4,3,5,8,9)
6.1.2 The Adapter Pattern
Classes are often written to provide similar functionality to other classes The
adapter design pattern applies to any context where we want to modify an existing
class so that its methods match those of a related, but different, class or interface One general way for applying the adapter pattern is to define the new class in such a way that it contains an instance of the old class as a hidden field, and implement each method of the new class using methods of this hidden instance variable The result of applying the adapter pattern is that a new class that performs almost the same functions as a previous class, but in a more convenient way, has been created With respect to our discussion of the array list ADT, we note that this ADT is sufficient to define an adapter class for the deque ADT, as shown in Table 6.1 (See also Exercise C-6.8.)
array list
Deque Method
Realization with Array-List Methods
size(), isEmpty() size(), isEmpty() getFirst()
Trang 40get(size() −1)
addFirst(e) add(0,e) addLast(e) add(size(),e)
removeFirst() remove(0) removeLast() remove(size() − 1)
6.1.3 A Simple Array-Based Implementation
An obvious choice for implementing the array list ADT is to use an array A, where A[i] stores (a reference to) the element with index i We choose the size N of array
A sufficiently large, and we maintain the number of elements in an instance
variable, n < N
The details of this implementation of the array list ADT are simple To implement
the get(i) operation, for example, we just return A[i] Implementations of
methods add(i, e) and remove(i) are given in Code Fragment 6.1 An important (and time-consuming) part of this implementation involves the shifting of elements up or down to keep the occupied cells in the array contiguous These shifting operations are required to maintain our rule of always storing an element
whose list index is i at index i in the array A (See Figure 6.1 and also Exercise 6.12.)
remove(i) in the array implementation of the array
list ADT We denote, with n, the instance variable
storing the number of elements in the array list