Sie sind auf Seite 1von 32

Stacks and Queues

Contents

Introduction o Stacks o Queues Implementing Stacks o Array Implementation Test Yourself #1 Test Yourself #2 Test Yourself #3 o Linked-list Implementation Test Yourself #4 Test Yourself #5 Implementing Queues o Array Implementation o Linked-list Implementation Comparison of Array and Linked-List Implementations Applications of Stacks and Queues Test Yourself #6 Answers to Self-Study Questions

Introduction
Both stacks and queues are like lists (ordered collections of items), but with more restricted operations. They can both be implemented either using an array or using a linked list to hold the actual items.

Stacks
The conceptual picture of a stack is something like this:
_______ / values in _________ \ / \ \/ / \/ | ----- | values out | | | ----- | | | | ----- | | | | | ----------

Think of a stack of newspapers, or trays in a cafeteria. The only item that can be taken out (or even seen) is the most recently added item; a stack is a Last-In-First-Out (LIFO) data structure. Here are the stack operations: OPERATION Stack() boolean empty() int size() void push(Object ob) Object pop() Object peek() DESCRIPTION (constructor) create an empty stack return true iff the stack is empty return the number of items in the stack add ob to the top of the stack remove and return the item from the top of the stack (error if the stack is empty) return the item that is on the top of the stack, but do not remove it (error if the stack is empty)

Queues
The conceptual picture of a queue is something like this:
values in ----> -----------------items in the queue ----> values out -----------------^ ^ | | this is the rear of this is the front of the queue the queue

Think of people standing in line. A queue is a First-In-First-Out (FIFO) data structure. Items can only be added at the rear of the queue, and the only item that can be removed is the one at the front of the queue. Here are the queue operations: OPERATION Queue() boolean empty() int size() void enqueue(Object ob) Object dequeue() DESCRIPTION (constructor) create an empty queue return true iff the queue is empty return the number of items in the queue add ob to the rear of the queue remove and return the item from the front of the queue (error if the queue is empty)

Implementing Stacks

The stack ADT is very similar to the list ADT; therefore, their implementations are also quite similar.

