Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

AhmadLang / Java, How To Program, 2004

.pdf
Скачиваний:
626
Добавлен:
31.05.2015
Размер:
51.82 Mб
Скачать

12 2 6 4 10 8 37 89 68 45

Starting from the left, but beginning with the element after 10, compare each element with 37 until an element greater than 37 is foundthen swap 37 and that element. There are no more elements greater than 37, so when we compare 37 with itself, we know that 37 has been placed in its final location of the sorted array. Every value to the left of 37 is smaller than it, and every value to the right of 37 is larger than it.

Once the partition has been applied on the previous array, there are two unsorted subarrays. The subarray with values less than 37 contains 12, 2, 6, 4, 10 and 8. The subarray with values greater than 37 contains 89, 68 and 45. The sort continues recursively with both subarrays being partitioned in the same manner as the original array.

Based on the preceding discussion, write recursive method quickSortHelper to sort a one-dimensional integer array. The method should receive as arguments a starting index and an ending index on the original array being sorted.

[Page 817]

Chapter 17. Data Structures

Much that I bound, I could not free;

Much that I freed returned to me.

Lee Wilson Dodd

'Will you walk a little faster?' said a whiting to a snail,

'There's a porpoise close behind us, and he's treading on my tail.'

Lewis Carroll

There is always room at the top.

Daniel Webster

Push onkeep moving.

Thomas Morton

I'll turn over a new leaf.

Miguel de Cervantes

OBJECTIVES

In this chapter you will learn:

To form linked data structures using references, self-referential classes and recursion.

The type-wrapper classes that enable programs to process primitive data values as objects.

To use autoboxing to convert a primitive value to an object of the corresponding type-wrapper class.

To use auto-unboxing to convert an object of a type-wrapper class to a primitive value.

To create and manipulate dynamic data structures, such as linked lists, queues, stacks and binary trees.

Various important applications of linked data structures.

How to create reusable data structures with classes, inheritance and composition.

[Page 818]

Outline

17.1 Introduction

17.2 Type-Wrapper Classes for Primitive Types

17.3 Autoboxing and Auto-Unboxing

17.4 Self-Referential Classes

17.5 Dynamic Memory Allocation

17.6 Linked Lists

17.7 Stacks

17.8 Queues

17.9 Trees

17.10 Wrap-Up

Summary

Terminology

Self-Review Exercises

Answers to Self-Review Exercises Exercises

Special Section: Building Your Own Compiler

[Page 818 (continued)]

17.1. Introduction

In previous chapters, we have studied fixed-size data structures such as one-dimensional and multidimensional arrays. This chapter introduces dynamic data structures that grow and shrink at execution time. Linked lists are collections of data items "linked up in a chain"insertions and deletions can be made anywhere in a linked list. Stacks are important in compilers and operating systems; insertions and deletions are made only at one end of a stackits top. Queues represent waiting lines; insertions are made at the back (also referred to as the tail) of a queue and deletions are made from the front (also referred to as the head). Binary trees facilitate high-speed searching and sorting of data, eliminating duplicate data items efficiently, representing file-system directories, compiling expressions into machine language and many other interesting applications.

We will discuss each of these major types of data structures and implement programs that create and manipulate them. We use classes, inheritance and composition to create and package these data structures for reusability and maintainability. In Chapter 19, Collections, we discuss Java's predefined classes that implement the data structures discussed in this chapter.

The examples presented here are practical programs that can be used in advanced courses and in industrial applications. The exercises include a rich collection of useful applications.

This chapter's examples manipulate primitive values for simplicity. However, most of the data-structure implementations in this chapter store only Objects. J2SE 5.0 has added a new feature, called boxing, that allows primitive values to be converted to and from objects for use in cases like this. The objects that represent primitive values are instances of Java's so-called type-wrapper classes in package java.lang. We discuss these classes and boxing in the next two sections, so we can use them in this chapter's examples.

We encourage you to attempt the major project described in the special section entitled Building Your Own Compiler. You have been using a Java compiler to translate your Java programs to bytecodes so that you could execute these programs on your computer. In this project, you will actually build your own compiler. It will read a file of statements written in a simple, yet powerful high-level language similar to early versions of the popular language BASIC. Your compiler will translate these statements into a file of Simpletron Machine Language (SML) instructionsSML is the language you learned in the Chapter 7 special section, Building Your Own Computer. Your Simpletron Simulator program will then execute the SML program produced by your compiler! Implementing this project by using an objectoriented approach will give you a wonderful opportunity to exercise most of what you have learned in this book. The special section carefully walks you through the specifications of the high-level language and describes the algorithms you will need to convert each high-level language statement into machinelanguage instructions. If you enjoy being challenged, you might attempt the many enhancements to both the compiler and the Simpletron Simulator suggested in the exercises.

[Page 819]

[Page 819 (continued)]

17.2. Type-Wrapper Classes for Primitive Types

