1. Trang chủ
  2. » Công Nghệ Thông Tin

Data Structures and Algorithms in Java 4th phần 4 ppsx

92 495 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 92
Dung lượng 1,66 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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 1

and 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 2

5.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 3

The 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 4

Matching 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 5

Figure 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 6

Code 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 8

Another 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 9

enqueue(5)

- (5) enqueue(3)

- (5, 3) dequeue( )

5

(3) enqueue(7)

- (3, 7) dequeue( )

3

(7) front( )

7

(7) dequeue( )

7

( ) dequeue( )

"error"

Trang 10

true

( ) 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 11

A 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 12

5.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 13

One 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 14

Using 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 15

The 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 16

which 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 17

less 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 18

Each 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 19

For 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 20

potato 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 21

5.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 23

Since 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 24

Thus, 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 26

Code 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 28

Suppose 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 29

Suppose 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 30

Suppose 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 31

holding 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 32

Implement 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 36

Suppose 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 37

to 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 40

get(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

Ngày đăng: 14/08/2014, 01:21

TỪ KHÓA LIÊN QUAN