Array Implementation
Below is the definition of the Stack class, using an array to store the items in the stack; note that we include a static final variable INITSIZE, to be used by the Stack constructor as the initial size of the array (the same thing was done for the List class).
public class Stack { // *** fields *** private static final int INITSIZE = 10; // initial array size private Object[] items; // the items in the stack private int numItems; // the number of items in the stack //*** methods *** // constructor public Stack() { ... } // add items public void push(Object ob) { ... } // remove items public Object pop() throws EmptyStackException { ... } // other public public public } methods Object peek() throws EmptyStackException { ... } int size() { ... } boolean empty() { ... }

TEST YOURSELF #1 Write the Stack constructor. solution

The push method is like the version of the List add method that adds an object to the end of the list (because items are always pushed onto the top of the stack). Note that it is up to us as the designers of the Stack class to decide which end of the array corresponds to the top of the stack. We could choose always to add items at the beginning of the array, or always to add items at the end of the array. However, it is clearly not a good idea to add items at the beginning of the array since that requires moving all existing items; i.e., that choice would make push be O(N) (where N is the number of items in the stack). If we add items at the end of the array, then push is O(1), except when the array is full. Here are before and after pictures, illustrating the effects of a call to push:

And here's the code for the push method:


public void push(Object ob) { if (items.length == numItems) expandArray(); items[numItems] = ob; numItems++; }

The pop method needs to remove the top-of-stack item, and return it, as illustrated below.

Note that, in the picture, the value "bbb" is still in items[2]; however, that value is no longer in the stack because numItems is 2 (which means that items[1] is the last item in the stack).

TEST YOURSELF #2 Complete the pop method, using the following header
public Object pop() throws EmptyStackException { }

solution

The peek method is very similar to the pop method, except that it only returns the top-ofstack value without changing the stack. The size method simply returns the value of numItems, and the empty method returns true iff numItems is zero.

TEST YOURSELF #3 Fill in the following table, using Big-O notation to give the worst and average-case times for each of the stack methods for a stack of size N. OPERATION WORST-CASE TIME AVERAGE-CASE TIME

constructor empty size push pop peek solution

Linked-list Implementation
To implement a stack using a linked list, we must first define the Listnode class. The Listnode definition is the same one we used for the linked-list implementation of the List class. The signatures of the methods of the Stack class are independent of whether the stack is implemented using an array or using a linked list; only the type of the items field needs to change:
private Listnode items; the stack // pointer to the linked list of items in

As discussed above, an important property of stacks is that items are only pushed and popped at one end (the top of the stack). If we implement a stack using a linked list, we can choose which end of the list corresponds to the top of the stack. It is easiest and most efficient to add and remove items at the front of a linked list, therefore, we will choose the front of the list as the top of the stack (i.e., the items field will be a pointer to the node that contains the top-ofstack item). Below is a picture of a stack represented using a linked list; in this case, items have been pushed in alphabetical order, so "cc" is at the top of the stack:

Notice that, in the picture, the top of stack is to the left (at the front of the list), while for the array implementation, the top of stack was to the right (at the end of the array). Let's consider how to write the pop method. It will need to perform the following steps: 1. Check whether the stack is empty; if so, throw an EmptyStackException. 2. Remove the first node from the list by setting items = items.next. 3. Decrement numItems.

4. Return the value that was in the first node in the list. Note that by the time we get to the last step (returning the top-of-stack value), the first node has already been removed from the list, so we need to save its value in order to return it (we'll call that step 2(a)). Here's the code, and an illustration of what happens when pop is called for a stack containing "cc", "bb", "aa" (with "cc" at the top).
public Object pop() throws EmptyStackException { if (empty()) throw new EmptyStackException(); Object tmp = items.getData(); // step 2(a) items = items.getNext(); // step 2(b) numItems--; // step 3 return tmp; // step 4 } // step 1

Now let's consider the push method. Here are before and after pictures, illustrating the effect of a call to push when the stack is implemented using a linked list:

The steps that need to be performed are: 1. Create a new node whose data field contains the object to be pushed and whose next field contains a pointer to the first node in the list (or null if the list is empty). Note that the value for the next field of the new node is the value in the Stack's items field. 2. Change items to point to the new node. 3. Increment numItems.

TEST YOURSELF #4 Complete the push method, using the following header.
public void push(Object ob) { }

solution

The remaining methods (the constructor, peek, size, and empty) are quite straightforward. You should be able to implement them without any major problems.

TEST YOURSELF #5 Fill in the following table, using Big-O notation to give the worst-case times for each of the stack methods for a stack of size N, assuming a linked-list implementation. Look back at the table you filled in for the array implementation. How do the times compare? What are the advantages and disadvantages of using an array vs using a linked list to implement the Stack class?

OPERATION WORST-CASE TIME constructor empty size push pop peek solution

Implementing Queues
The main difference between a stack and a queue is that a stack is only accessed from the top, while a queue is accessed from both ends (from the rear for adding items, and from the front for removing items). This makes both the array and the linked-list implementation of a queue more complicated than the corresponding stack implementations.

Array Implementation
Let's first consider a Queue implementation that is very similar to our (array-based) List implementation. Here's the class definition:
public class Queue { // *** fields *** private static final int INITSIZE = 10; // initial array size private Object[] items; // the items in the queue private int numItems; // the number of items in the queue //*** methods *** // constructor public Queue() { ... } // add items public void enqueue(Object ob) { ... } // remove items public Object dequeue() throws EmptyQueueException { ... } // other methods public int size() { ... } public boolean empty() { ... } }

We could implement enqueue by adding the new item at the end of the array and implement dequeue by saving the first item in the array, moving all other items one place to the left, and returning the saved value. The problem with this approach is that, although the enqueue operation is efficient, the dequeue operation is not -- it requires time proportional to the number of items in the queue.

To make both enqueue and dequeue efficient, we need the following insight: There is no reason to force the front of the queue always to be in items[0], we can let it "move up" as items are dequeued. To do this, we need to keep track of the indexes of the items at the front and rear of the queue (so we need to add two new fields to the queue class, frontIndex and rearIndex, both of type int). To illustrate this idea, here is a picture of a queue after some enqueue and dequeue operations have been performed:

Now think about what should happen to this queue if we enqueue two more items: "dd" and "ee". Clearly "dd" should be stored in items[6]. Then what? We could increase the size of the array and put "ee" in items[7], but that would lead to wasted space -- we would never reuse items[0], items[1], or items[2]. In general, the items in the queue would keep "sliding" to the right in the array, causing more and more wasted space at the beginning of the array. A better approach is to let the rear index "wrap around" (in this case, from 6 to 0) as long as there is empty space in the front of the array. Similarly, if after enqueuing "dd" and "ee" we dequeue four items (so that only "ee" is left in the queue), the front index will have to wrap around from 6 to 0. Here's a picture of what happens when we enqueue "dd" and "ee":

Conceptually, the array is a circular array. It may be easier to visualize it as a circle. For example, the array for the final queue shown above could be thought of as:

We still need to think about what should happen if the array is full; we'll consider that case in a minute. Here's the code for the enqueue method, with the "full array" case still to be filled in:
public void enqueue(Object ob) { // check for full array and expand if necessary if (items.length == numItems) { // code missing here } // use auxiliary method to increment rear index with wraparound rearIndex = incrementIndex(rearIndex); // insert new item at rear of queue items[rearIndex] = ob; numItems++; } private int incrementIndex(int index) { if (index == items.length-1) return 0; else return index + 1; }

Note that instead of using incrementIndex we could use the mod operator, and write: rearIndex = (rearIndex + 1) % items.length. However, the mod operator is quite slow, and it is easy to get that expression wrong, so we will use the auxiliary method (with a check for the "wrap-around" case) instead. To see why we can't simply use expandArray when the array is full, consider the picture shown below.

After calling expandArray, the last item in the queue is still right before the first item -- there is still no place to put the new item (and there is a big gap in the middle of the queue, from items[7] to items[13]). The problem is that expandArray copies the values in the old array into the same positions in the new array. This does not work for the queue implementation; we need to move the "wrapped-around" values to come after the non-wrapped-around values in the new array. The steps that need to be carried out when the array is full are: 1. Allocate a new array of twice the size. 2. Copy the values in the range items[frontIndex] to items[items.length-1] into the new array (starting at position frontIndex in the new array). 3. Copy the values in the range items[0] to items[rearIndex] into the new array (starting at position items.length in the new array). Note: if the front of the queue was in items[0], then all of the values were copied by step 2, so this step is not needed. 4. Set items to point to the new array. 5. Fix the value of rearIndex. Here's an illustration:

And here's the final code for enqueue:


publc void enqueue(Object ob) { // check for full array and expand if necessary if (items.length == numItems) { Object[] tmp = new Object[items.length*2]; System.arraycopy(items, frontIndex, tmp, frontIndex, items.length-frontIndex); if (frontIndex != 0) { System.arraycopy(items, 0, tmp, items.length, frontIndex); } items = tmp; rearIndex = frontIndex + numItems - 1; } // use auxiliary method to increment rear index with wraparound rearIndex = incrementIndex(rearIndex); // insert new item at rear of queue

items[rearIndex] = ob; numItems++; }

The dequeue method will also use method incrementIndex to add one to frontIndex (with wrap-around) before returning the value that was at the front of the queue. The other queue methods (empty and size) are the same as those methods for the Stack class - they just use the value of the numItems field.

Linked-list Implementation
The first decision in planning the linked-list implementation of the Queue class is which end of the list will correspond to the front of the queue. Recall that items need to be added to the rear of the queue, and removed from the front of the queue. Therefore, we should make our choice based on whether it is easier to add/remove a node from the front/end of a linked list. If we keep pointers to both the first and last nodes of the list, we can add a node at either end in constant time. However, while we can remove the first node in the list in constant time, removing the last node requires first locating the previous node, which takes time proportional to the length of the list. Therefore, we should choose to make the end of the list be the rear of the queue, and the front of the list be the front of the queue. The class definition is the same as for the array implementation, except for the fields:
// *** fields *** private Listnode qFront; // // private Listnode qRear; // // private int numItems; // pointer to the front of the queue (the first node in the list) pointer to the rear of the queue (the last node in the list) the number of items in the queue

Here's a picture of a Queue with three items, aa, bb, cc, with aa at the front of the queue:

You should be able to write all of the queue methods, using the code you wrote for the linked-list implementation of the List class as a guide.

Comparison of Array and Linked-List Implementations

The advantages and disadvantages of the two implementations are essentially the same as the advantages and disadvantages in the case of the List class:

In the linked-list implementation, one pointer must be stored for every item in the stack/queue, while the array stores only the items themselves. On the other hand, the space used for a linked list is always proportional to the number of items in the list. This is not necessarily true for the array implementation as described: if a lot of items are added to a stack/queue and then removed, the size of the array can be arbitrarily greater than the number of items in the stack/queue. However, we could fix this problem by modifying the pop/dequeue operations to shrink the array when it becomes too empty. For the array implementation, the worst-case times for the push and enqueue methods are O(N), for a stack/queue with N items. This is because when the arary is full, a new array must be allocated and the values must be copied. For the linked-list implementation, push and enqueue are always O(1).

Applications of Stacks and Queues


Stacks are used to manage methods at runtime (when a method is called, its parameters and local variables are pushed onto a stack; when the method returns, the values are popped from the stack). Many parsing algorithms (used by compilers to determine whether a program is syntactically correct) involve the use of stacks. Stacks can be used to evaluate arithmetic expressions (e.g., by a simple calculator program), and they are also useful for some operations on graphs, a data structure we will learn about later in the semester. Queues are useful for many simulations, and are also used for some operations on graphs and trees.

TEST YOURSELF #6 Complete method reverseQ, whose header is given below. Method reverseQ should use a Stack to reverse the order of the items in its Queue parameter.
public static void reverseQ(Queue q) { // precondition: q contains x1 x2 ... xN (with x1 at the front) // postcondition: q contains xN ... x2 X1 (with xN at the front) }

solution

Answers to Self-Study Questions


Test Yourself #1
public Stack() { items = new Object[INITSIZE]; numItems = 0;

Test Yourself #2
public Object pop() throws EmptyStackException { if (numItems == 0) throw new EmptyStackException(); else { numItems--; return items[numItems]; } }

Test Yourself #3 OPERATION WORST-CASE TIME AVERAGE-CASE TIME constructor O(1) O(1) empty O(1) O(1) size O(1) O(1) push O(N) O(1) pop O(1) O(1) peek O(1) O(1)

Test Yourself #4
public void push(Object ob) { items = new Listnode(ob, items); numItems++; }

Test Yourself #5 OPERATION WORST-CASE TIME constructor O(1) empty O(1) size O(1) push O(1) pop O(1) peek O(1)

Test Yourself #6
public static void reverseQ(Queue q) { // precondition: q contains x1 x2 ... xN (with x1 at the front) // postcondition: q contains xN ... x2 X1 (with xN at the front) Stack s = new Stack(); while (!q.empty()) s.push(q.dequeue()); while (!s.empty()) q.enqueue(s.pop()); }

Application of linked lists Stacks and Queues , Polynomial handling


Inserting an element in a sorted linked list.
Let the data be sorted and put in a singly linked linear list which is being pointed by address head . Let the new data to be entered be d. Use malloc get a node with address pNew. Suppose we want to write a code to enter data d into the node and insert it into its proper place in the list. typedef struct node { int data; struct node *next; }; struct node* pNew = (struct node*) (malloc(sizeof(struct node))); pNew -> data = d; pNew ->next = NULL; pCur = head ; /* check if data is smaller than smallest item on the list*/ if (pNew -> data < pCur -> data ) { pNew ->next = pCur ; head = pNew; }

/* now examine the remaining list */ p = pCur -> next ; while(p!=NULL ||p->data < pNew->data ) { pCur = pCur -> next ; p = p -> next ; } pNew -> next = pCur -> next; pCur -> next = pNew ;

Implementation of stacks and queues using linked lists.


Stacks and Queues are easier to implement using linked lists. There is no need to set a limit on the size of the stack. Let us develop push, pop and enqueue and dequeue functions for nodes containing integer data. typedef struct node { int data; struct node *next; }; The Push function places the data in a new node, attaches that node to the front of stacktop. void push(struct node**stacktop,int d ) { struct node* pNew = (struct node*) (malloc(sizeof(struct node))); pNew-> data = d ; pNew->next = *stacktop; *stacktop = pNew ; }

Pop Function returns the data from top element, and frees that element from the stack.
int pop(struct node* *stacktop)

{ struct node* temp; if(*stacktop== NULL) { printf(\nstack empty\n); } else { temp = *stacktop; d = temp->data; *stacktop = temp->next ; free (temp); } return d; } For Queue operations, we can maintain two pointers qfront and qback as we had done for the case of array implementation of queues. For the Enqueue operation, the data is first loaded on a new node. If the queue is empty, then after insertion of the first node, both qfront and qback are made to point to this node, otherwise, the new node is simply appended and qback updated. void enqueue(struct node**qfront,int d , struct node**qback) { struct node* pNew = (struct node*) (malloc(sizeof(struct node))); pNew-> data = d ; pNew->next = NULL; if (*qfront ==NULL && *qback == NULL) { *qfront = pNew;

*qback = pNew; } else { *qback->next = pNew; *qback = pNew; } } In Dequeue function, first of all check if at all there is any element . If there is none, we would have *qfront as NULL, and so report queue to be empty, otherwise return the data element, update the *qfront pointer and free the node. Special care has to be taken if it was the only node in the queue. int dequeue(struct node**qfront, struct node**qback) { struct node* temp; if (*qfront== NULL) printf(\nqueue is empty\n); else { temp = *qfront; d = temp->data; *qfront = *qfront->next; if (*qfront == NULL) *qback = NULL; free (temp); } return d; }

Representing a polynomial using a linked

list
A polynomial can be represented in an array or in a linked list by simply storing the coefficient and exponent of each term. However, for any polynomial operation , such as addition or multiplication of polynomials , you will find that the linked list representation is more easier to deal with. First of all note that in a polynomial all the terms may not be present, especially if it is going to be a very high order polynomial. Consider 5 x12 + 2 x9 + 4x7 + 6x5 + x2 + 12 x Now this 12th order polynomial does not have all the 13 terms (including the constant term). It would be very easy to represent the polynomial using a linked list structure, where each node can hold information pertaining to a single term of the polynomial. Each node will need to store the variable x, the exponent and the coefficient for each term. It often does not matter whether the polynomial is in x or y. This information may not be very crucial for the intended operations on the polynomial. Thus we need to define a node structure to hold two integers , viz. exp and coff Compare this representation with storing the same polynomial using an array structure. In the array we have to have keep a slot for each exponent of x, thus if we have a polynomial of order 50 but containing just 6 terms, then a large number of entries will be zero in the array. You will also see that it would be also easy to manipulate a

pair of polynomials if they are represented using linked lists.

Addition of two polynomials


Consider addition of the following polynomials 5 x12 + 2 x9 + 4x7 + 6x6 + x3 7 x8 + 2 x7 + 8x6 + 6x4 + 2x2 + 3 x + 40 The resulting polynomial is going to be 5 x12 + 2 x9 + 7 x8 + 6 x7 + 14x6 + 6x4 +x3 2x2 + 3 x + 40 Now notice how the addition was carried out. Let us say the result of addition is going to be stored in a third list. We started with the highest power in any polynomial. If there was no item having same exponent , we simply appended the term to the new list, and continued with the process. Wherever we found that the exponents were matching, we simply added the coefficients and then stored the term in the new list. If one list gets exhausted earlier and the other list still contains some lower order terms, then simply append the remaining terms to the new list. Now we are in a position to write our algorithm for adding two polynomials. Let phead1 , phead2 and phead3 represent the pointers of the three lists under consideration. Let each node contain two integers exp and coff . Let us assume that the two linked lists already contain relevant data about the two polynomials. Also assume that we have got a function append to insert a new node at the end of the given list. p1 = phead1; p2 = phead2; Let us call malloc to create a new node p3 to build the

third list p3 = phead3; /* now traverse the lists till one list gets exhausted */ while ((p1 != NULL) || (p2 != NULL)) { / * if the exponent of p1 is higher than that of p2 then the next term in final list is going to be the node of p1* / while (p1 ->exp > p2 -> exp ) { p3 -> exp = p1 -> exp; p3 -> coff = p1 -> coff ; append (p3, phead3); /* now move to the next term in list 1*/ p1 = p1 -> next; } / * if p2 exponent turns out to be higher then make p3 same as p2 and append to final list * / while (p1 ->exp < p2 -> exp ) { p3 -> exp = p2 -> exp; p3 -> coff = p2 -> coff ; append (p3, phead3); p2 = p2 -> next; } /* now consider the possibility that both exponents are same , then we must add the coefficients to get the term for the final list */ while (p1 ->exp = p2 -> exp ) { p3-> exp = p1-> exp; p3->coff = p1->coff + p2-> coff ; append (p3, phead3) ; p1 = p1->next ; p2 = p2->next ;

} } /* now consider the possibility that list2 gets exhausted , and there are terms remaining only in list1. So all those terms have to be appended to end of list3. However, you do not have to do it term by term, as p1 is already pointing to remaining terms, so simply append the pointer p1 to phead3 */ if ( p1 != NULL) append (p1, phead3) ; else append (p2, phead3); Now, you can implement the algorithm in C, and maybe make it Stacks and queues.
In this section, we introduce two closely-related data types for manipulating arbitrarily large collections of objects: the stack and the queue. Each is defined by two basic operations: insert a new item, and remove an item. When we insert an item, our intent is clear. But when we remove an item, which one do we choose? The rule used for a queue is to always remove the item that has been in the collection the most amount of time. This policy is known as first-in-first-out or FIFO. The rule used for a stack is to always remove the item that has been in the collection the least amount of time. This policy is known as last-in first-out or LIFO.

Pushdown stacks.
A pushdown stack (or just a stack) is a collection that is based on the last-in-firstout (LIFO) policy. When you click a hyperlink, your browser displays the new page (and inserts it onto a stack). You can keep clicking on hyperlinks to visit new pages. You can always revisit the previous page by clicking the back button (remove it from a stack). The last-in-first-out policy offered by a pushdown stack provides just the behavior that you expect.

By tradition, we name the stack insert method push() and the stack remove operation pop(). We also include a method to test whether the stack is empty. The following API summarizes the operations:

The asterisk indicates that we will be considering more than one implementation of this API.

Array implementation.
Representing stacks with arrays is a natural idea. The first problem that you might encounter is implementing the constructor ArrayStackOfStrings(). An instance variable a[] with an array of strings to hold the stack items is clearly needed, but how big should it be? For the moment, We will finesse this problem by having the client provide an argument for the constructor that gives the maximum stack size. We keep the items in reverse order of their arrival. This policy allows us to add and remove items at the end without moving any of the other items in the stack.

We could hardly hope for a simpler implementation of ArrayStackOfStrings.java: all of the methods are one-liners! The instance variables are an array a[] that hold the items in the stack and an integer N that counts the number of items in the stack. To remove an item, we decrement N and then return a[N]; to insert a new item, we set a[N] equal to the new item and then increment N. These operations preserve the following properties: the items in the array are in their insertion order the stack is empty when the value of N is 0 the top of the stack (if it is nonempty) is at a[N-1]

The primary characteristic of this implementation is that the push and pop operations take constant time. The drawback of this implementation is that it requires the client to estimate the maximum size of the stack ahead of time and always uses space proportional to that maximum, which may be unreasonable in some situations.

Linked lists.
For classes such as stacks that implement collections of objects, an important objective is to ensure that the amount of space used is always proportional to the number of items in the collection. Now we consider the use of a fundamental data structure known as a linked list that can provide implementations of collections (and, in particular, stacks) that achieves this important objective.

A linked list is a recursive data structure defined as follows: a linked list is either empty (null) or a reference to a node having a reference to a linked list. The node in this definition is an abstract entity that might hold any kind of data in addition to the node reference that characterizes its role in building linked lists. With object-oriented programming, implementing linked lists is not difficult. We start with a simple example of a class for the node abstraction:
class Node { String item; Node

next; } A Node has two instance variables: a String and a Node. The String is a placeholder in this example for any data that we might want to structure with a linked list (we can use any set of instance variables); the instance variable of type Node characterizes the linked nature of the data structure. Now, from the recursive definition, we can represent a linked list by a variable of type Node just by ensuring that its value is either null or a reference to a Node whose next field is a reference to a linked list.

We create an object of type Node by invoking its (no-argument) constructor. This creates a reference to a Node object whose instance variables are both initialized to the value null. For example, to build a linked list that contains the items "to", "be", and "or", we create a Node for each item:

Node Node(); Node Node(); Node Node();

first

new

second

new

third

new

and set the item field in each of the nodes to the desired item value: first.item "to"; second.item = =

"be"; third.item "or"; and set the next fields to build the linked list: first.next second; second.next third; third.next null; When tracing code that uses linked lists and other linked structures, we use a visual representation of the changes where we draw a rectangle to represent each object we put the values of instance variables within the rectangle we depict references as arrows that point to the referenced object This visual representation captures the essential characteristic of linked lists and allows us to focus on the links. Insert. Suppose that you want to insert a new node into a linked list. The easiest place to do so is at the beginning of the list. For example, to insert the string "not" at the beginning of a given linked list whose first node is first, we save first in oldfirst, assign to first a new Node, assign its item field to "not" and its next field to oldfirst. = = =

Remove. Suppose that you want to remove the first node from a list. This operation is even easier: simply assign to first the value first.next. Normally,

you would retrieve the value of the item (by assigning it to some String variable) before doing this assignment, because once you change the value of first, you may not have any access to the node to which it was referring. Typically, the node object becomes an orphan, and the memory it occupies is eventually reclaimed by the Java memory management system.

These two operations take constant time (independent of the length of the list).

Implementing stacks with linked lists.


Program LinkedStackOfStrings.java uses a linked list to implement a stack of strings. The implementation is based on a nested class Node like the one we have been using. Java allows us to define and use other classes within class implementations in this natural way. The class is private because clients do not need to know any of the details of the linked lists.

List traversal.
One of the most common operations we perform on collections is to iterate through the items in the collection. For example, we might wish to implement the toString() method to facilitate debugging our stack code with traces. For ArrayStackOfStrings, this implementation is familiar: public String toString() { String s = ""; for (int i = 0; i < N; i++)

s += a[i] + " "; return s; } As usual, this solution is intended for use only when N is small - it takes quadratic time because string concatenation takes linear time. Our focus now is just on the process of examining every item. There is a corresponding idiom for visiting the items in a linked list: We initialize a loop index variable x that references the the first Node of the linked list. Then, we find the value of the item associated with x by accessing x.item, and then update x to refer to the next Node in the linked list assigning to it the value of x.next, repeating this process until x is null (which indicates that we have reached the end of the linked list). This process is known as traversing the list, and is succinctly expressed in this implementation of toString() for LinkedStackOfStrings: public toString() { String

more efficient.

Das könnte Ihnen auch gefallen