Each primitive type (listed in Appendix D, Primitive Types) has a corresponding type-wrapper class (in package java.lang). These classes are called Boolean, Byte, Character, Double, Float,

Integer, Long and Short. Each type-wrapper class enables you to manipulate primitive-type values as objects. Many of the data structures that we develop or reuse in Chapters 1719 manipulate and share Objects. These classes cannot manipulate variables of primitive types, but they can manipulate objects of the type-wrapper classes, because every class ultimately derives from Object.

Each of the numeric type-wrapper classesByte, Short, Integer, Long, Float and Doubleextends class Number. Also, the type-wrapper classes are final classes, so you cannot extend them.

Primitive types do not have methods, so the methods related to a primitive type are located in the corresponding type-wrapper class (e.g., method parseInt, which converts a String to an int value, is located in class Integer). If you need to manipulate a primitive value in your program, first refer to the documentation for the type-wrapper classesthe method you need might already be declared.

[Page 819 (continued)]

17.3. Autoboxing and Auto-Unboxing

In versions of Java prior to J2SE 5.0, if you wanted to insert a primitive value into a data structure that could store only Objects, you had to create a new object of the corresponding type-wrapper class, then insert this object in the collection. Similarly, if you wanted to retrieve an object of a type-wrapper class from a collection and manipulate its primitive value, you had to invoke a method on the object to obtain its corresponding primitive-type value. For example, suppose you want to add an int to an array that stores only references to Integer objects. Prior to J2SE 5.0, you would be required to "wrap" an int value in an Integer object before adding the integer to the array and to "unwrap" the int value from the Integer object to retrieve the value from the array, as in

Integer[] integerArray = new Integer[ 5 ]; // create integerArray

//assign Integer 10 to integerArray[ 0 ] integerArray[ 0 ] = new Integer( 10 );

//get int value of Integer

int value = integerArray[ 0 ].intValue();

Notice that the int primitive value 10 is used to initialize an Integer object. This achieves the desired result but requires extra code and is cumbersome. We then need to invoke method intValue of class Integer to obtain the int value in the Integer object.

[Page 820]

J2SE 5.0 simplifies converting between primitive-type values and type-wrapper objects, requiring no additional code on the part of the programmer. J2SE 5.0 introduces two new conversionsthe boxing conversion and the unboxing conversion. A boxing conversion converts a value of a primitive type to an object of the corresponding type-wrapper class. An unboxing conversion converts an object of a type-wrapper class to a value of the corresponding primitive type. J2SE 5.0 allows these conversions to be performed automatically (called autoboxing and auto-unboxing). For example, the previous statements can be rewritten as

Integer[] integerArray = new Integer[ 5 ]; integerArray[ 0 ] = 10; // assign Integer int value = integerArray[ 0 ]; // get int

// create integerArray 10 to integerArray[ 0 ] value of Integer

In this case, autoboxing occurs when assigning an int value (10) to integerArray[ 0 ], because integerArray stores references to Integer objects, not int primitive values. Auto-unboxing occurs when assigning integerArray[ 0 ] to int variable value, because variable value stores an int value, not a reference to an Integer object. Autoboxing and auto-unboxing also occur in control statementsthe condition of a control statement can evaluate to a primitive boolean type or a Boolean reference type. Many of this chapter's examples use these conversions to store primitive values in and retrieve them from data structures that store only references to Objects.

[Page 820 (continued)]

17.4. Self-Referential Classes

A self-referential class contains an instance variable that refers to another object of the same class type. For example, the declaration

class Node

 

 

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

 

private

int

data;

 

 

 

 

 

 

 

private Node nextNode; // reference to next linked node

public

Node(

int data

)

{

/*

constructor

body */ }

public

void

setData(

int data )

{

/*

method

body

*/

}

public

int getData()

 

{

/*

method

body

*/

}

public

void

setNext( Node next ) { /* method body

*/

}

public

Node

getNext()

 

{

/*

method

body

*/

}

} // end class

Node

 

 

 

 

 

 

 

declares class Node, which has two private instance variablesinteger data and Node reference nextNode. Field nextNode references a Node object, an object of the same class being declared herehence, the term "self-referential class." Field nextNode is a linkit "links" an object of type Node to another object of the same type. Type Node also has five methods: a constructor that receives an Integer to initialize data, a setData method to set the value of data, a getdata method to return the value of data, a setNext method to set the value of nextNode and a getNext method to return a reference to the next node.

Programs can link self-referential objects together to form such useful data structures as lists, queues, stacks and trees. Figure 17.1 illustrates two self-referential objects linked together to form a list. A backslashrepresenting a null referenceis placed in the link member of the second self-referential object to indicate that the link does not refer to another object. Note the backslash is illustrative; it does not correspond to the backslash character in Java. Normally, a null reference indicates the end of a data structure. There are other ways to represent the end of a data structure that are beyond the scope of this text.

[Page 821]

Figure 17.1. Self-referential-class objects linked together.

[View full size image]

[Page 821 (continued)]

17.5. Dynamic Memory Allocation

Creating and maintaining dynamic data structures requires dynamic memory allocationthe ability for a program to obtain more memory space at execution time to hold new nodes and to release space no longer needed. Remember that Java programs do not explicitly release dynamically allocated memory. Rather, Java performs automatic garbage collection of objects that are no longer referenced in a program.

The limit for dynamic memory allocation can be as large as the amount of available physical memory in the computer or the amount of available disk space in a virtual-memory system. Often, the limits are much smaller, because the computer's available memory must be shared among many applications.

The declaration and class-instance creation expression

Node nodeToAdd = new Node( 10 ); // 10 is nodeToAdd's data

allocates the memory to store a Node object and returns a reference to the object, which is assigned to nodeToAdd. If insufficient memory is available, the expression throws an OutOfMemoryError.

The following sections discuss lists, stacks, queues and trees that all use dynamic memory allocation and self-referential classes to create dynamic data structures.

[Page 821 (continued)]

17.6. Linked Lists

A linked list is a linear collection (i.e., a sequence) of self-referential-class objects, called nodes, connected by reference linkshence, the term "linked" list. Typically, a program accesses a linked list via a reference to the first node in the list. The program accesses each subsequent node via the link reference stored in the previous node. By convention, the link reference in the last node is set to null to mark the end of the list. Data is stored in a linked list dynamicallythe program creates each node as necessary. A node can contain data of any type, including references to objects of other classes. Stacks and queues are also linear data structures and, as we will see, are constrained versions of linked lists. Trees are non-linear data structures.

Lists of data can be stored in arrays, but linked lists provide several advantages. A linked list is appropriate when the number of data elements to be represented in the data structure is unpredictable. Linked lists are dynamic, so the length of a list can increase or decrease as necessary. The size of a "conventional" Java array, however, cannot be altered, because the array size is fixed at the time the program creates the array. "Conventional" arrays can become full. Linked lists become full only when the system has insufficient memory to satisfy dynamic storage allocation requests. Package java.util contains class LinkedList for implementing and manipulating linked lists that grow and shrink during program execution. We discuss class LinkedList in Chapter 19.

[Page 822]

Performance Tip 17.1

An array can be declared to contain more elements than the number of items expected, but this wastes memory. Linked lists provide better memory utilization in these situations. Linked lists allow the program to adapt to storage needs at runtime.

Performance Tip 17.2

Insertion into a linked list is fastonly two references have to be modified (after locating the insertion point). All existing node objects remain at their current locations in memory.

Linked lists can be maintained in sorted order simply by inserting each new element at the proper point in the list. (It does, of course, take time to locate the proper insertion point.) Existing list elements do not need to be moved.

Performance Tip 17.3

Insertion and deletion in a sorted array can be time consumingall the elements following the inserted or deleted element must be shifted appropriately.

Linked list nodes normally are not stored contiguously in memory. Rather, they are logically contiguous. Figure 17.2 illustrates a linked list with several nodes. This diagram presents a singly linked listeach node contains one reference to the next node in the list. Often, linked lists are implemented as doubly linked listseach node contains a reference to the next node in the list and a reference to the previous node in the list. Java's LinkedList class is a doubly linked list implementation.

Figure 17.2. Linked list graphical representation.

[View full size image]

Performance Tip 17.4

Normally, the elements of an array are contiguous in memory. This allows immediate access to any array element, because its address can be calculated directly as its offset from the beginning of the array. Linked lists do not afford such immediate access to their elementsan element can be accessed only by traversing the list from the front (or from the back in a doubly linked list).

The program of Fig. 17.3Fig. 17.5 uses an object of our List class to manipulate a list of miscellaneous objects. The program consists of four classesListNode (Fig. 17.3, lines 637), List (Fig. 17.3, lines 40147),

EmptyListException (Fig. 17.4) and ListTest (Fig. 17.5). The List, ListNode and EmptyListException classes are placed in package com.deitel.jhtp6.ch17, so they can be reused throughout this chapter. Encapsulated in each List object is a linked list of ListNode objects. [Note: Many of the classes in this chapter are declared in the package com.deitel.jhtp6.ch17. Each such class should be compiled with the -d command-line option to javac. When compiling the classes that are not in this package and when running the programs, be sure to use the - classpath option to javac and java, respectively.]

[Page 823]

Figure 17.3. ListNode and List class declarations.

(This item is displayed on pages 823 - 826 in the print version)

1

//

Fig.

17.

3: List.java

2

//

ListNode

and List class definitions.

3

package

com.deitel.jhtp6.ch17;

4

 

 

 

 

5

//

class

to

represent one node in a list

6class ListNode

7{

8

// package access members; List can access these directly

9

Object data;

10

ListNode nextNode;