e_book_javadatastructure_satishsinghal

453
Java Essentials – Topic 1 Page 1 of 33 CS 20 B : Java Data Structures Topic 1 : Java Basics Author: Satish Singhal Ph. D. Version 1.0 In this chapter, we will like to review those fundamentals of Java, which form the backbone of Java Data Structures technology. We break the essential topics into the following subcategories: Access Modifiers in Java Inheritance with in Classes Inheritance from and with in Interfaces Abstract Classes Having learned the above essential topics, we can progress towards learning about the abstractions needed to learn and represent data structures. Access Modifiers The access modifiers regulate the access of java classes, fields and methods. There are four different access modifiers. 1. public 2. private (An outer java class cannot be declared private. But inner classes can be declared private). 3. protected (An outer java class cannot be declared protected. But inner classes can be declared protected). 4. No modifier is specified. A minimal java class declaration may be done as follows: //************************************************* class MyClass { //---------------- } Listing 1.1 //********************************************** In defining the class in Listing 1.1 no modifier is used before the word class. Thus the class MyClass becomes what is called “package-private” or package visible. Note the absence of package name! This means that class is in an unknown package and is not accessible outside it!

Upload: api-3715806

Post on 16-Nov-2014

111 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 1 of 33

CS 20 B : Java Data Structures

Topic 1 : Java Basics Author: Satish Singhal Ph. D.

Version 1.0 In this chapter, we will like to review those fundamentals of Java, which form the backbone of Java Data Structures technology. We break the essential topics into the following subcategories: Access Modifiers in Java Inheritance with in Classes Inheritance from and with in Interfaces Abstract Classes Having learned the above essential topics, we can progress towards learning about the abstractions needed to learn and represent data structures.

Access Modifiers The access modifiers regulate the access of java classes, fields and methods. There are four different access modifiers. 1. public 2. private (An outer java class cannot be declared private. But inner classes can be declared private). 3. protected (An outer java class cannot be declared protected. But inner classes can be declared protected). 4. No modifier is specified. A minimal java class declaration may be done as follows: //*************************************************

class MyClass { //---------------- } Listing 1.1 //********************************************** In defining the class in Listing 1.1 no modifier is used before the word class. Thus the class MyClass becomes what is called “package-private” or package visible.

Note the absence of package name! This means that class is in an unknown package and is not accessible outside it!

Page 2: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 2 of 33

What that means is that MyClass is visible only to the classes, which are in the same package in which MyClass would be. All java classes are part of a package. If no package statement appears in the source code of the class, then the class becomes part of an un-named package, where the package is essentially the folder in which the file MyClass.java will reside. The folder name cannot be used to access the classes in it, the way a package name can be. Most useful form of java class declaration is as follows: //*************************************************

package datastructures; public class MyClass2 { //---------------- } Listing 1.2 //********************************************** Why is above form useful? Because now the class MyClass2 being declared as public becomes accessible to rest of the world by virtue of being part of a named package1. All Sun Java Classes are defined in this manner (All public, all members of some named package.)2 Modifiers private and protected are not used for the outer classes, but they can be used for the inner or nested classes. We will discuss additional modifiers for classes after we have discussed the topic of inheritance.

Modifiers for fields and methods Stand alone static Modifier: All useful java classes have fields and methods in them. Depending upon use of keyword static, the fields and methods in a java class may of any of the two types:

• Instance Fields

1 The package naming requires that name must be unique. Sun uses the system of reverse domain notation. For example the packages from Oracle Corporation may have names such as com.oracle.packagename. Since the name com.oracle will be unique, all packages become unique, with out any chance of naming conflict and confusion. 2 The details of compiling packages are given at the Web site below: http://www.yoda.arachsys.com/java/compiling.html

Includes the package name of which the class is member.

Class is declared public.

Page 3: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 3 of 33

• Class Fields • Instance Methods • Class Methods

The class fields and class methods use the modifier static in their declaration whereas the instance methods and fields do not use the modifier static. Example is shown in Listing 1.3. //************************************************* public class MyClass { int value; static int standard_num; static int findSquare(int val) { return val*val; } public String toString( ) { String Str = "The values of class members of class MyClass\n"; Str = Str + "standard_num = " + standard_num + “\n”; Str = Str + "value = " + value; return Str; } } Listing 1.3 //********************************************** Class fields and class methods can be invoked by using the class name alone. However, instance methods and instance fields are invoked by using the instance of the particular class. Listing 1.4 gives the code for testing the class MyClass. The results of running the program in Listing 1.4 are given in Figure 1.1.

Instance field

Class field

Class method

Instance method

Page 4: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 4 of 33

//************************************************************* public class TestMyClass { public static void main(String[] args) { MyClass Temp1 = new MyClass( ); Temp1.value = -10; MyClass.standard_num = 100; MyClass Temp2 = new MyClass( ); Temp2.value = -30; System.out.println (Temp1.toString( )); System.out.println (Temp2.toString( )); int num = MyClass.findSquare (5); System.out.println ("Value of square of 5 = " + num); } } Listing 1.4 //**********************************************************************

FIG. 1.1 It is clear from the Listing 1.3 and 1.4 that:

Setting the value of an instance field. Field named “value” had no modifier attached to it.

Setting the value of a class field.

Calling instance method.

Calling class method.

TestMyClass is the client class residing in the same package as MyClass.

Page 5: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 5 of 33

• Instance variables and methods can be accessed3 by qualifying them with the name of the instance of the class. For example if Temp1 is an instance of MyClass, then Temp1.value qualifies the value field of the instance Temp1.

• Using class name one can access class variables and methods. For example static variable standard_num can be accessed by using the class name MyClass as MyClass. standard_num.

Other Modifiers For Fields and Methods: If no modifier is specified, then a field or method is package-private or package visible. The examples are shown in Listing 1.3 and 1.4. The other modifiers that can be used for the fields and methods are:

• public • private • protected

Fields and methods that are declared public are accessible everywhere as long as the class in which they are declared is also public. The access mechanism for public identifiers is somewhat similar to the example shown in Listing 1.4. Figure 1.2 shows the visibility domain of public modifiers in more detail.

3 Methods and variables must be package-private(no access modifier used), public, or protected in order to be accessed inside another class in the same package. Public methods and variables are accessible everywhere.

We also must understand that static variables have only one copy for all the instances of a class, whereas non-static variables will have number of copies, equal to the number of instances for that class.

Page 6: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 6 of 33

FIG. 1.2 As shown in Figure 1.2, the public field in the class “a class” is visible inside all of its subclasses as well as inside all the client4 classes. Protected fields and methods are visible and accessible in the derived classes and in the classes in the same package, but not in any other classes. We discuss the protected fields in more detail after we discuss the inheritance. The Figure 1.3 below shows the visibility of protected fields in more detail.

4 Client classes are the one which may make use of “a class” either as a local variable or as a class level variable.

Class “ a class” must be declared public. And must be in a named package.

Page 7: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 7 of 33

FIG. 1.3 As shown in Figure 1.3, the protected fields and methods are visible to all sub classes and classes in the same package, but not to the clients that are outside the package. A sub class may lie inside or outside the package. Private fields and methods are not visible outside the class they are declared. Private fields can only be accessed, outside their native class by using a public method to allow access to them. Figure 1.4 shows the visibility of private fields and methods in more detail. The sub-classes inherit the private members of super class, but have no access to them. To access the private but inherited members, the super class must have a public method available, which can get the access to the private fields.

Clients that lie outside the package also lie outside the area of visibility!

Also visible to clients inside the package, in which the class “a class” is !!

Need not lie inside the same package in which “ a class” is!

Page 8: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 8 of 33

FIG. 1.4

Sub classes inherit the private fields and methods but they do not have direct access to them.

When fields and methods have no access modifier attached (package private), the access to them is even more restricted than the protected modifier. The package private fields and methods are visible only inside the package. In fact even subclasses of a class that lies outside the package has no direct access to them!

Page 9: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 9 of 33

The Figure 1.5 below shows how the visibility of package-private fields and methods work.

Package – Private or No M odifierPackage

boundary

a class

Field with no modifier

Client 1

Client 2

Subclass 1

Subclass 2

Area of visibility.

Clients and subclasses, both must lie inside

the package

FIG. 1.5

Inheritance

When we write a Java classes, we try to model a part of the reality of the world. For example, when we write a complex number class, we model the behavior of complex numbers in the class. However, in reality the things are related. By modeling the related things together, we can see that how modeling one thing, can help us model something related to it. Using Java’s mechanism of establishing inheritance relationship between classes can model reality more effectively. Let us take an example of vehicles in the world. Every vehicle has at least one property – speed. We can write a class called vehicle, which has one data member called speed. However, there are many kinds of vehicle in the world. For example, the vehicle may be a wheeled vehicle, which will move on roads and surfaces or it may be an airplane that will fly in air. Both vehicles such as airplane and vehicle with wheels have a relationship with general concept vehicle. We can say that an airplane is-a vehicle. We can also say that a vehicle with wheels is-a vehicle. When objects have this “is-a” type relationship between each other, then an inheritance relationship

Page 10: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 10 of 33

exists between them. Using this concept of “is-a” type relationship, we can set up an inheritance hierarchy between vehicles of various types as follows (Figure 1.6).

Microsoft PowerPoint Presentation

FIG. 1.6 Now, at each level of above inheritance hierarchy, new properties are added to the system of related objects. For example, we may ask the question, as to what is the most common property to all vehicles? Obvious answer is that all vehicles have speed. So now, we take this system of inheritance relationships and start filling it with properties. This is dynamically presented below.

Microsoft PowerPoint Presentation

We can see that all vehicles have a property speed, and all airplanes have a property called “engine thrust”. We keep adding one or more new property at each level of inheritance derivation in our system. The Figure 1.7 below shows the result.

Page 11: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 11 of 33

FIG. 1.7 Based on the inheritance relationship we established above, and based on the properties assigned at each level, now we may start writing the code for this system of related classes. However, let us look at few definitions here. These are summarized in the Figure 1.8 below.

FIG. 1.8 At each level of inheritance, the class, which is the source of inheritance, is called a base class or a superclass. Moreover, the class that results from inheritance is called a derived class of subclass. The phrase subclass should not be taken to mean that somehow the derived class is “lesser” in some way. In fact, if anything it has more data members. In the present system that we have built here, the Vehicle class is the

Page 12: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 12 of 33

base class or superclass, where as the with respect to the vehicle class, the class Airplane is a derived class or subclass. We illustrate that in the Figure 1.9 and 1.10

FIG. 1.9

FIG. 1.10

Page 13: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 13 of 33

Syntax For writing classes related by inheritance

Taking the example of the system of inheritance we built in the Figure 1.6, we show the syntax for establishing the inheritance relationship between classes below (Figure 1.11).

FIG. 1.11 Generally in java classes that are not expected to be extended, the data members are declared private. One characteristic of private data members is that, they are visible to the methods in the class, but invisible to rest of the world. The establishment of inheritance, when all the data members of base class are private, complicates the matter somewhat. Java allows another form of access specifier for the data members of a class, which makes them invisible to world outside the class and its package, but visible to derived classes. This specifier is called “protected”. As shown in Figure 1.3, the protected data members are visible in the derived classes and in the client classes in the same package.

public class Vehicle { //Fields and Methods } public class Airplane extends Vehicle { //Fields and Methods }

Uses the keyword extends to establish inheritance!

Page 14: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 14 of 33

FIG. 1.12 By making the field of class Vehicle, for example in this case, the speed protected, has the affect of class Airplane having two fields, speed and numberOfwings. Similarly, the class Jet has three fields, speed, numberOfwings, and enginethrust. What we do now is that we add fields, constructors, and a method called describe ( ) to all classes in the Figure 1.7. This results in the code given in Listing 1.5 below.

In Listing 1.5, the function describe ( ) for each class has a single output statement describing the type of vehicle the class represents. For example the function describe ( ) for class Vehicle is coded as follows , and we also add the constructors for class vehicle.

public class Vehicle { protected double speed; } public class Airplane extends Vehicle { protected int numberOfwings; } public class Jet extends Airplane { protected double enginethrust; }

Visible to both class Airplane and class Jet. However, invisible to client code, like main function, when doing black box testing and client lies outside the package.

Visible to class Jet, but invisible to client code, like main function, when doing black box testing and client lying outside the package.

Page 15: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 15 of 33

Listing 1.5 – Part A There are two constructors in the Listing 1.5 part A. The first one is argument-less, and second one has arguments equal to the number of fields to be initialized in the class. The two constructors are chained together. The chaining of constructors is done as follows:

• Write the argument-less constructor first. • From with in the argument-less constructor, call the constructor with one

argument using the reference operator “this”, and provide a default value for the argument of the constructor.

• Inside the one argument constructor, set the value of the field, equal to the constructor argument.

The biggest advantage of chaining constructors is that it allows us to set some default values for the class fields. Java will always provide a constructor, even if one is not authored, but hazards of doing that are too great at times. Besides, the constructor provided by Java will set the class fields to some fundamental values, which may not always be the best choice. Therefore, it is best to always write constructors and chain them together. We modify the Listing 1.5 A somewhat by putting an output statement inside each constructor. Figure 1.5 B to 1.5 G show the code for all the classes diagrammed in the Figure 1.7. We discuss them starting on page 22.

public class Vehicle { protected double speed; public Vehicle( ) { this (0.0); } public Vehicle (double init_speed) { speed = init_speed;

} public void describe( ) { System.out.println ("I am an abstract Vehicle\n"); } }

Chains the constructor with no argument to the constructor with speed as an argument! Keyword this refers to calling the constructor, in this case the one with one argument.

Page 16: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 16 of 33

public class Vehicle { protected double speed; public Vehicle() { this(0.0); System.out.println ("From Vehicle CTOR #1"); } public Vehicle( double init_speed) { speed = init_speed; System.out.println ("From Vehicle CTOR #2"); } public void describe() { System.out.println ("I'm an abstract Vehicle\n"); } public double getSpeed( ) { return speed; } } Listing 1.5 – Part B

Page 17: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 17 of 33

public class Airplane extends Vehicle { protected int numberOfWings; public Airplane() { this(0); System.out.println("From Airplane CTOR #1"); } public Airplane( double init_speed) { this(init_speed, 0); System.out.println("From Airplane CTOR #2"); } public Airplane( double init_speed , int init_wings ) { super(init_speed); numberOfWings = init_wings; System.out.println("From Airplane CTOR #3"); } public void describe() { super.describe(); System.out.println("I'm a general Airplane.\nI travel “ +

through the air.\n"); } public int getNumWings( ) { return this.numberOfWings; } }

Listing 1.5 – Part C

Page 18: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 18 of 33

public class Proplane extends Airplane { protected int numberOfProps; public Proplane() { this(0); System.out.println("From Proplane CTOR #1"); } public Proplane( double init_speed) { this(init_speed,0); System.out.println("From Proplane CTOR #2"); } public Proplane( double init_speed, int init_wings) { this(init_speed, init_wings, 0); System.out.println("From Proplane CTOR #3"); } public Proplane( double init_speed, int init_wings, int numProps) { super(init_speed, init_wings); numberOfProps = numProps; System.out.println("From Proplane CTOR #4"); } public void describe() { super.describe(); System.out.println("I'm a PropPlane.\nI have propellers “ + to fly.\n"); } }

Listing 1.5 – Part D

Page 19: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 19 of 33

public class Jet extends Airplane { protected double engineThrust; public Jet() { this(0); System.out.println("From Jet CTOR #1"); } public Jet( double init_speed) { this(init_speed,0); System.out.println("From Jet CTOR #2"); } public Jet( double init_speed, int init_wings) { this(init_speed, init_wings, 0.0); System.out.println("From Jet CTOR #3"); } public Jet( double init_speed, int init_wings, double thrust) { super(init_speed, init_wings); engineThrust = thrust; System.out.println ("From Jet CTOR #4"); } public void describe() { super.describe(); System.out.println("I'm a Jet.\nI am propelled by a fast jet engine.\n"); } public double getEngineThrust() { return engineThrust; } }

Listing 1.5 – Part E

Page 20: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 20 of 33

public class VehicleWithWheels extends Vehicle { protected int numberOfWheels; public VehicleWithWheels() { this(0); System.out.println("From VehicleWithWheels CTOR #1"); } public VehicleWithWheels( double init_speed) { this(init_speed, 0); System.out.println("From VehicleWithWheels CTOR #2"); } public VehicleWithWheels( double init_speed , int init_wheels ) { super(init_speed); numberOfWheels = init_wheels; System.out.println("From VehicleWithWheels CTOR #3"); } public void describe() { super.describe(); System.out.println("I'm a general Vehicle With Wheels.\nI use “ + wheels to travel on the land.\n"); } }

Listing 1.5 – Part F

Page 21: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 21 of 33

public class Car extends VehicleWithWheels { protected double horsepower; public Car( ) { this(0.0); System.out.println("From Car CTOR #1"); } public Car( double init_speed) { this(init_speed, 0); System.out.println("From Car CTOR #2"); } public Car( double init_speed,int nWheels) { this(init_speed, nWheels, 0.0); System.out.println("From Car CTOR #3"); } public Car( double init_speed,int nWheels, double init_hp) { super(init_speed, nWheels); horsepower = init_hp; System.out.println("From Car CTOR #4"); } public void describe() { super.describe(); System.out.println("I am a Car. I'm the most “ + common wheeled vehicle.\n"); } }

Listing 1.5 – Part G

Page 22: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 22 of 33

Writing the Constructor for the derived classes: Since the derived classes inherit a part of them from their super class, during the construction of derived classes, the super class constructor is always called5. Note that except the java class Object, all classes in java are derived classes!! Therefore, any time a class is instantiated, the constructor of class Object is always called. How can we confirm that an Object class constructor is always called, when any java class is instantiated? We can do a very simple experiment to prove this. In Listing 1.6, we create a class called AnyClass.

We create an instance Temp of class AnyClass by calling the operator new and calling the default java provided constructor. Then we call an instance method hashcode( ), to print the hash code for the Temp. The resulting output is shown in the Figure 1.13 below.

FIG. 1.13 The class AnyClass does not have a method called hashcode. Therefore, where did the method hashcode come from? It actually came from the class Object, which is the super-class for AnyClass. Moreover, the Object part of AnyClass instance Temp is built, when during the constructor call new AnyClass( ), the Object class 5 If derived class constructor does not make an explicit call to the super class constructor, then Java will automatically call the default super class constructor.

With exception of java class Object; ALL classes in java are derived classes!!!

Listing 1.6

Page 23: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 23 of 33

constructor is called. This is done automatically, even if there is no constructor or no explicit call to the super class constructor is made. The hashcode method call just prints the memory location, where the object “Temp” happens to be located on the heap part of the memory. Looking at the Listing 1.5 C, we observe that for the class Airplane we have three constructors, the argument-less, one argument and two arguments. The super class constructor, using the word super is called from the two arguments constructor. Testing Inheritance – Polymorphism in Action The word Polymorphism means, “having many forms”. When a class is a base class then it can hold the reference to the instances of all derived classes. A reference type of base class then can have many forms, as it can polymorph itself in forms to act like any of its base classes. For example in the system of inheritance shown in Listing 1.5 (also see Figure 1.7), the reference type Vehicle can point to the objects of type Airplane, Jet, Proplane, car, and VehicleWithWheels. We test the polymorphism in the Listing 1.7. public class TestInheritance { public static void main(String[ ] args) { Vehicle Veh1 = new Jet(500,4, 200); Veh1.describe( ); Veh1 = new Airplane(400, 2); Veh1.describe( ); Veh1 = new Car ( 60, 4, 20); Veh1.describe( ); } } Listing 1.7 The output of the Listing 1.7 is shown in the Figure 1.14.

Veh1 points to an object of type Jet.

Jet class describe method is called.

Veh1 now points to an object of type Car.

Car class describe method is called.

Page 24: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 24 of 33

FIG. 1.14 Sequence of Constructor Calls In Listing 1.7, first the Jet constructor is called by the statement, Vehicle Veh1 = new Jet (500,4, 200); The Jet class constructor, which takes three arguments, first calls its super class constructor with parameter speed equal to 500 and number of wings equal to 4. The super class to Jet is Airplane. The two-argument constructor in Airplane class calls its super class constructor with the parameter speed equal to 500. Now although the Vehicle class does not show an explicit call to a constructor to its super class ( java Object class), an Object class constructor is nevertheless called. After that the speed is set equal to 500 by executing the statement in Vehicle class one argument constructor as: speed = init_speed; Therefore, in order to build an instance of Jet class, the constructor calls are made in the following sequence (Figure 1.15).

Page 25: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 25 of 33

Sequence of Construction of Jet class instance

Object Constructor call

Vehicle Constructor call

Airplane Constructor call

Jet Constructor call

TimeObject part is created first!

Vehicle part is created

next

Jet part is created Last!

FIG. 1.15 The Figure 1.14 shows the output statements coded in the constructors of Vehicle, Airplane, and Jet class, when the Jet class constructor in the Listing 1.17 is called. These output statements are: From Vehicle CTOR #2 From Airplane CTOR #3 From Jet CTOR #4 There is no output statement in the Object class constructor so we do not see a similar statement from that. However, the sequence of other three-constructor call is similar to the sequence shown in the Figure 1.15. General conclusion drawn is as follows:

In java constructor calls are made in the sequence of inheritance hierarchy of an object. The java Object class constructor will ALWAYS be called first, followed by the sequence of inheritance hierarchy of an object being created.

Page 26: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 26 of 33

Dynamic Binding or Late Binding One powerful manifestation of polymorphism is dynamic binding or late binding. We emphasized before that a derived class inherits ALL data and methods from a super class. In Listing 1.5, the method describe exists in all classes. The second statement in Listing 1.7 makes a call to the method describe as follows: Veh1.describe( ); Veh1 is a reference of type Vehicle. How does it know:

• That it is pointing to a Jet type object • That Veh1.describe ( ) must call the describe method

from Jet class? The answer to the first question lies in fact that all constructor calls are made at the run time, when an object is created on the heap part of the memory. At run time when Veh1 points to the Jet type object, Java Virtual machine (JVM) records that fact. Also all the instance method calls (instance of a class calling a non-static method) are resolved not at the compile time, but at the run time6. Then JVM correctly binds the object type to the method that belongs to the object. This happens irrespective of the type of reference pointing to the object. Therefore, even though in statement Veh1.describe ( ), the Veh1 is a Vehicle reference type, JVM knows that Veh1 is pointing to a Jet type object and at run time the describe( ) method from Jet class is invoked, giving the output (Figure 1.14): I'm an abstract Vehicle I'm a general Airplane. I travel through the air. I'm a Jet. I am propelled by a fast jet engine. The mechanism where JVM will correctly bind an object to “its” method at run time is called dynamic binding or late binding. Limitations of Dynamic Binding Calling describe method for a Jet Object using the Vehicle Class reference worked because both Vehicle as well as Jet have a method called describe with identical signatures. What if we remove the method describe from the class Vehicle? When we remove the method describe from the Vehicle class and try to compile the Listing 1.7, we get the following compiler errors: TestInheritance.java:14: cannot resolve symbol symbol : method describe ( ) location: class Vehicle Veh1.describe( ); ^ 6 All the static or class method calls are resolved at the compile time.

Page 27: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 27 of 33

TestInheritance.java:19: cannot resolve symbol symbol : method describe () location: class Vehicle Veh1.describe( ); ^ TestInheritance.java:21: cannot resolve symbol symbol : method describe () location: class Vehicle Veh1.describe( ); All of a sudden because of absence of describe method from the Vehicle class, the Vehicle reference Veh1 can no longer bind to the describe methods in its descendent objects. This is because JVM knows how to correctly bind the method name and object type, only when the method with the same signatures exists in the super class and sub class. A super class reference, pointing to a subclass object has no way of knowing about those methods, which do not also exist in the super class. The super class reference to a sub class object in such case is only usable by using the instanceof operator and by proper type casting. This is shown in Listing 1.8. public class TestInheritance { public static void main(String[ ] args) { Object Temp = new Jet(500, 4, 200); if(Temp instanceof Jet) { Jet Jet1 = (Jet)(Temp); Jet1.describe(); System.out.println("The speed of Jet = " + Jet1.getSpeed()); System.out.println ("The number of wings in Jet = " + Jet1.getNumWings( )); System.out.println ("The Engine thrust for Jet = " + Jet1.getEngineThrust( )); } } } Listing 1.8 In Listing 1.8, the Boolean expression inside the if clause first decides whether the Temp is indeed type Jet or not? If Temp is not type Jet, then instanceof operator will return a false and the statements in the scope of if clause will not be executed.

If Temp is type Jet, then instanceof will return true otherwise it will return false.

Now it is safe to cast Temp into Jet Type reference.

Page 28: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 28 of 33

But if Temp is indeed type Jet, then instanceof operator returns a Boolean true, and the statements in the scope of if clause are executed. Then it becomes safe to cast the Temp to Jet type reference, since we already know that it is Jet type. After that all the Jet class instance methods can be called using the new reference Jet1 (Listing 1.8). The results of running Listing 1.8 are shown in Figure 1.16.

FIG. 1.16 One can see that all Jet class methods are called properly and they give proper results by following the procedure described in the Listing 1.8. Further advantages of polymorphism will be discussed after we have discussed the interfaces and Java’s limited form of multiple inheritance. Preventing Inheritance Some times for security and other reasons it is necessary that another class never inherits a class. Defining a class final prevents it from another class inheriting it. All classes in java.lang package are final classes. The syntax of defining a class to become final is as follows: A compile error will result if another class tries to extend the class FinalClass. An important example of a java class that is defined as final is java String class. Once a String object is created, it can never be altered because the String class is a final

public final class FinalClass { //----------------------------- }

Page 29: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 29 of 33

class and an object of a final class cannot be altered. Such objects are called immutable objects. Strings are used as username and passwords in the Java Web Services environments. Their immutability makes java network transactions more secure.

Other Related Topics Local Class Definitions: Java allows classes to be defined locally inside the methods. The locally defined classes are not allowed to have any access modifier attached to them, but they can be declared final. They are visible only inside the method in which they are defined. The locally defined classes cannot have static fields or methods and they themselves cannot be static. Irrespective of access modifier used for the fields and methods of local classes, their members are all public and visible in the method in which they are defined. However, they can extend other classes and implement interfaces like regular classes. Listing 1.9 shows a declaration of local class SomeClass and its use. The results of running the program are shown in the Figure 1.17.

Listing 1.9

Page 30: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 30 of 33

FIG 1.17 Local classes like Listing 1.9 can be defined inside any method, constructor or initialization block7. Local classes can access, with in scope, locally defined constant reference types8 or class level static variables. The method in which local class is declared can return their instance as a method return value or pass their instance as a parameter to another method. Anonymous Classes Anonymous or no name classes are declared on the fly, by using the operator new. They behave just like locally defined classes, except they are for one time use only as they have no name. For example in Listing 1.9, the program statement below can create a no-name instance of class AnyClass: new AnyClass( ); In a form such as shown by the above statement, they can only show an output from the constructor being called. Anonymous classes are more useful when they are instantiated and used to return as a value from a method, or provide some other “one time” functionality. The rules as to which local and class level parameters anonymous classes can access locally are same as for the local classes discussed above. In Listings 1.10 and 1.11 below, we contrast the use of named local inner class and anonymous class to achieve the same objective, which is used to close a 7 An initialization block is located at the class level as a stand-alone block (bounded with curly braces). The initialization blocks may be marked as static or may be unmarked. Code in all static blocks is executed “once” when program is run first time. Non-static or unmarked blocks are executed each time a class constructor call is made. 8 Local constant reference types can be declared as follows: final AnyClass Var1 = new AnyClass( ); Note that in this case the reference Var1 is constant, which means that Var1 cannot be used to point to another object of type AnyClass later in the program sequence. This does not however make the object to which Var1 is pointing to immutable (or unchangeable). The java objects are immutable only if their class is defined as final.

Page 31: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 31 of 33

graphical java program using java class JFrame properly. The programs simply show up a Java Graphical window with a label in it, which can be closed by clicking on the close icon on top right side and free the system resources.

import java.awt.event.*; import javax.swing.*; import java.awt.*; public class Window1 extends JFrame { private JLabel My_Label; public Window1() { super("A Simple Window with a label in it!"); Container Content = getContentPane(); Content.setLayout(new FlowLayout()); Content.setBackground(Color.cyan); this.My_Label = new JLabel("Hello From a java Window"); this.My_Label.setFont(new Font("Ariel",Font.BOLD,30)); this.My_Label.setForeground(Color.red); Content.add(this.My_Label); setSize(400,120); setVisible(true); } public static void main(String [ ] args) { Window1 Temp = new Window1(); class HelpCloseWindow extends WindowAdapter { public void windowClosing(WindowEvent Evt) { System.exit(0); } } Temp.addWindowListener (new HelpCloseWindow()); } } Listing 1.10

Page 32: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 32 of 33

Figure 1.18 shows the output of the Listing 1.10 below.

FIG. 1.14 However, if only one window is to be created in the program, then we can use an anonymous class, which extends the Java class WindowAdapter for one time use. The Listing 1.11 shows the code for such anonymous class and its use. The results of Listings 1.10 and 1.11 are identical. WindowAdapter is an abstract class in the java.awt.event package. As we will discuss in future sections, the abstract classes can only be extended. In Listing 1.10, the class HelpCloseWindow extends the abstract class WindowAdapter. The latter has one unimplemented method called windowClosing( ), which is implemented by the class HelpCloseWindow. However, the anonymous class in Listing 1.11 is directly instantiated by calling the constructor for it and extending the class WindowAdapter and implementing the method windowClosing ( ) all at once. While it almost appears as if in Listing 1.11, we are calling a constructor of the abstract class WindowAdapter (a forbidden practice), that is not the case. The constructor call applies to entire anonymous class, and not to the WindowAdapter class, which is being extended. Here the constructor call and extending the WindowAdapter class are broken down into two sections. The portion, new WindowAdapter( ) instantiates the anonymous class, while the portion, { public void windowClosing (WindowEvent Evt) { System.exit (0); } } extends the WindowAdapter class by implementing its abstract method windowClosing( ).

Page 33: E_Book_JavaDataStructure_SatishSinghal

Java Essentials – Topic 1 Page 33 of 33

import java.awt.event.*; import javax.swing.*; import java.awt.*; public class Window1 extends JFrame { private JLabel My_Label; public Window1() { super("A Simple Window with a label in it!"); Container Content = getContentPane(); Content.setLayout(new FlowLayout()); Content.setBackground(Color.cyan); this.My_Label = new JLabel("Hello From a java Window"); this.My_Label.setFont(new Font("Ariel",Font.BOLD,30)); this.My_Label.setForeground(Color.red); Content.add(this.My_Label); setSize(400,120); setVisible(true); } public static void main(String [ ] args) { Window1 Temp = new Window1( ); Temp.addWindowListener ( new WindowAdapter( ) { public void windowClosing (WindowEvent Evt) { System.exit(0); } } ); } }

Notice that we use the class to be extends or implements directly and use of keyword extends or implements is not needed.

Pair of braces after the constructor call defines the body of anonymous class

The unimplemented and other methods are now coded inside the pair of braces, which define the anonymous class.

The right parenthesis for the method addWindowListener seems out of place and awkward but is syntactically correct!

Listing 1.11

Page 34: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 1 of 16

CS 20 B : Java Data Structures

Topic 2 : Abstract and Inner Classes Author: Satish Singhal Ph. D.

Version 1.0 Object oriented languages as Java and C++ use a concept of abstract classes, so that an object of certain type can be conceptualized and then extended further. For example, take the concept of round shapes in geometry. The roundness is a property that is present in many different forms in real objects. The circle, spheres, and, elliptical objects have roundness. However, the concept roundness is a bit abstract, so if we wrote a class to capture this concept, we may never be able to truly find a real object for which the class Roundness can be a template. The concepts and behaviors, that apply to other objects, but are too abstract to make concrete, are often written in the form of abstract classes. In java, the abstract classes have following main properties. FIG. 2.1 Example of an abstract class and its extension. Let us consider the following inheritance hierarchy (Figure 2.2).

Abstract Class 1. The abstract class has at least one abstract method. An abstract method is a method, which has no body or definition. It just has a signature and return type. 2. The usefulness of abstract class lies in it being extended by other classes or other classes being derived from it. 3. An abstract class cannot be instantiated.

Page 35: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 2 of 16

FIG. 2.2 A shape is a general abstract concept. We know that a shape can have area and in some cases volume. A shape certainly has a name. However, shape is abstract enough that we can define an abstract class called Shape to model it. Now there can be many types of shapes. A point is a shape, which is infinitesimal in size. However, it has properties such as coordinates x and y in the Cartesian system. Therefore, the Class Point can extend the general concept of Shape to a concrete form. Hence, we derive the class Point from Shape. A Square has a center point just like a Point and a class Square can inherit the coordinates x and y from class Point. Nevertheless, Square shape has additional property like a length or edge. Therefore, we derive the class Square from Point. Finally, a Cube has the same properties or fields as the Square does – like a central point, and an edge, but has a volume, which square does not have. Yet, no new fields are needed if we were to derive the class Cube from class Square. Converting the Inheritance Design Into The Source Code: Now we convert the above design into source code, first by writing various classes in the inheritance hierarchy and then writing a driver class program to test them.

Page 36: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 3 of 16

class Shape The Listing 2.1 below gives the code for the abstract class Shape.

Listing 2.1 The class Shape is an abstract class because it has one un-implemented (abstract) method getName( ). The syntax requires that the keyword “abstract” be included in the declaration of method signature. A class can also become abstract if it derives from an abstract class and does not provide implementation for the abstract methods for its parent. Little bit later we will study another java construct called interface. A Java interface only contains abstract methods and can be “implemented” by other classes by using keyword “implements”. A java class that implements an interface but does not provide implementation (body) for all of its super-interface methods is also an abstract class. class Point The source code for the class Point is given below in the Listing 2.2.

Abstract Method

Implemented Methods

Uses the keyword abstract

Page 37: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 4 of 16

public class Point extends Shape { protected int x; protected int y; public Point() { this(0); } public Point(int a) { this(a,0); } public Point(int a, int b) { this.x = a; this.y = b; } public String getName() { return "Point"; } public void setPoint(int a, int b) { this.x = a; this.y = b; } public void setX(int x1) { this.x = x1; } public void setY(int y1) { this.y = y1; } public int getX() { return this.x; }

Fields are x and y coordinates for the Point.

Default and explicit constructors, all chained together.

abstract method from the parent class Shape implemented.

Other methods of class Point.

Page 38: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 5 of 16

public int getY() { return this.y; } public String toString() { return "[" + this.x + ", " + this.y + "]"; } } Listing 2.2 class Square The source code for the class Square is given below in the Listing 2.3. public class Square extends Point { protected double edge; public Square() { this(0.0); } public Square(double edge1) { this(edge1,0,0); } public Square(double edge1, int a) { this(edge1, a, 0); } public Square(double m_edge, int a, int b) { super(a,b); setEdge(m_edge); } public void setEdge(double m_edge) { this.edge = (m_edge >= 0 ? m_edge : 0 ); }

New field edge is added. It inherits x and y.

Constructors

Methods

Page 39: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 6 of 16

public double getEdge() { return this.edge; } public double area( ) { return this.edge*this.edge; } public String toString() { return "center = " + super.toString() + " ; Edge = " + this.edge; } public String getName() { return "Square"; } }//End of class Square Listing 2.3 class Cube The source code for the class Square is given below in the Listing 2.4. Does not need any new fields. public class Cube extends Square { public Cube() { this(0.0); } public Cube(double edge1) { this(edge1,0,0); } public Cube(double edge1, int a) { this(edge1, a, 0); } public Cube(double m_edge, int a, int b) { super(m_edge, a , b); }

Constructors

Page 40: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 7 of 16

public double volume() { return this.edge*this.edge*this.edge; } public double area() { return 6*this.edge*this.edge; } public String toString() { //return super.toString() + " ; Height = " + this.edge; return super.toString(); } public String getName() { return "Cube"; } }//end of class Cube Listing 2.4 The UML(Unified Modeling Language) diagram for all these classes is given on next page.(Figure 2.3).

Page 41: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 8 of 16

FIG. 2.3 Inner Classes: A class is an inner class, when it is declared inside another class. Some time such classes are also called nested classes. However, the Java Specification document points out a subtle difference between the inner classes and nested classes. First, let us discuss few more definitions. A stand-alone class is not allowed to be declared static. For example:

Page 42: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 9 of 16

However, a nested class can be declared static. For example, the declaration like below is OK.

static class ExplicitStatic { }

Compile time error. A stand-alone class cannot be declared static.

public class Outer { static class NetsedClass { Can declare static variables and constants. } }

This is OK!!

public class Outer { class InnerClass { //Cannot declare static variables!! //Can declare static constants! } }

This is also OK!!

public class Outer { class InnerClass extends SomeClass { //Cannot declare static variables!! //Can declare static constants! } }

This is also OK!!

Page 43: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 10 of 16

Instantiation of Inner Classes: A static inner class or a nested class can be instantiated all by itself, even if the class it is enclosed in is not instantiated. One example we already discussed is an intelligent closeable window which had an inner static class inherited from the java class WindowAdapter. The code is given below in Listing 2.5.

Listing 2.5 The above program creates a window with blue background color and it can be closed by clicking on the icon X on right hand corner or by pressing alt + F4. The nested class WindowClosing is instantiated in the main method by the statement below, even though the class CloseableWindow is never instantiated in the whole program. WindowClosing Object_WindowClosing = new WindowClosing( );

Static inner class/nested class

Page 44: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 11 of 16

This is not very surprising, as all static members of any java class can be created before any instance of that class can come into existence. Important question however, is that what would happen if the inner class WindowClosing is made a non-static class. In Listing 2.5, a whole bunch of extra code will be needed to achieve the same results if the nested class WindowClosing is made non-static. This code is shown for the class ColseableWindow2 in Listing 2.6.

Listing 2.6 Note that when an inner class is non-static then an instance of outer class will be needed, before an instance of inner class can be created. Also, note the syntax of the constructor call for the inner class:

Instance of class CloseableWindow2 is now needed because with out it the inner class WindowClsoing cannot be instantiated.

Instance.new is required to call the constructor for inner class

Page 45: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 12 of 16

CloseableWindow2.WindowClosing Object_WindowClosing = Instance.new WindowClosing( ); The operator new cannot be called directly. Rather it has to be dotted with the instance of outer class to signify as to which object is it for which the inner class instance is being created. The reference specification also requires that we show data-type as CloseableWindow2.WindowClosing, to provide the exact identification of the outer and inner class pairing. Even more code would be needed if the Frame type class variable My_Window is made non-static in the Listing 2.6. Affect of this are shown in the Listing 2.7 below. import java.awt.*; import java.awt.event.*; public class CloseableWindow3 { private Frame My_Window ; public CloseableWindow3( ) { this.My_Window = new Frame(); } private class WindowClosing extends WindowAdapter { public void windowClosing(WindowEvent event) { CloseableWindow3 Temp = new CloseableWindow3(); Temp.My_Window.dispose( ); System.exit(0); } } public static void main(String [] args) { CloseableWindow3 Instance = new CloseableWindow3( ); Instance.My_Window.setBounds(10,10,600,600); Instance.My_Window.setBackground(Color.blue); Instance.My_Window.setVisible(true); CloseableWindow3.WindowClosing Object_WindowClosing = Instance.new WindowClosing(); Instance.My_Window.addWindowListener(Object_WindowClosing); } }

A constructor to instantiate the object Frame is now needed. Otherwise the inner class WindowClosing will not know as to which My_Window to dispose.

Before the dispose can be called on My_Window, we need an object of type CloseableWindow3 to establish a binding between the outer class and inner class objects.

Creation of My_Window also needs an object of type ClosingWindow3.

Listing 2.7

Page 46: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 13 of 16

It is important to note that Listings 2.5, 2.6 and 2.7 all accomplish almost the same results. What is then the advantage of the Listing 2.7, where neither the object Frame (My_Window), nor the inner class (WindowClosing) is static? Well now, we can have more than one window in our program if we wanted to. This is shown in Listing 2.8 for WindowClsoing4, where we create two windows one with background blue and other with yellow background. import java.awt.*; import java.awt.event.*; public class CloseableWindow4 { private Frame My_Window ; public CloseableWindow4() { this.My_Window = new Frame(); } private class WindowClosing extends WindowAdapter public void windowClosing(WindowEvent event) { CloseableWindow4 Temp = new CloseableWindow4(); Temp.My_Window.dispose(); System.exit(0); } } public static void main(String [] args) { CloseableWindow4 Instance = new CloseableWindow4(); Instance.My_Window.setBounds(10,10,600,600); Instance.My_Window.setBackground(Color.blue); Instance.My_Window.setVisible(true); CloseableWindow4.WindowClosing Object_WindowClosing = Instance.new WindowClosing(); Instance.My_Window.addWindowListener(Object_WindowClosing); display1(); } public static void display1( ) { CloseableWindow4 Instance = new CloseableWindow4(); Instance.My_Window.setBounds(100,100,100,100); Instance.My_Window.setBackground(Color.yellow); Instance.My_Window.setVisible(true); CloseableWindow4.WindowClosing Object_WindowClosing = Instance.new WindowClosing(); Instance.My_Window.addWindowListener(Object_WindowClosing); } }

Method display has the same code as the main method, except the window is smaller and yellow in color and its origin is located differently.

Listing 2.8

Page 47: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 14 of 16

One thing one would notice is that when in executing the program in Listing 2.8 one tries to close any of the window, both Windows are closed at the same time. That is because when we click on Window closing (x) icon, though the method WindowClosing is called only for one object, the method System.exit (0) is executed anyhow, which results in program termination as a whole. Testing of The System of Classes Developed in Figure 2.2 There are two ways to test the Inheritance System built in Figure 2.2 and the system of classes shown in Listings 2.1 to 2.4. 1. Using JoptionPane for user input and output. 2. Using the normal Window Frame or an instance of java.awt.Frame class. In last two laboratory exercises, we made a good use of the JoptionPane. Therefore, now it is time for us to move further. In testing the classes of Figure 2.2, and Listing 2.1 to 2.4, while we will be using the JoptionPane for input, we would display all data in the Window Frame using objects of class java.awt.Label, added to the window Frame. In the last section on inner classes, we showed that in order for a colosebale Window to show up, we need the following objects and steps.

1. A static java.awt.Frame object constructed at the class level.

2. Set the bounds of this Frame object. 3. Set the background color of this Frame object. 4. Make the Frame object visible. 5. Close and dispose the Frame object, when user clicks on

window closing icon or presses alt + F4. The last step requires that we give our frame, created in steps 1 to 4 a capability to “listen” and respond to user clicks or keystrokes. Asynchronous Programming When we give a graphical component in a user interface the capability to respond to user actions (like keystrokes or mouse actions), such programming is called asynchronous programming. It is so because the user action will not follow the sequential flow and execution that program source code has. The program may be doing ten different things, but if user presses certain keys or clicks mouse then the program has to respond to it. The whole process requires interaction of three agents:

1. User 2. Operating System (in our case Windows) 3. The program

When user takes an action, like pressing a key or clicking certain active area of the program window, the operating system fires an object (called windows event) to the

Page 48: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 15 of 16

program. This is the way of operating system informing the program that certain event has taken place in the program. However, this communication from the operating system to the program will go unheard unless the program has some intelligence to “listen” to those messages from the operating system. Adding so-called “listeners” to the each graphical component, which user can activate in the program by clicking a mouse or using keyboard, provide this intelligence. These listeners are always in the background to receive any messages that operating system sends them. After receiving the message from the operating system, the listener will execute one or program method to respond to the user action. Those methods are called event handlers. The listeners are added to graphical or GUI components by invoking one of their method which has a name similar to: addSomeListener ( ). In addition, a method that adds a listener will take as its argument an object, which can invoke an event handling method or invoke an event handler. The process of adding a listener is called “registering” a GUI component with the event listener. This listener is a java class that has the event handling method in it. In Listing 2.4, we accomplish the closing of program window as follows. When the window from Listing 2.4 is in front of user and the user clicks the mouse to close it, then at that point the following sequence of events takes place.

1. The Frame object is registered with a “listener” by calling its inherited method addWindowLsitener. This method takes an argument of “type” WindowListener. WindowAdapter happens to be an class of type WindowListener, which has a method called “windowClosing”, which can be executed if user acts to close the window. 2. To provide this “listener” object, we write a nested class in our program, which extends the class WindowAdapter. (WindowAdapter is an abstract class, so it needs to be extended). 3. Inside our nested class (WindowClsoing) we write an event handling method called windowClosing( ) and we put the code to close the window inside that method.

Page 49: E_Book_JavaDataStructure_SatishSinghal

Abstract Classes and inheritance in Java Page 16 of 16

• Operating system fires, an object of type WindowEvent to tell the program that user has pressed the mouse or pressed keystrokes to close the window.

• The listener which is an object of type WindowClosing ( also of type WindowAdapter and WindowListener – due to inheritance relationships), gets the message and executes the code with in its method windowClosing( ).

• The code with in the windowClosing( ) method executes and closes the window. The method windowClosing is called an event handler.

For procedural programmers it is daunting to see that our program has no explicit call to the method windowClosing ( ), which has the code for closing the window in our program. But this is the key behind asynchronous programming. Since program cannot anticipate as to when user action will take place, it must be on the stand bye to execute in response to user actions any time such actions are born. This is exactly what is accomplished by the event listeners and their event handling methods.

Page 50: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 1 of 18

Topic 3: Interfaces in Java Author: Satish Singhal Ph. D.

Version 1.0 The fundamental unit of programming in java is a class, but in object oriented programming, the fundamental unit is a reference type. Interfaces are a way to define reference types with out defining a class. This adds to Java’s power of object-oriented programming. Interfaces define reference types in abstract form as a collection of method headers and static constants. Interface contains no implementation of methods; therefore, we can say that an interface is an expression of pure design. On the other hand, the classes are mixture of design and implementation. The classes that implement interface are agreeing to a design contract enforced by the interface method headers and static constants. The method names inside the interface are “holy”. They cannot be changed by the implementing subclasses. This is crucial in Graphical Interface Programming in Windows, where when the user clicks on an icon, or presses a key on the keyboard, the Operating System fires an event object to the program and then in order to respond to the user action, the program must execute a certain method. There is a contract with the operating system that if “you send me an object name X, I will execute the method name Y”. This contract cannot be violated. Implementation of methods inside the interfaces enforces this contract. Interface can be implemented by a class or extended by another interface. The class implementing interface can implement the inherited abstract methods in anyway in which the designer of the class chooses to do it. The syntax of writing an interface is as follows: The class implementing a java interface uses keyword implements to implement it. Syntax of implementing a java interface is given below.

public interface SomeInterface { /*Can contain zero or more public abstract methods and static final fields. Cannot contain private or protected fields or methods. Cannot contain instance or class variables.*/ }

Page 51: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 2 of 18

Interfaces can have inheritance relationships between themselves. An interface can be “extended” from one or more interfaces. The syntax is as follows: …… Note that keyword “extends” is used when an interface extends another interface.

public class SomeClass implements SomeInterface {

/*Inherits all the methods and static constants from SomeInterface. Must provide implementation (code) for all the inherited methods else be declared as an abstract class.*/

}

Public interface SomeInterface extends Interface1 { } public interface SomeInterface extends Interface1, Interface2 { }

Page 52: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 3 of 18

We show an example of writing a java interface and its implementation in Listing 3.1 below. public interface Door { public void open( ); public void close( ); } public class CarDoor implements Door { public void open( ) { System.out.println ( "Enter the car" ); } public void close( ) { System.out.println ( "Look out, closing the door"); } } public class TestDoor { public static void main( String[ ] args ) { Door Instance1 = new CarDoor( ); Instance1.open( ); CarDoor Instance2 = new CarDoor( ); Instance2.open( ); } } Listing 3.1 Multiple Inheritance Using Java Interfaces Java interfaces can provide a limited form of multiple inheritance, in the sense that more than one reference types can represent any class that implements more than one interface. Figure 3.1 below shows the most difficult case of multiple inheritance, which is also called the “diamond” multiple inheritance.

Only abstract methods!!!

Provides implementation for all the methods inherited from interface Door!

A reference of type Door can point to an object of type CarDoor because CarDoor is-a Door.

Page 53: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 4 of 18

Person

Student Voter

StudentVoter

FIG. 3.1 C++ allows classes to be derived as a result of multiple inheritance, therefore in C++ all, Person, Student, Voter and StudentVoter can be classes. The class StudentVoter may inherit the fields and virtual functions from all of its super classes. This at times may cause ambiguities and special care is needed to remove them. Java however, would not allow the inheritance shown in Figure 3.1 if all the entities shown are coded as classes. One allowable scenario in java is that entities Person and Voter are declared as interfaces, and then Student and StudentVoter can be declared as classes (Figure 3.2).

Page 54: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 5 of 18

Student

StudentVoter

Person

Voter

FIG 3.2 The advantage of such multiple inheritance is that an object of StudentVoter Class may be represented by a reference of any of its three classes – one of the key advantage of multiple inheritance. Limitation however is that unlike C++ in java, Person and Voter being interfaces, they cannot contain any protected fields. Java in this sense only allows “behavioral” multiple inheritance. Codes for classes and interfaces in Figure 3.2 are shown in Listing 3.2. The UML diagram for Figure 3.2 is shown in the Figure 3.3.

In java, diamond inheritance as this Figure is possible by making entities Person and Voter as Interfaces.

Page 55: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 6 of 18

StudentVoter Shown on next page!

Page 56: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 7 of 18

FIG. 3.3 As shown in the UML diagram, we put two methods in the interface Person getName( ) and getAge( ). These methods relate to the fact that each person will have an age or name. More methods may be added in the Person interface, but for the illustration purpose two methods include would suffice. The coded person interface is shown below in the Listing 3.2 Part A. public interface Person { String getName( ); int getAge( ); } Listing 3.2 Part A Interface Voter extends interface Person. That means that the methods in the Person interface are also automatically inherited by the interface Person. We however, include some static constant strings in the Voter interface to hard code the possible party a student voter may be member of. We also include a method getParty( ) in the Voter interface, which returns the party of the student voter as a string. The coded Voter interface is shown in the Listing 3.2 Part B.

Page 57: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 8 of 18

public interface Voter extends Person { String PARTY1 = "Republican"; String PARTY2 = "Democrat"; String PARTY3 = "Liberatarian"; String PARTY4 = "Independent"; String PARTY5 = "None"; String getParty( ); } Listing 3.2 Part B After coding the two interfaces, now we can code the necessary classes. First, we code the class Student, which derives from interface Person. Class students has name, age and gpa as fields, and we have shown all the necessary chained constructors. Since Person interface is implemented by the class Student, we provide the implementation of the methods getAge( ) and getName( ). We also code some helper methods such as getGpa( ), and toString( ). The method getGpa( ) is unique to class student, as only a person, who is a student can have gpa. The toString ( ) method overrides the corresponding method in the Object class. The code for the class Student is shown in the Listing 3.2 Part C. public class Student implements Person { protected String Name; protected int age; protected double gpa; //Chained constructors public Student() { this(""); } public Student(String Init_Name) { this(Init_Name,0); } public Student(String Init_Name, int init_age) { this(Init_Name, init_age, 0.0); }

Page 58: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 9 of 18

public Student(String Init_Name, int init_age, double init_gpa) { this.Name = Init_Name; this.age = init_age; this.gpa = init_gpa; } //Implementation of inherited abstract methods public String getName() { return this.Name; } public int getAge() { return this.age; } //Other helper methods public double getGpa() { return this.gpa; } public String toString() { String Str = "The name is = " + this.Name + "\n"; Str += "The age = " + this.age +"\n"; Str+= "The GPA = " + this.gpa + "\n"; return Str; } } Listing 3.2 Part C The class StudentVoter extends Student and implements interface Voter. Note that since class Student already implemented the abstract methods from Person class, the class StudentVoter need not do that. This, in spite of the fact that interface Voter extends the interface Person and include Person methods in it. The general rule that applies here is that if any of the super-classes implement the methods of an interface, they are already available to all sub-classes. However, the StudentVoter must implement the abstract method getParty( ) in the Voter interface. The StudentVoter class introduces two new fields, the date of last vote(lastvote) and Party of the StudentVoter( Party). It because the StudentVoter inherits all the fields from the Student class, it in effect, has five fields and needs six chained constructors. It has helper methods such as getLastVote( ) and over-rides the toString( ) method from object class. Listing 3.2 Part D shows the code for the class StudentVoter.

Page 59: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 10 of 18

public class StudentVoter extends Student implements Voter { private int lastvote; //records the year of last vote 0 for new voter private String Party; //Constructors public StudentVoter() { this(""); } public StudentVoter(String Init_Name) { this(Init_Name,0); } public StudentVoter(String Init_Name, int init_age) { this(Init_Name, init_age, 0.0); } public StudentVoter(String Init_Name, int init_age, double init_gpa) { this(Init_Name, init_age,init_gpa,0); } public StudentVoter(String Init_Name, int init_age, double init_gpa, int init_lastvote) { this(Init_Name, init_age,init_gpa,init_lastvote,""); } public StudentVoter(String Init_Name, int init_age, double init_gpa, int init_lastvote, String Init_Party) { super(Init_Name, init_age,init_gpa); this.lastvote = init_lastvote; if(Init_Party.equals(StudentVoter.PARTY1)) this.Party = PARTY1; else if(Init_Party.equals(StudentVoter.PARTY2)) this.Party = PARTY2; else if(Init_Party.equals(StudentVoter.PARTY3)) this.Party = PARTY3; else if(Init_Party.equals(StudentVoter.PARTY4)) this.Party = PARTY4; else if(Init_Party.equals(StudentVoter.PARTY5)) this.Party = PARTY5; else { System.out.println("Bad party name. Setting party to none."); this.Party = PARTY5;

Page 60: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 11 of 18

} } //Implementation of inherited abstract methods //Only Voter interface methods need be implemented //Since the Person interface methods are already implemented by the //Super class Student public String getParty() { return this.Party; } //Helper methods public String toString( ) { String Str = super.toString(); Str += "The year of last vote = " + this.lastvote + "\n"; Str += "The party affiliation is = " + this.Party + "\n"; return Str; } public int getLastVote( ) { return this.lastvote; } } Listing 3.2 Part D The class TestDiamond is written to test the diamond inheritance formed by the interfaces and classes shown in Figure 3.2 and 3.3. We create three objects two of type StudentVoter and one of type Student. The references used to point to them are StudentVoter, Student and Person. In creation of all objects we call the constructor with largest number of arguments for that class. The object STVoter1 is of type StudentVoter. It can be printed directly by the System.out.println ( ) because the method toString ( ) of StudentVoter class is overrides the object class toString ( ) method to correctly print the StudentVoter objects. The methods getName ( ), getAge ( ), getGpa ( ), getParty ( ) are easily called using STVoter1 because STVoter1 is a StudentVoter Type reference pointing to a StudentVoter type object. Next we create a Person type reference Person_Gen which points to a Student object. This is now polymorphism in play, as one can use a Person type reference to point to Student or StudentVoter object. The Person_Gen reference being a Person type can only be used to call the methods of Person interface on the object of type Student. Therefore one can only call methods getName ( ) and getAge ( ). Attempt to call method getGpa ( ) will cause compile error, but if Person_Gen is cast into a reference of type Student, then a call to getGpa ( ) method can be made easily. We assign an additional reference to object being pointed to by Person_Gen. This additional reference is Student type called Stu1. Then we go ahead and use Person_Gen reference to point to a new object of StudentVoter type (name field for

Page 61: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 12 of 18

this object being “Barney Bush”). This is possible because a StudentVoter is also a Person type. public class TestDiamond { public static void main(String[] args) { StudentVoter STVoter1 = new StudentVoter("Elliot John", 23,3.92,1998,"Republican"); System.out.println(STVoter1); System.out.println("The party of "+ STVoter1.getName() + " is " + STVoter1.getParty( )); System.out.println("The age of "+ STVoter1.getName() + " is " + STVoter1.getAge( )); System.out.println("The year of last vote by "+ STVoter1.getName() + " is " + STVoter1.getLastVote( )); System.out.println("The gpa of "+ STVoter1.getName() + " is " + STVoter1.getGpa( )); //Showing Multiple inheritance behavior //A reference of type Person can point to both Student and //Student Voter Person Person_Gen = new Student("Mary Smith",29,3.0); Person Stu1 = Person_Gen; System.out.println("\n" + Person_Gen); System.out.println("The age of "+ Person_Gen.getName() + " is " + Person_Gen.getAge( )); /*uncommneting the code below will cause compile error Because getGpa cannot be called with Person Type reference Person_Gen must be cast into a Student type reference.*/ /*System.out.println("The gpa of "+ Person_Gen.getName() + " is " + Person_Gen.getGpa( ));*/ System.out.println ("The gpa of "+ Person_Gen.getName() + " is " + ((Student)(Person_Gen)).getGpa( )); Person_Gen = new StudentVoter("Barney Bush",45,3.99,1996,"Democrat"); System.out.println("\n" + Person_Gen); Person [ ] Array = {STVoter1,Stu1,Person_Gen}; //Calling the polymorphic sort method selectionSort(Array); //Print the sorted array System.out.println ("Printing the sorted array - sorted based on name."); for (int ind=0; ind<Array.length; ind++) System.out.println (Array [ind]); }

StudentVoter type object being pointed to by same type reference.

Page 62: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 13 of 18

/**The method selection sort accepts an array of type * person and sorts the array alphabetically based on name.Reference * to sorted array can be used to print the array in the caller method */ public static void selectionSort(Person [ ] list) { Person largest = null; int length = list.length; int passCount ; int L_Index ; int S_Index; for ( passCount = 0 ; passCount < length ; passCount++ ) { largest = list[0]; L_Index = 0; //Find the index of largest for ( S_Index=0; S_Index < (length - passCount) ; S_Index++ ) { if((largest.getName()).compareTo(list[S_Index].getName())<0) { L_Index = S_Index; largest = list [S_Index]; } } list [ L_Index ] = list [ length - (passCount+1) ] ; //swap now list [ length - (passCount+1) ] = largest ; } } } Listing 3.2 Part E Polymorphic Sorting Method The power of polymorphism and multiple inheritance shown in Figure 3.2 can be used by writing polymorphic methods. One such method is selectionSort (listing 3.2 Part E) which takes an array of Person type as an argument and sorts the array alphabetically based on first name. Notice that the beauty of this method is that Person array passed to it may have all elements as

• Student Type • StudentVoter Type • Mixture of Student and StudentVoter both.

Still, the method will sort the array correctly based on the name in alphabetical order. The results of test run based on Listing 3.2 Part E (TestDiamond class) are shown in Figure 3.4. We see that all class objects represented by their respective objects behave as expected. Next we create an array of type Person, and put three objects in it, which are respectively, of type StudentVoter, Student, and StudentVoter. Their references are mixed type. The first one has a reference of type StudentVoter (STVoter1), the second one of type Student (Stu1) and third of type

Page 63: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 14 of 18

Person (Person_Gen). Person array can hold any of the reference types shown in Figure 3.2.

FIG. 3.4 The method selectionSort is a standard sorting method, which works based on a well-known selection sort principle of array sorting technology. The method looks for the object, which has the largest member (in this case the name) and bubbles it to the end of the array. The maximum number of passes required to sort the array are one less than the array size. We can see very clearly from the results that the polymorphic method selectionSort works nicely as it takes an array of mixed Student and StudentVoter type objects and sorts them in the alphabetical order by the name.

The polymorphic method selectionSort sorts the Person type array alphabetically.

Page 64: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 15 of 18

Nested Interfaces Java allows local1 definitions of classes but not of interfaces. For example, a code of type illustrated by Listing 3.3 is allowed. Listing 3.3

1 Local definition means defining with in a stand-alone block, constructor, or a method.

public class LocalClasses { { class Class1 { } } static { class Class2 { } } public LocalClasses( ) { class Class3 { } } public static void main(String[ ] args) { class Class4 { } } }

Class defined within a stand-alone non-static block.

Class defined within a stand-alone static block.

Class defined within a constructor.

Class defined within a static method. Same way the classes can be defined within non-static methods as well.

Page 65: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 16 of 18

Java would not allow the interfaces to be defined locally, the way local classes have been defined in the Listing 3.3. Java does however allow one to define nested interfaces. A nested interface means that an interface may be defined as a nested member of a class or of another interface. For example, an interface definition like the Listing 3.4 is allowed.

Listing 3.4

The nested interface (in this case NestedInterface1) may use the modifiers public, protected, private and static as needed. Static and public nested interface acts like a stand-alone interface, but the enclosing class defines its name and accessibility. A class cannot implement the interface it encloses within itself. However, other classes can implement an interface nested inside another class. For example, a code similar to the Listing 3.5 is allowed. Listing 3.5 Also, note that the Class1 could implement the nested interface NestedInterface1 because it was not defined private. Privately defined nested interfaces can only be implemented with in their member class. For example, a code similar to Listing 3.6 is allowed.

public class EncloseInterface { interface NestedInterface1 { } }

public class EncloseInterface { interface NestedInterface1 { } } class Class1 implements EncloseInterface.NestedInterface1 { }

Note that to access the nested interface it must be qualified with the class, which encloses it.

Page 66: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 17 of 18

Listing 3.6 Interfaces can nest other interfaces and classes as well. The members of nested interfaces are also all public. A class that implements an enclosing ( or outer interface is only responsible for implementing the methods in the enclosing interface. For example, in Listing 3.7, the class MyClass only need to implement method1 ( ). It is not responsible to implement method2 ( ). On the other hand the class TheirClass, which implements both, the nested and outer interface, must implement both method1 ( ) and method2 ( ).

public class EncloseInterface { private interface NestedInterface1 { } { class someclass implements NestedInterface1 { } } }

Private nested interfaces are only visible within their member class.

Page 67: E_Book_JavaDataStructure_SatishSinghal

Java Interfaces Page 18 of 18

Listing 3.7

public interface InterfaceNesting { void method1( ); interface NestedInterface2 { void method2( ); } class ClassInInterface { } } class MyClass implements InterfaceNesting { public void method1( ){ } } class YourClass implements InterfaceNesting.NestedInterface2 { public void method2( ){ } } class TheirClass extends InterfaceNesting.ClassInInterface implements InterfaceNesting.NestedInterface2, InterfaceNesting { public void method1( ){ } public void method2( ){ } }

Only responsible to implement the method1( ).

Only responsible to implement method2 ( )

Must implement both method1 ( ) and method2 ( )

Page 68: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 1 of 28

CS 20 B : Java Data Structures

Topic 5 : Stacks Author: Satish Singhal Ph. D.

Version 1.0

You are already familiar with some of the data structures provided by Java. For example, Array is one kind of data structure, which can be used to store large amount of data in the program memory. The goal of certain type of data structure provided by a language is to facilitate certain type of operations on data during the course of use of a program. Using the data structure designed for a programming protocol fulfills that protocol easier. Take the example of arrays. If our programming protocol requires that:

• We have random access to the elements of a data structure • We should be able to swap two members of the data structure

easily • Data structure knows its capacity in terms of maximum elements

it can hold • We can iterate through the data structure sequentially if we like

Then all the above protocols boil down to the design of array data structure, which we use in java. Programming technology requires other protocols, depending upon the type of problem being solved by the software to be developed. For example, there are situations where program requires that we use a data structure, which fulfills the following protocols:

• Elements added to the data structure must only be added through one end. This means that element added most recently will be on the front of the data structure.

• Elements must also be removed from the data structure from the end that was used to add them. This means that items are removed in a manner that most recently added item will be removed first, and the item before that next and so on.

• All elements are same type, which means that the data structure is homogeneous in its membership.

These protocols have been reduced to a simple description called “Last In First Out” or LIFO. Therefore a condensed definition of a Stack is that it is a data structure in which elements are added and removed from only one end; and it behaves like a “last in, first out”(LIFO) structure. Figure 5.1 below shows some physical models of a stack.

Page 69: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 2 of 28

FIG 5.1 All items shown in the Figure 5.1 illustrate a common principle called “stack”. When cafeteria trays are “stacked”, the stack follows all the protocols described on the previous page. A stack of pennies, shoeboxes, folded shirts, or books will behave the same way. Looking from another perspective, a stack may be considered an “ordered” group of items because the elements occur in sequence according to the total time of their membership in the stack. The items that have been in the stack longest are at the bottom, whereas the recent items are on the top. Logical Operational Design Details For a Stack The above protocol description only gives us a logical picture of what a stack is and what are its most important characteristics. Now we need to design the operations on a stack data structure, which will help us use the stack and perform operations on it. Here is kind of a laundry list of operations one may need to perform on a stack:

• Add elements into a stack. This operation is commonly called “push”.

• Remove elements from a stack. This operation is commonly called “pop”.

• Peek at the top element of the stack and return a copy of it, with out removing it from the stack. This operation is commonly called “top” or “peek”.

Page 70: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 3 of 28

• Find out if stack is empty or not. We can call this operation “isEmpty”.

• In a stack that has fixed capacity, find out if stack is full or not. We can call this operation “isFull”.

• Notice that some of the operations we perform on the stack change it and some other do not. The operations that change the data structure are called “mutators” or “transformers”. The operations that do not change data structure but simple observe if and convey some information about it is called “observers”. Question: In above list which operations are transformers and which ones are observers? In order to design the stack to be used as a data structure in a java program, we may write it as a class called stack, and each of the operation performed on the stack may be coded as a member method of the class stack. For example we may have a method called push ( ) which when called by a stack type object will push an object of that type on the stack. Conversely when an object of type stack invokes the method pop ( ) the top element from the stack is removed and returned. The operations push and pop as performed by instance method calls may be illustrated by the Figure 5.2 below.

Page 71: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 4 of 28

FIG 5.2 The method push( ) adds the data members to the stack in the order in which method calls are made. And the method pop( ) removes them in accordance with the LIFO protocol. Now knowing these operational details we can go a step further in the logical design of our stack data structure. During the discussions of java interfaces, we indicated that the interfaces are expression of pure design (or at least the expression of pure “behavioral” design). We may in the process of writing a functional stack class consider writing an interface which will capture the entire essence of the “behavior” of a stack, and then have the stack class implement this interface. Why is the use of interface important? Because by designing the interface that governs the behavior of a stack data structure, we can communicate to the code writer very precisely as to what our design specifications are. The Listing 5.1 below gives us the code for the StackInterface, which the code writer for the class Stack can implement.

Page 72: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 5 of 28

/** Interface for a class that implements a stack of Objects. A stack is a last-in, first-out structure*/ public interface StackInterface { public void push (Object item) throws StackOverflowException; /** Effect: Adds item to the top of this stack Postconditions: If (this stack is full) an unchecked exception that communicates 'push on stack full' is thrown else item is at the top of this stack*/ public void pop( ) throws StackUnderflowException; /**Effect: Removes top item from this stack Postconditions: If (this stack is empty) an unchecked exception that communicates 'pop on stack empty' is thrown else top element has been removed from this stack*/ public Object top( ) throws StackUnderflowException; /**Effect: Returns a reference to the element on top of this stack Postconditions: If (this stack is empty) an unchecked exception that communicates 'top on stack empty' is thrown else return value = (top element of this stack)*/ public boolean isEmpty(); /**Effect: Determines whether this stack is empty Postcondition: Return value = (this stack is empty)*/ public boolean isFull(); /** Effect: Determines whether this stack is full Postcondition: Return value = (stack is full)*/ } Listing 5.1 From the Listing 5.1 one would notice that we handle the situation where the stack is empty and the method pop is invoked, by throwing an exception StackUnderflowException. Similarly if a push ( ) or top ( ) operation is

Page 73: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 6 of 28

performed on a stack that is already full then an exception StackOverflowException must also be thrown. The code designer would need the specifications for the above exception classes as well. For each method in the StackInterface, we describe the followings:

• Effect: This item describes as to what effect the method is going to create ( if any ) on the stack data structure.

• Postcondition: An assertion that must be true after the method has been called.

The descriptions also indicate that two exception classes needed are “unchecked exceptions”. That means that the programmer need not catch these exceptions explicitly. Unchecked exception classes are written by extending java RuntimeException class. Therefore, the codes for Exception classes StackUnderflowException and StackOverflowException are written as follows (Listing 5.2 part A and B). You may also wish to make them unchecked exception for the reason because it is not a good idea to force the client to write try and catch blocks in their programs – unless it is necessary. Client can however, catch an unchecked exception if they wish to do so. public class StackUnderflowException extends RuntimeException { public StackUnderflowException( ) { this (“”); } public StackUnderflowException(String message) { super (message); } } Listing 5.2 Part A public class StackOverflowException extends RuntimeException {

Calls the constructor for the RuntimeException class.

Page 74: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 7 of 28

public StackOverflowException( ) { this (“”); } public StackOverflowException(String message) { super(message); } } Listing 5.2 Part B The code for the array based stack class, which adheres to the design of the interface of Listing 5.1 and uses the exception classes of 5.2, is shown below in the Listing 5.3. public class ArrayStack implements StackInterface { private Object[] stack; // Array that holds stack elements private int topIndex = -1; // index of top element in stack // Constructors public ArrayStack( ) { this(100); } public ArrayStack(int maxSize) { stack = new Object[maxSize]; } public void push(Object item) // Adds an element to the top of this stack { if (!isFull( )) { topIndex++; stack [topIndex] = item; } else throw new StackOverflowException ("Push attempted on a full stack."); } public void pop() // Removes an element from the top of this stack {

Creates a stack of size 100, unless user calls the constructor with explicit size.

Page 75: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 8 of 28

if (!isEmpty()) { stack [topIndex] = null; topIndex--; } else throw new StackUnderflowException ("Pop attempted on an empty stack."); } public Object top() // Returns the element on top of this stack { Object topOfStack = null; if (!isEmpty()) topOfStack = stack[topIndex]; else throw new StackUnderflowException("Top attempted on an empty stack."); return topOfStack; } public boolean isEmpty() // Checks if this stack is empty { if (topIndex == -1) return true; else return false; } public boolean isFull() // Checks if this stack is full { if (topIndex == (stack.length - 1)) return true; else return false; } } Listing 5.3 A simple test class to test the ArrayStack class is shown in the Listing 5.4. Figure 5.3 shows the test results from Listing 5.4. public class TestArrayStack {

Page 76: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 9 of 28

public static void main(String [ ] args) { ArrayStack Stk1 = new ArrayStack(35); Stk1.push(new Integer(55)); Stk1.push(new String("Frank Elliot")); Stk1.push(new Double(99.992)); Stk1.push(new Character('T')); System.out.println (Stk1.top( )); Stk1.pop( ); System.out.println(Stk1.top( )); } } Listing 5.4

FIG. 5.3 From the Figure 5.3 one would notice that the last element pushed on the stack was character ‘T’. Therefore a call to method top ( ) shows the element T. Then we call the method pop ( ) which discards the element T. The call to method top ( ) then prints the current top element on the stack 9.992. Stack Applications #1 : Compiler/Debugger Operation Stacks have many applications. Compilers use a function call stack to keep track of calls during the debug operation when the debugger is invoked. For example, consider the following C++ program (Listing 5.5). (You do not have to know C++ in order to understand this application, but it may help). #include <iostream> using namespace std; void function1(); void function2(); void function3(); void function4(); void function5(); void main() { function1(); }

Page 77: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 10 of 28

void function1( ) { cout<<"In function1\n"; function2(); } void function2( ) { cout<<"In function2\n"; function3(); } void function3( ) { cout<<"In function3\n"; function4(); } void function4( ) { cout<<"In function4\n"; function5(); } void function5( ) { cout<<"In function5\n"; } Listing 5.5 The program has a main function and five other C++ functions called function1 ( ), function2 ( ) etc. The key thing is that main function calls the function1 ( ), then function1 ( ) calls the function 2( ) , function 2 ( ) calls function 3 ( ) and so on. How does compiler keeps track as to where to return when one function call completes itself? Well it builds a stack structure as it piles up the locations where it was in each function. When the compiler is executing the function 5, the function call stack looks like the given figure 5.4.

Page 78: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 11 of 28

FIG. 5.4 In Figure 5.4 the yellow arrow shows as to which line compiler will execute, when programmer moves execution forward (This is done by pressing F10 or F11 as the case may be). The call stack also shows as to where compiler is. Figures 5.5 A to E show as to how the call stack is popped by the compiler as the execution progresses beyond the state shown in Figure 5.4.

Function call stack. Debugger is right now at line 39 of program in function function5( ). When it starts to pop the stack, it will go to line 35 in function4 ( ) and then to function 3( ) and so on.

Compiler executing line in function5 ( )

Page 79: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 12 of 28

FIG. 5.5 A

FIG. 5.5 B

Call to function5 ( ) is popped off the stack. Control returns to function4( )

Call to function4 ( ) is popped off the stack. Control returns to function3( )

Page 80: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 13 of 28

FIG. 5.5 C

Fig. 5.5 D

Call to function3 ( ) is popped off the stack. Control returns to function2( )

Call to function2 ( ) is popped off the stack. Control returns to function1( )

Page 81: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 14 of 28

Fig. 5.5 E We see that compiler keeps track of as where to return in the program structure by looking at the function call stack it built, and then popping that stack as the calls to each function is completed. As execution of a certain function completes, it is taken off the stack and the control returns to the next function on the stack. A similar process is followed when a call stack is built up during recursion. We shall show this when we discuss recursion. Stack Applications #2 : Conversion From Infix Expression to Postfix form: Most people learn the algebraic expression in what is called “infix” form. When infix form is used, the binary operators such as +, -, *, and / are applied to two operands on either side of the operator. For example in expression ( a + b), the operator + is located in between the operands a and b, thus such expressions are called infix expressions. There are computers and calculators, however, which use a somewhat different way to apply binary operators to the operands. This form is called a “postfix” form. In postfix, form the operator is placed after the operands. For example the postfix form for ( a + b ) will be: a b + The advantage of postfix form is that one does not need to remember the operator precedence rules and unlike infix form, no parentheses are needed. Example of Evaluation of a Postfix Expression A POSTFIX expression such as: 5 7 + 6 2 - * will be evaluated as follows: We scan from left to right, until we hit an operator. Then we take two operands before the operator just found and apply the operator to those two. Keep this

Call to function1 ( ) is popped off the stack. Control returns to main ( ).

Page 82: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 15 of 28

process going until all the operators are consumed. For example in the above expression, scanning from left to right we hit operator '+' first. The two operands before this '+' operator are 5 and 7. This is interpreted as 5 + 7 = 12, then our expression becomes: 12 6 2 - * . Then we scan again from left to right. This time we hit a '-' sign. The two operands before '-' are 6 and 2. This is evaluated as 6-2 = 4. This changes our expression to 12 4 * . Scanning left to right once more we hit '*' - the multiplication sign. Thus 12*4 is evaluated to 48, the final value of the POSTFIX expression 5 7 + 6 2 - *. Using a Stack to Evaluate Postfix Expression A stack can be used to evaluate a postfix expression. In fact the stack's Last In First Out(LIFO) property fits extremely well to the process of such evaluation. This works as follows: The stack is used to store each operand, until we read a POSTFIX operator like (+, -, * or /). As soon as we read an operator we pop 2 operands from the stack and apply the Operator to the two Popped operands as follows: Operand(2nd Pop)<Operator>Operand(1st Pop). This is shown further in the example given below. The algorithm for the evaluation of the arithmetical POSTFIX expression is clearly demonstrated by the following example using the stacks data structure. Let us revisit our example of a POSTFIX arithmetical expression such as: 5 7 + 6 2 - * = ? We scan from left to right and we find number ‘5’. We push this number into the stack as shown in the figure 5.6 A:

5 FIG. 5.6 A

Our topindex pointer is at point just after number 5. We show the location of the pointer by an apostrophe as follows: 5’ 7 + 6 2 - * = ? Then we move the pointer further and we find and read number 7. We push ‘7’ also on to the stack. The pointer and stacks now look as follows: 5 7’ + 6 2 - * = ?

57

FIG. 5.6 B

Now we move the pointer to the right again to read the next character. This character is a binary arithmetical operator ‘+’. We now pop two operands ‘7’ and ‘5’ from the stack and apply the operator ‘+’ to them as follows: We add 5 to 7 and push the result back in the

Stack

topindex location shown by using ‘

Page 83: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 16 of 28

5

First Pop Second Pop Push Resultback IntoStack

12

Apply thebinary Operator

5 + 7 = 12+ 7

FIG. 5.6 C

stack as shown in the figure 5.6C. Our pointer now is placed after the binary operator ‘+’ as follows: 5 7 +’ 6 2 - * = ? Then we move the pointer once again to the right, and we read the number 6. We push the ‘6’ on to the stack as given by the figure 5.6 D:

12

6

FIG. 5.6 D

Our pointer is now placed after the number ‘6’ as follows: 5 7 + 6’ 2 - * = ? Then we start reading again and this time we read number ‘2’. We push number ‘2’ on to the stack, as shown by the figure 5.6 E:

1262

FIG. 5.6 E

Our pointer is now placed just after the number ‘2’ as follows: 5 7 + 6 2’ - * = ? As we resume reading the next we read is the operator ‘-’. At this point we pop ‘2’ and ‘6’ from the stack and apply the binary subtraction to them as shown in the figure 5.6 F:

First Pop Second Pop Push Resultback IntoStack

12

Apply thebinary Operator

126

2 12 6 - 2 = 44

-

FIG. 5.6 F

Our pointer now is placed just after the subtraction operator ‘-’ as follows:

Page 84: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 17 of 28

5 7 + 6 2 -’ * = ? As we read our final binary operator ‘*’ from the input expression, we pop the two values 12 and 4 from the stack and apply the binary ‘*’ multiplication operator as shown in the figure 5.6 G:

124

First PopOperation

12 4* = 48

Second PopOperation

If Stack is Emptyoutput the result

cout

*

FIG. 5.6 G

The entire expression has now been read, and stack is empty, so the result ‘48’ is outputted to the screen or any other relevant output device. We may write our POSTFIX equation as follows: 5 7 + 6 2 - * = 48. The simple rule to remember here is that as we pop a value to apply a binary operator, we put the operator in front of the first popped value, and then we pop the second value and put it in the front of the binary operator. Using a Stack to Convert From Infix form to Postfix Form Very often for the evaluation of a complex infix expression, it is easier to convert it into POSTFIX form, and then apply the algorithm discussed above for its evaluation. Now we discuss an algorithm to convert an alphanumeric INFIX expression to the POSTFIX form. First, let us enumerate the rules of the conversion algorithm as follows: Repeat The Following steps and Rules. Determine if the next input is an operand, an operator, a left parenthesis, or a right parenthesis. Rule 1. If the next input is a left parenthesis, i. e. a ‘(‘, then push it into the stack. Rule 2. else If the next input is an operand then output the operand or store for later output. Rule 3. else If the next input is an operator then; Pop all operators off the stack until you reach a ‘(‘ ; OR the stack is empty: OR you reach an operator with a lower precedence than the operator just read. (This lower precedence operator is left in the stack); Then after all above, push the newly read operator in the stack. Rule 4. else If the next input is a right parenthesis, i. e. a ‘)’, then Pop and output all operators off the stack until you reach a corresponding left parenthesis i. e. a ‘(‘, or the stack is empty. Discard the matching left parenthesis “(“ Popped from the stack. If there is no matching left parenthesis is found then the INFIX expression was parenthetically unbalanced. An output message to this affect may be issued. Rule 5. Repeat above until, there is no more input.

Page 85: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 18 of 28

Let us show this algorithm in action by applying to the following expression such as: 3 * X + (Y - 12) - Z. . In this case also we use a stack to store the binary operators as well as left parentheses. But in this case all the operands (3, Y, X etc.) are outputted to the output stream as soon as they are read. This is how the sequence of events will work. 1. Reading from left to write, first we read operand ‘3’. This is outputted and stack is empty. This is shown in the figure 5.7A:

Stack isEmpty

output

3

3 * X + (Y - 12) - Z3

FIG. 5.7 A

The gray color in the figure above shows as to how much of the input expression has been read. 2. We read again to right and next we read the multiplication operator ‘*’. We push this operator into the stack as shown by the figure 5.7B. The output remains unchanged.

output

3

3 * X + (Y - 12) - Z3 *

*

Stack FIG. 5.7 B

3. We read to right again and we read the operand X, which is outputted. The stack remains unchanged. (Figure 5.7 C).

output

3 * X + (Y - 12) - Z3 *

*

Stack

3 X

X

FIG. 5.7 C

Page 86: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 19 of 28

4. Then we read the binary operator ‘+’, which is pushed on the stack, but before we do that, we examine the stack and output any operators that have either the equal or higher precedence than the ‘+’ operator we just read. The output gets the operator ‘*’ (Rule 3 above) as operator ‘+’ gets pushed to the stack, as shown by the figure 5.7D:

output

3 * X + (Y - 12) - Z3 *

*

Stack

3 X

X

+

+

FIG. 5.7 D

5. Then as we resume reading we read the left parenthesis ‘(‘. We push that on to the stack (Rule 1). The output remains unchanged. (Figure 5.7 E).

output

3 *

*

Stack

3 X

X

+

+

(

( Y - 12 ) - Z

FIG. 5.7 E

6. We resume reading and we read the operand Y, which is outputted. The stack remains unchanged. (Figure 5.7 F).

Page 87: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 20 of 28

output

3 *

*

Stack

3 X

X

+

+

(

( Y - 12 ) - Z

Y

FIG. 5.7 F

7. Next we read the binary operator ‘-’. Now according to Rule 3, we examine the contents of stack. Since stack has only left parenthesis on the top, we do not output any thing. Rather we push the binary operator ‘-’ on to the stack. (Remember that the left parenthesis is to be taken out and discarded only when a right parenthesis ‘)’ is read.). The situation is shown by the figure 5.7G:

output

3 *

*

Stack

3 X

X

+

+

(

( Y - 12 ) - Z

Y-

FIG. 5.7 G

8. Then we read the operand ‘12’ which is outputted(Rule 2). (Figure 5.7H).

output

3 *

*

Stack

3 X

X

+

+

(

( Y - 12 ) - Z

Y-

12

FIG. 5.7 H

9. Continuing reading to right, we read the right parenthesis ‘)’. According to Rule 4, we Pop and output the operator ‘-’ from the stack and then we Pop and discard the left parenthesis. The right parenthesis is discarded as well. This is shown in the figure 5.7 I:

Page 88: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 21 of 28

output

3 *

Stack

X

+

+

(

( Y - 12 ) - Z

Trash

3 X * Y 12 -)

FIG. 5.7 I

10. Then we read the binary operator ‘-’. As per Rule 4, we examine the contents of our stack and output any operators of equal or higher precedence (until we reach the bottom or a left parenthesis or an operator of lower precedence). This require Popping and outputting the ‘+’ operator, and then we push the binary operator ‘-’ that was just read, on to the stack. (Figure 5.7J).

output

3 *

Stack

X

+

+

(

( Y - 12 ) - Z

Trash

3 X * Y 12 -

_

)

FIG. 5.7 J

11. We finally read the last operand in our expression ‘Z’. We output it (Rule 2). The stack remains unchanged. (Figure 5.7 K).

output

3 *

Stack

X

+

+

(

( Y - 12 ) - Z

Trash

3 X * Y 12 -

_

Z)

FIG. 5.7 K

12. As a last operation we output any operators left in the stack, so we output the operator ‘-’ from the stack, and stack is now empty, as well as our conversion from

Page 89: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 22 of 28

Infix Form to Postfix Form is complete. (Figure 5.7 L).

output

Stack

+ (

Trash

3 X * Y 12 - _Z

InFix Form

Post Fix FormEmpty

3 * X + (Y-12) - Z

)

FIG. 5.7 L

Java Stack Class Java library has its own class, which is included in the java.util package. The table 5.1 below gives the summary of constructor and methods for Stack class in java library.

Constructor Summary Stack() Creates an empty Stack.

Method Summary boolean empty()

Tests if this stack is empty. Object peek()

Looks at the object at the top of this stack without removing it from the stack.

Object pop() Removes the object at the top of this stack and returns that object as the value of this function.

Object push(Object item) Pushes an item onto the top of this stack.

int search(Object o) Returns the 1-based position where an object is on this stack.

Table 5.1 The method peek ( ) is similar to the method top( ) discussed in Listing 5.3. If stack is empty then methods peek ( ) and pop ( ) will throw an EmptyStackException object. The Stack class in java uses a Vector. Therefore, it avoids the problem of stack being full, since Vector will keep expanding as more elements are added.

Postfix Form

Page 90: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 23 of 28

Methods empty ( ) and push ( ) work exactly similar to the same name methods in Listing 5.3. The method search ( ) looks for an object passed to it in the stack, and if the object is found, then it returns the index of the location of the object. The indexing is done assuming that top element is at index one. If object is not in the stack, then search ( ) returns a value of –1. Program to Convert an Infix Expression to Postfix form Listing 5.6 below gives a java program, which converts an expression from the infix form to postfix form. This program essentially codes the algorithm we described on pages 17 to 22. Postfix expressions are also commonly called “Reverse Polish Notation” or RPN logic. Listing 5.6 gives a class called ReversePolishNotation, which asks user to input an infix expression and converts it to postfix form. We use the java Stack class here in order to perform the necessary stack operations. import java.util.*; import javax.swing.*; public class ReversePolishNotation { String inputString; String outputString = ""; public ReversePolishNotation(String s) { inputString = s; } private int priority(String s) { int operatorPriority = -1; if (s.equals("[")) operatorPriority = 0; else if (s.equals("(")) operatorPriority = 1; else if (s.equals("+")) operatorPriority = 2; else if (s.equals("-")) operatorPriority = 2; else if (s.equals("*")) operatorPriority = 3; else if (s.equals("/")) operatorPriority = 3; else if (s.equals("^")) operatorPriority = 4; return(operatorPriority); }

Page 91: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 24 of 28

private void convert( ) { Stack myStack = new Stack(); String nextCharacter; String topOfStack; for (int i = 0;i<=inputString.length()-1;i++) { nextCharacter = inputString.substring(i,i+1); System.out.println(nextCharacter); if (nextCharacter.equals(")")) { topOfStack = (String)myStack.pop(); while (!topOfStack.equals("(")) { this.outputString+=" "; outputString += topOfStack; topOfStack = (String)myStack.pop(); } } else if (nextCharacter.equals("]")) { topOfStack = (String)myStack.pop(); while (!topOfStack.equals("[")) { this.outputString+=" "; outputString += topOfStack; topOfStack = (String)myStack.pop(); } } else if (nextCharacter.equals("(") || nextCharacter.equals("[")) { myStack.push(nextCharacter); }

Get the next character.

Enforcing rule # 4 on page 17.

Enforcing rule #1 on page 17

Page 92: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 25 of 28

else if (nextCharacter.equals("+") || nextCharacter.equals("-") || nextCharacter.equals("*") || nextCharacter.equals("/") || nextCharacter.equals("^")) { topOfStack = (String)myStack.peek( ); while (priority(nextCharacter) <= priority(topOfStack)) { topOfStack = (String)myStack.pop( ); this.outputString+=" "; outputString += topOfStack; topOfStack = (String)myStack.peek( ); } myStack.push(nextCharacter); } else { this.outputString+=" "; outputString += nextCharacter; } } } public String toString() { return(outputString); } public static void main(String[] args) { String Value = JOptionPane.showInputDialog("Please enter the " +"Expression to be converted into Postfix form.Enclose the expression " + " in square brackets.\n"); Value.trim(); ReversePolishNotation myConverter = new ReversePolishNotation(Value); myConverter.convert(); System.out.println(myConverter); JOptionPane.showMessageDialog(null, "The postfix form of " + Value + " is : " + "\n" + myConverter); System.exit(0); } } Listing 5.6 The algorithm for infix to postfix conversion (pages 17-22) is implemented by class ReversePolishNotation as follows. The method convert gets the user entered infix expression as a string enclosed in a pair of square brackets. The method scans the expression character by character. Then the method invokes a system of nested if-

Enforcing rule #3 on page 17.

Enforcing rule #2 on page 17

Page 93: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 26 of 28

else constructs and various portions of this construct enforce the four rules of infix to postfix conversion listed on page 17. For example, the rule #3, which the most complex is enforced by the method, convert as follows: The code inside the else if clause given below determines if the next character is an operator? Then rule three (page 17) is enforced by determining the priority of the next character in comparison to the priority of the characters (operators, left and right parentheses, and square bracket ) in the stack. The method priority ( ) helps to enforce the rule three as shown in Table 5.2. Operator Priority Right square bracket or ] -1 Right parenthesis or ) -1 Left square bracket or [ 0 Left parenthesis or ( 1 Plus ( + ) 2 Minus ( - ) 2 Multiply ( * ) 3 Divide ( / ) 3 Exponentiation (^) 4 Table 5.2 The loop given below is entered only if the priority assigned to the character read is lower or equal to the character on the top of the stack. Notice that rule three will be enforced only if the next operator is one of the followings: +, -, *, /, ^ . This is because all other situations, where the character is other than mathematical operator are taken care by other rules. Therefore the method priority ( ) will return a value of two if the next character is + or -, a value of three if the next character is *, or /, and a value of four if the next character is an exponentiation ^. The while loop then pop the characters on the stack until an operator of lower precedence is seen. The last line of the code to enforce rule three pushes the next character on to the stack as follows:

else if (nextCharacter.equals("+") || nextCharacter.equals("-") || nextCharacter.equals ("*") || nextCharacter.equals("/") || nextCharacter.equals ("^"))

while (priority(nextCharacter) <= priority(topOfStack)) { topOfStack = (String)myStack.pop( ); this.outputString+=" "; outputString += topOfStack; topOfStack = (String)myStack.peek( ); }

Page 94: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 27 of 28

myStack.push (nextCharacter); Results From Listing 5.6 The main method of Listing 5.6 tests the class ReversePolishNotation by taking the user input by means of a JOptionPane java object. The results of infix to post fix conversion are also presented using a JOptionPane object. The Figure 5.8 shows the input and output pop up screens when the program executes.

FIG. 5.8 A

FIG. 5.8 B

Infix form entered by user.

Postfix form outputted.

Page 95: E_Book_JavaDataStructure_SatishSinghal

Topic 5 Stacks Page 28 of 28

FIG. 5.8 C

FIG. 5.8 D

Page 96: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 1 of 23

CS 20 B : Java Data Structures

Topic 6: FIFO Queue Author: Satish Singhal Ph. D.

Version 1.0 The FIFO queue works very similar to the LIFO stack, except that queue follows a First in first out (FIFO) protocol. There are queue data structures that do not follow FIFO rule. Those are called priority queues. The formal definition of a FIFO queue is given below in Figure 6.1.

FIG. 6.1

One everyday example of a FIFO queue is the line in the stores where people line up to pay for the merchandise they purchase (Figure 6.2).

Page 97: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 2 of 23

FIG. 6.2

Now we analyze the kind of data structure and operations associated with it, which can simulate the queue we encounter many times in our daily experience. Logical Operational Design Details For a Queue To make the protocol for a queue more concrete, we may envision some list of operations that we may perform on a FIFO queue. Figure 6.3 below enumerates some operations that would be needed in a queue data structure.

Page 98: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 3 of 23

• We may also add method such as isFull ( ) which will return a true if queue is full or a false if queue has empty space in it.

------------------------------------------------------------------------------------------------------ FIG. 6.3

Question: Which operations in the Figure 6.3 are transformers and which ones are observers? Queue Interface We communicate our design of a queue data structure by writing the specification for a queue interface. These specifications are shown in the listing 6.1. //---------------------------------------------------------------------------- // QueueInterface.java // Interface for a class that implements a queue of Objects. // A queue is a first-in, first-out structure. //---------------------------------------------------------------------------- public interface QueueInterface { public void enqueue(Object item) throws QueueOverflowException; // Effect: Adds item to the rear of this queue. // Precondition: This queue is not full. // Post-condition: item is at the rear of this queue. public Object dequeue( ) throws QueueUnderflowException; // Effect: Removes front element from this queue and returns it. // Precondition: This queue is not empty. // Post-conditions: Front element has been removed from this queue. // Return value = (the removed element) public boolean isEmpty( ); // Effect: Determines whether this queue is empty. // Post-condition: Return value = (this queue is empty) public boolean isFull( ); // Effect: Determines whether this queue is full. // Post-condition: Return value = (queue is full) } Listing 6.1

Page 99: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 4 of 23

In interface QueueInterface the assumption is that during enqueue operation, an item is added to the rear of the queue, and if queue is already full, then an exception QueueOverflowException will be thrown. The dequeue operation removes one element from the front of the queue and returns it, but if queue is empty then a QueueUnderflowException is thrown. Methods isEmpty ( ) returns a true if queue is empty, whereas the method isFull ( ) returns a true if queue is full. The classes QueueOverflowException and QueueUnderflowException are shown in Listing 6.2. public class QueueOverflowException extends RuntimeException { public QueueOverflowException( ) { this(“”); } public QueueOverflowException(String message) { super(message); } } Listing 6.2 A public class QueueUnderflowException extends RuntimeException { public QueueUnderflowException( ) { this(“”); } public QueueUnderflowException(String message) { super(message); } } Listing 6.2 B As was the case for Stack interface, the exceptions used for queue are also “unchecked” exceptions, which are only implemented at runtime. Array Based Queue Before we implement the design specification contained in the queue interface, we must decide as to how we would use the array for a queue if an array were used to hold the queue elements. The situation is different for queue compared to a stack, because stack had only one point (the top) from which the elements were added or removed. With queue, we must manage two pointers – one for the front and one for the back. The way to implement the queue operation would be that when we add an

Page 100: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 5 of 23

element, we move the rear pointer and when we remove an element we move the front pointer. However, removal of an element from the front would mean that there is an empty spot in the queue. Therefore, entire queue must be moved in order to make use of the empty spot in the front. The Figure 6.4 below shows this situation. In addition, power point slide presentation shows the same thing dynamically.

Microsoft PowerPoint Presentation

FIG 6.4 A

Page 101: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 6 of 23

FIG 6.4 B

FIG 6.4 C

Page 102: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 7 of 23

FIG 6.4 D

FIG 6.4 E

Page 103: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 8 of 23

FIG 6.4 F

FIG 6.4 G

Page 104: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 9 of 23

FIG 6.4 H

FIG 6.4 I

Page 105: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 10 of 23

FIG 6.4 J It is important to realize that the implementation shown in the Figure 6.4 will work, except it would be slow and cumbersome. Later on when we discuss the Big O analysis of algorithms, we would see that the implementation similar to the Figure 6.4 gives a linear algorithm or an algorithm of type O (1), which is very slow. To overcome this limitation we design another implementation where front pointer is not kept fixed at the location zero in the array. Rather we move it as well. In such case, in order to use the empty spots after the dequeue operation, subsequent enqueue operation may start to put elements in empty spots in places which have index lower than the front pointer. Such queue design is called a “Circular Queue”. Circular Queue We use the java applet shown on the following link on Internet to understand the operation of array based circular queue. Circular Queue Java Applet The Figure 6.5 shows the sequence of operation in the circular or wrap around array based queue. First we show the empty queue (Figure 6.5 A).

Page 106: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 11 of 23

FIG. 6.5 A

FIG. 6.5 B

Empty queue. Front pointer at zero. Back pointer is not shown.

Front is fixed at zero. Rear is kept at the empty spot behind the last element added. Note that there may be some difference between various implementations. For example, the Fig. 6.4 keeps the rear pointer at the filled element.

enqueue(A)

Page 107: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 12 of 23

FIG. 6.5 C

FIG. 6.5 D

Filled Queue. The front and rear pointers point to same location.

enqueue (B);

Page 108: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 13 of 23

FIG. 6.5 E

FIG. 6.5 F

As elements are added, the front stays fixed at zero but back pointer keeps moving. Finally, when the queue is full the front and back pointers are at the same location (Figure 6.5 D). As we know that front moves only when dequeue is performed. We remove three elements ( A, B, and C), so then front moves to location 3 (Figure 6.5 E). Now if we perform enqueue operation, then the new element is added at the first empty location zero and the rear pointer is moved to location 1. In wrap around or circular implementation, the rear pointer may be at the index less than the front pointer, but the queue still follows the FIFO protocol precisely. The advantage is that unlike linear implementation, we do not have to move and readjust the entire queue to reuse the empty front space. The wrap around logic is implemented by adjusting the front and rear pointers as follows (Listing 6.1): Initially the rear pointer is set to zero. When the first element is added the rear pointer moves to one and the first element is added to the location zero. Therefore the location of rear pointer can be adjusted by the expression:

Operations done: dequeue( ); dequeue( ); dequeue( );

Three dequeue operations remove three elements. The rear stays fixed but front moves to index 3.

Now the operation enqueue (G) adds the new element to index zero and rear moves to index 1.

Page 109: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 14 of 23

rear = (rear + 1)%Capacity, where the capacity is the maximum number of elements in the array holding the queue. The queue will wrap around as soon as the index of rear is one less than the Capacity, therefore if available, one can fill the element zero and so on. Front pointer wraps around just the way back pointer does. The queue is full when number of items in the queue is equal to its capacity, and the queue is empty when the number of elements is zero. An implementation of the wrap around queue is given by class ArrayQueue as shown in the Listing 6.3. //---------------------------------------------------------------------------- // ArrayQueue.java // // Implements QueueInterface using an array to hold the queue items //---------------------------------------------------------------------------- public class ArrayQueue implements QueueInterface { private Object[] queue; // Array that holds queue elements private int capacity; // size of the array (capacity of the queue) private int numItems = 0; // number of items on the queue private int front = 0; // index of front of queue private int rear = -1; // index of rear of queue private static final int value = 100; // Constructors public ArrayQueue( ) { this ( value); } public ArrayQueue(int maxSize) { queue = new Object[maxSize]; capacity = maxSize; } // Method enqueue adds an element to the rear of this queue public void enqueue (Object item) throws QueueOverflowException { if(isFull( )) throw new QueueOverflowException (“Enqueue attempted on a full queue”); else { rear = (rear+ 1) % capacity; queue[rear] = item; numItems = numItems + 1; } }

Page 110: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 15 of 23

// Removes an element from the front of this queue public Object dequeue( ) throws QueueUnderflowException { if(isEmpty ( ) ) throw new QueueUnderflowException (“Dequeue attempted on an empty “ + “ queue”); else { Object toReturn = queue [front]; queue[front] = null; front= (front+ 1) % capacity; numItems = numItems - 1; return toReturn; } } public boolean isEmpty() // Checks if this queue is empty { return (numItems == 0); } public boolean isFull() // Checks if this queue is full { return (numItems == capacity); } } Listing 6.3 The code to test the queue of Listing 6.3 is given in the Listing 6.4, where we use the class TestQueue for this purpose. public class TestQueue { public static void main( String [ ] args) { ArrayQueue Queue1 = new ArrayQueue( ); Queue1.enqueue(new Integer(55)); Queue1.enqueue("Moshe"); Queue1.enqueue(new Double (99.992)); Queue1.enqueue(new Character('T')); for(int index=0; index<5; index++) System.out.println (Queue1.dequeue()); } }

Listing 6.4

Page 111: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 16 of 23

We add the following elements to the queue in the order 55, “Moshe”,99.992, and ‘T’. Then we perform five dequeue operations. Since there are only four elements in the queue, the last dequeue operation will throw an exception of type QueueUnderflowException. Figure 6.6 shows the screen shot of the output result.

FIG. 6.6 Application of Queue and stack: Finding number of Palindromes in a data file Palindrome is a word which when spells the same front to back or back to front. For example, the name Bob is a palindrome. You might have heard another palindrome in a Chinese restaurant. Won ton? Not now. In finding, whether certain string is a palindrome we ignore the punctuations, spaces, and capitalization. In using the program that would find whether strings contained in a file are palindrome or not we use an input file similar to shown in Figure 6.7 below. A man, a plan, a canal, Panama amanaplanacanalpanama This is not a palindrone! aaaaaaaaaaa a aaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaa aAaaaaaaaabaaaaaaAaaa This string is too long xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxx xxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxx bob dan Madam, I'm Adam. eve Eve FIG. 6.7

dequeued elements.

Exception thrown when last dequeue operation is done on the empty queue.

Page 112: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 17 of 23

Many of the strings such as bob, eve, and Eve in this file are palindromes. The program should read the file and decide as to which strings are palindromes and which ones are not. Since we limit the maximum length of the string to be tested, the program also must determine as to which string is too long to be processed. The results for each string must be printed to an output file. Figure 6.8 shows the principle behind the program. If a string is a palindrome and we push it character by character onto a stack and enqueue all its characters in a queue, then as we pop the stack and dequeue the queue, we can compare each popped and dequeued character. If all characters processed in this manner turn out equal to each other then the string is a palindrome. Let us assume that our string is “won ton not now”. If we scan from left to right then the Figure 6.8A shows the characters pushed onto a stack.

“Won ton not now” on a stackW

O

N

T

O

N

N

O

T

N

O

w FIG. 6.8 A The Figure 6.8B shows the same string entered into a queue character by character.

Page 113: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 18 of 23

“Won ton not now” enqueued

W O N T O N N O T N O W

Front Rear

FIG 6.8B One can see that if one pops the stack of Figure 6.8A and dequeues the queue of Figure 6.8B, and compares the characters obtained, then if they are all the same, then the string is a palindrome. This principle has been extended in the form of a program in the Listing 6.5. The main algorithm for the program is given below in the Figure 6.9.

Main Algorithm Initialize expression counts Read the first input line While there are still lines to process Increment the total number of strings Echo print the strings to the output file If the string is longer than the maximum length allowed Increment the number of oversize string count Write “String too long” to the output file Else Process the current string //See algorithm in Figure 6.8 If the string is not a palindrome Increase the count of non-palindrome Write “Not a Palindrome” to the output file Else Increase the count of palindromes Write, “Is a Palindrome” to the output file Write the summary information to the output frame FIG. 6.9

Page 114: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 19 of 23

The code to process a string requires the use of queue and stack data structures. The algorithm for that is given in the Figure 6.10 below. Listing 6.5 below shows the code for the class Palindrome, which when run takes the input and output file name on the command line. As indicated before the program reads the strings from the input file and outputs them to an output file indicating whether the string is a palindrome or not, or too long to process. //---------------------------------------------------------------------------- // Palindrome.java // // Checks for palindromes // Input file consists of a sequence of strings, one per line // Output file contains, for each string: // Whether or not the string is a palindrome ... blanks are ignored // Input and output file names are supplied by user through command line parameters. // Output frame supplies summary statistics //---------------------------------------------------------------------------- import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.io.*; import java.text.DecimalFormat;

Process the current string Create a new stack Create a new queue For each character in the string If the character is a letter Change the character to a lower case Push the character onto a stack Enqueue the character onto the queue Assume that the string is a palindrome and set the Boolean variable stillpalindrome to true While string is still palindrome and has more characters to process Pop a character from the stack Dequeue a character from the queue If above two characters are not same Set stillpalindrome to false FIG. 6.10

Page 115: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 20 of 23

public class Palindrome { public static void main(String[] args) throws IOException { final int maxStringSize = 180; // maximum size of an input line int numStrings = 0; // total number of strings processed int palindromes = 0; // number of palindromes found int nonPalindromes = 0; // number of non-palindromes found int tooLong = 0; // number of strings too long to process char ch; // current input string character being processed int numLetters; // number of letter characters in current string int charCount; // number of characters checked so far Character fromStack; // current Char object popped from stack Character fromQueue; // current Char object dequeued from queue boolean stillPalindrome; // true as long as the string might still be a palindrome StackInterface stack; // holds non-blank string characters QueueInterface queue; // also holds non-blank string characters String line = null; // input line String dataFileName = args[0]; // name of input file String outFileName = args[1]; // name of output file BufferedReader dataFile = new BufferedReader(new FileReader(dataFileName)); PrintWriter outFile = new PrintWriter(new FileWriter(outFileName)); DecimalFormat fmt = new DecimalFormat("000"); outFile.println(); // print a blank line line = dataFile.readLine(); // read the first input line while(line!=null) // while haven't read all of the input lines { numStrings = numStrings + 1; outFile.println("String " + fmt.format(numStrings) + ": " + line); if (line.length() > maxStringSize) { tooLong = tooLong + 1; outFile.println("String too long - processing skipped"); } else { // check if line is a palindrome

Page 116: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 21 of 23

stack = new ArrayStack(maxStringSize); queue = new ArrayQueue(maxStringSize); numLetters = 0; for (int i = 0; i < line.length(); i++) { ch = line.charAt(i); if (Character.isLetter(ch)) { numLetters = numLetters + 1; ch = Character.toLowerCase(ch); stack.push(new Character(ch)); queue.enqueue(new Character(ch)); } } stillPalindrome = true; charCount = 0; while (stillPalindrome && (charCount < numLetters)) { fromStack = (Character)stack.top(); stack.pop(); fromQueue = (Character)queue.dequeue(); if (!fromStack.equals(fromQueue)) stillPalindrome = false; charCount++; } if (!stillPalindrome) { nonPalindromes = nonPalindromes + 1; outFile.println(" Not a palindrome "); } else { palindromes = palindromes + 1; outFile.println(" Is a palindrome."); } } outFile.println(); line = dataFile.readLine(); // set up processing of next line } dataFile.close(); outFile.close();

Page 117: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 22 of 23

//Set up output frame JFrame outputFrame = new JFrame(); outputFrame.setTitle("Palindromes"); outputFrame.setSize(300,200); outputFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // outputFrame.setDefaultCloseOperation(3); // Instantiate content pane and information panel Container contentPane = outputFrame.getContentPane(); JPanel infoPanel = new JPanel(); // set layout infoPanel.setLayout(new GridLayout(5,1)); infoPanel.add(new JLabel("Total Number Of Strings "+ numStrings)); infoPanel.add(new JLabel("Number Of Strings too long for Processing " + tooLong)); infoPanel.add(new JLabel("Number Of Palindromes "+ palindromes)); infoPanel.add(new JLabel("Number Of Non Palindromes "+ nonPalindromes)); infoPanel.add(new JLabel("Program completed. Close window to exit program.")); contentPane.add(infoPanel); // show information outputFrame.show(); } } Listing 6.5 Figure 6.11 shows the output file, which is obtained because of processing the input file shown in the Figure 6.7. String 001: A man, a plan, a canal, Panama Is a palindrome. String 002: amanaplanacanalpanama Is a palindrome. String 003: This is not a palindrone! Not a palindrome String 004: aaaaaaaaaaa Is a palindrome. String 005: a Is a palindrome.

Page 118: E_Book_JavaDataStructure_SatishSinghal

Topic 6 FIFO Queue Page 23 of 23

String 006: aaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaa Not a palindrome String 007: aAaaaaaaaabaaaaaaAaaa Is a palindrome. String 008: This string is too long xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxx xxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxx String too long - processing skipped String 009: bob Is a palindrome. String 010: dan Not a palindrome String 011: Madam, I'm Adam. Is a palindrome. String 012: eve Is a palindrome. String 013: Eve Is a palindrome. FIG. 6.11 The program does not process the strings larger than 180 characters. For all other strings ignoring space, punctuations and capitalization, it determines whether a particular string is a palindrome or not. Then it prints the string number, the string, and the results of the processing (Figure 6.11). The program also outputs a frame showing the summary of overall processing results (Figure 6.12).

FIG. 6.12

Page 119: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 1 of 60

CS 20 B : Java Data Structures

Topic 7: Linked Lists Author: Satish Singhal Ph. D.

Version 1.0 In topics, five and six we discussed a fixed storage implementation of stacks and queues. In those implementations, the maximum size of a stack or queue was fixed which was assigned at the run-time. The fixed size implementation is constrained in three different ways.

1. If number of elements in the (fixed size) data structure are much smaller than the maximum size then program becomes wasteful of the memory.

2. On the other hand, at times the fixed size implementation may fall short, when the number of data items may exceed the maximum size allotted for the array.

3. The array implementation of data structure will need the contiguous memory allocation in the memory space. When the memory becomes fragmented, then a contiguous block of memory may become unavailable.

The linked structures can avoid all of the above limitations. In linked structures, every element in the structure stores a reference to the next element in the structure. We also called such structure as dynamic structure as they can grow and shrink dynamically and do not have a fixed capacity. This last characteristic provides more flexibility to dynamic or linked structures because their capacity does not have to be known in advance. Fixed structures such as arrays of course faster than the dynamic structures such as linked lists but they are less flexible. How is storage allocated in java? Before we try to understand the mechanism of building and manipulating a linked list in java, we must clearly understand as to how is storage allocated in java. Java has two types of data, which need storage.

1. Atomic data: These are data such as: • int, float, double, long, char and boolean etc. • References or pointers, which store the address of

“structured” data. In storing atomic data, java assigns a name to the container or storage location where atomic data are stored.

2. Structured Data: These types of storage can be empty or can store containers containing atomic data.

Page 120: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 2 of 60

The containers containing structured data in java have no name. Therefore, they are “anonymous”. Only way to get access to containers of structured data in java is to reach them through the pointer or reference pointing to them. Examples of both are given below.

Microsoft PowerPoint Presentation

FIG. 7.1

In Figure 7.1 the statement int ival = 5; creates an atomic or simple container which has a name ival and it stores a value of five in it. On the other hand, the statement String Name; creates another atomic or simple type container called “Name”, whose contents are unknown but it can store the address of another structured container of type String. Once “Name” is assigned a value, it will store the address of structured container it is assigned to point to. This is shown in Figure 7.2.

Page 121: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 3 of 60

FIG. 7.2

The statement boolean bval = true; creates a container named bval and stores a value true in it. However the statement String Name = null; creates a simple or atomic container called “Name”, which stores the address of a no-name container, which stores an object called null. A linkage is formed between the pointer called “Name” and the no-name object because the address of the no-name object is stored inside the atomic container called “Name”.

FIG. 7.3

Page 122: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 4 of 60

Figure 7.3 gives two more examples of creation of atomic or simple containers of type char and double. The structured containers can also be totally empty, whereas the atomic containers can never be – or at the very least they will contain some unknown value (Figure 7.1). Figure 7.4 shows an example of empty structured container.

FIG. 7.4

The atomic container Cls1 stores the address of the empty structured container of type SomeClass. In java an array is also a structured container. The Figure 7.5 shows the example of an array containing only simple or atomic containers.

Page 123: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 5 of 60

FIG. 7.5

In the figure 7.5 the statement int [ ] Arr1 = new int[ 3]; creates a structured container type array, which contains three simple or atomic containers, named such as Arr [0], Arr [1], etc. Each atomic container contains a value zero. The atomic container Arr1 stores the address of no-name container which is integer type array of three elements. Notice the ironical fact that the aggregate structured container has no name, but its individual atomic components do. Finally we show an example of a class which has instance variables as fields. The definition of class Person is given in Listing 7.1.

public class Person { private int age; private String Name; public Person(int data1, String Name1 ) { age = data 1; Name = Name1; } }

Listing 7.1

Page 124: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 6 of 60

The fields in the class Person are an atomic container types int called age, and container Name. The latter can store the address of a structured container of type String. Now in the client code we execute the following two statements: String P_Name = new String(“Randy”); Person Aperson = new Person(23, P_Name); The execution of above two statements gives rise to the containers and linkages shown by the Figure 7.6.

FIG. 7.6

In Figure 7.6 once the constructor call Person Aperson = new Person(23, P_Name); is complete, then the “Name” container of Aperson stores the address of a String type object or structured container, which stores string “Randy”. The container age contains a value of 23. Notice the linkages that Aperson points to nameless container, which contains “Name, which in turn points to a container which stores “Randy”. The linkages allow us to travel between the containers and access the data, subject to the restrictions placed by the access modifiers for the class fields. Understanding the linkages between the references is the key skill to understand linked lists. The Figure 7.7 summarizes the conclusions drawn from the discussions above.

Page 125: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 7 of 60

FIG. 7.7

Basic Linked List Java allows a class to contain a reference to self. This does not create any problem because the reference to itself is really an atomic container, which can store the address of another structured container of similar class type. Let us consider a basic linked list, which really acts as a wrapper for an integer. We call it an IntList and define it in the Listing 7.2. import javax.swing.*; public class IntList { //Instance variables private int data; private IntList Link; //Chained Constructors public IntList() { this(0); } public IntList(int init_data) { this(init_data , null); }

Stores a reference to an IntList type object.

Page 126: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 8 of 60

public IntList(int init_data, IntList Init_Link) { data = init_data; Link = Init_Link; } public String toString( ) { return new Integer(data).toString( ) + "\n"; } public static void main(String [ ] args) { String Input = ""; boolean done = false; //Create a head dummy node with age member = 0. IntList Head = new IntList(0,null); IntList Iterator = Head; while (!done) { Input = JOptionPane.showInputDialog( "Please enter an integer to add to the linked list." + " Type word done to end ."); Input = Input.trim(); //Create a node with data in it if(!Input.equals("done")) { int val = Integer.parseInt(Input); IntList Node = new IntList(val , null); //Add to the list Iterator = Head; while(Iterator.Link!=null) Iterator = Iterator.Link; Iterator.Link = Node; } else done = true; }

Page 127: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 9 of 60

//print the list Iterator = Head; String Output =""; while(Iterator.Link !=null) { Output+=(Iterator.Link).toString(); Iterator = Iterator.Link; } JOptionPane.showMessageDialog(null, "The list you prepared is \n" + Output); System.exit(0); } } Listing 7.2 The results of running the program in Listing 7.2 are shown in Figure 7.8.

FIG. 7.8 A

FIG. 7.8 B

The program asks for input of integers by popping an input box similar to the Figure 7.8 A. The data input continues unless user ends the input by typing word

Page 128: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 10 of 60

“done” in the input box. After the input is over the program prints the linked list prepared by the user by popping an output message pane that is similar to the Figure 7.8 B. Analyzing The Listing 7.2 : Building and Printing a Linked List The mechanism of building a linked list as coded in the main function of Listing 7.2 can be illustrated as follows with the power point slides and with the following pictures.

Microsoft PowerPoint Presentation

FIG. 7.9 A

The program statement IntList Head = new IntList (0,null); creates a node of type IntList, with the data field being equal to zero and the Link field being set to null. The reference Head points to this node. The statement IntList Iterator = Head; sets the IntList type reference Iterator to point to the same node where the reference called "Head" is pointing. Notice that the node with value zero is the dummy node in the list. It is a member of the list, but we ignore it and do not use it.

Dummy Node.

Page 129: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 11 of 60

FIG. 7.9 B

The plan is that as user enters the data, we move Iterator to find the last node and attach the new node to the last node. Figure 7.9 B shows the creation of a new node. The program statement int val = Integer.parseInt (Input); parses the user input to get the integer entered by the user. The statement IntList Node = new IntList (val , null); builds a new node with user data as the data field and the Link field being null. The pointer Node points to this new structured container or object. In figure, 7.9B we assume that user entered a value of five to be inserted into the linked list. When a node is created, at that instant, the list may have only dummy node in it or it may have many members. The task at that point is to travel the list and find the last node, to which we can attach the newly created node. The traveling of the list is done by a while loop which is executed as illustrated by Figures 7.9C to 7.9E . In Figure 7.9C first the Iterator is set to point to where Head node is pointing. This is to be done every time when the loop is executed to get user input.

Page 130: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 12 of 60

FIG. 7.9 C

The first node is schematically shown to have an address of 1000, therefore both references Head and Iterator store the value of address 1000 in them. (See also Figure 7.9 A). Then program does the loop pretest, while (Iterator.Link !=null) In Figure 7.9 D the Iterator.Link and Head.Link , both refer to the Link part of the dummy node’s Link data member, which is not null. Rather it is schematically shown to store an address of 2000. Therefore, the loop pretest condition is true and loop is entered.

Dummy Node

Page 131: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 13 of 60

FIG 7.9 D

When the loop is entered the statement Iterator = Iterator.Link; is executed. This moves the Iterator to point to the next node (Figure 7.9E).

FIG 7.9 E

Dummy Node.

Dummy Node

Page 132: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 14 of 60

Iterator stores an address of 2000 in it, which is the address of the first node in the linked list. The process shown in the figures 7.9D to 7.9E is repeated until the Iterator moves to point to the last node – which is the node with address 4000 and stores four and its Link member points to null. The situation at that point is shown in the Figure 7.9 F.

FIG. 7.9 F

Now the loop pretest condition evaluates to false and loop is exited. We have now located the node, to which the new node generated in the figure 7.9B can be attached. The program executes the statement Iterator.Link = Node; And the new node is attached to the list as the Link member of the last node no longer points to null. Rather it now stores 5000, the address of the new node. (Figure 7.9G).

Dummy Node

Page 133: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 15 of 60

FIG. 7.9 G

Printing the Linked List The movement of Iterator to print the linked list is similar to when we built the linked list. First, we set the Iterator to point to same node where head is pointing. Then we enter a loop and we do the same loop pretest, while (Iterator.Link !=null) Loop tasks now are different. Inside the loop, we build an output string as Output+=(Iterator.Link).toString ( ); The toString ( ) method of IntList class returns a String which merely prints the data member of class IntList and adds a line feed. Then the statement below does loop update Iterator = Iterator.Link; Loop executes until the loop update places the Iterator at the last node. However, before than happens the data member of the last node is already added to the output string. Generic Linked List The linked list built above is ad-hoc in the nature as it can only store integers and the methods to add data to the list and print the list are generated as code snippets inside the main method. Java provides us the power and flexibility to create objects that will facilitate generation of linked list, which can store any type of objects. We follow the same design philosophy to design a generic linked list as we did for

Dummy Node

Page 134: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 16 of 60

designing the data structure stacks and queues. First, we express our design through an interface called List that details the methods that every linked list type class implementing it must have. The Listing 7.3 shows the design specifications for the interface List. We make our List interface Serializable so that objects can be written to files and read from them directly. The Table 7.1 gives the summary of the interface methods and then Listing 7.3 provides the code details.

Method Summary

void addFront(java.lang.Object obj) Function: Transforms the List by adding an object to its front Effect: List size is increased by one Postcondition: List size = List Size + 1

void addRear(java.lang.Object obj) Function: Transforms the List by adding an element to its rear. Effect: List size is decreased by one Postcondition: List size = List Size + 1

boolean contains(java.lang.Object obj) Function: Inspects the List to see if the given element is in the List Effect: The List is unaltered.

java.util.Iterator elements() Function: Creates a facility to iterate through the List Effect: The List is unaltered.

java.lang.Object front() Function: Observes the List and returns the front element without altering the list. Effect: The List is unaltered.

boolean isEmpty() Function: Observes the List to see if it is empty or not. Effect: returns true if List is empty else returns false.

Page 135: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 17 of 60

void makeEmpty() Function: Transforms the List to an empty container Effect: Removes all objects from List Precondition: List has objects in it Postcondition: List is empty.

java.lang.Object rear() Function: Observes the List and returns the rear element without altering the list. Effect: The List is unaltered.

void removeFront() Function: Transforms the List by removing an element from its front Effect: List size is decreased by one Postcondition: List size = List Size - 1

void removeRear() Function: Transforms the List by removing an element from its rear Effect: List size is decreased by one Postcondition: List size = List Size - 1

int size() Function: Observes and determines the number of objects in the List container Effect: Returns the number of elements in the List

Table 7.1 Notice that in a generic linked list we may wish to have functionality that would allow us to add and remove elements to the front or rear, or allow us to peek at the front and rear elements. Therefore, our List interface has the facilitating methods. Observer methods such as isEmpty and size ( ) are needed to find the List size or to see if List is empty. When we wrote the code for IntList we built an iterator to iterate through the list on an ad-hoc basis. Now we use java Iterator class in order to be able to navigate through the list and method elements facilitates such iteration. Notice that interface List specification, in no way constrains the user as to how the

Page 136: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 18 of 60

links are made between the elements of the List. The links may point only to their followers – as singly linked list or links may be established both (to predecessor and follower) – like doubly linked list. These are implementation details, which are left to the implementer of the List interface. Listing 7.3 provides the code for the List interface. import java.io.*; import java.util.*; public interface List extends Serializable { /** * Function: Transforms the List to an empty container<BR> * Effect: Removes all objects from List<BR> * Precondition: List has objects in it<BR> * Postcondition: List is empty. */ public void makeEmpty(); /** * Function: Observes the List to see if it is empty or not.<BR> * Effect: returns true if List is empty else returns false.<BR> * @return true if <code>List</code> is empty else returns false. */ public boolean isEmpty(); /** * Function: Observes and determines the number of objects in the <code> * List</code> container<BR> * Effect: Returns the number of elements in the List<BR> * @return The number of elements in the list as integer value. */ public int size(); /** * Function: Transforms the <code>List</code> by adding an object to its front<BR> * Effect: List size is increaded by one<BR> * Postcondition: List size = List Size + 1 <BR> * @param obj added to the front of the <code>List</code> */ public void addFront(Object obj); /** * Function: Transforms the <code>List</code> by adding an element to * its rear.<BR> * Effect: List size is decreaded by one<BR> * Postcondition: List size = List Size + 1 <BR>

Page 137: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 19 of 60

*@param obj added to the rear of the <code>List</code> */ public void addRear(Object obj); /** * Function: Transforms the <code>List</code> by removing an element from * its front<BR> * Effect: List size is decreaded by one<BR> * Postcondition: List size = List Size - 1 <BR> */ public void removeFront(); /** * Function: Transforms the <code>List</code> by removing an element from * its rear<BR> * Effect: List size is decreaded by one<BR> * Postcondition: List size = List Size - 1 <BR> */ public void removeRear(); /** * Function: Observes the <code>List</code> and returns the front * element without altering the list.<BR> * Effect: The <code>List</code> is unaltered. The front element is * returned.NoSuchElementExceptionis thrown if <code>List</code> is empty. * @return the front element with out altering the <code>List</code> * @exception NoSuchElementException */ public Object front() throws NoSuchElementException; /** * Function: Observes the <code>List</code> and returns the rear * element without altering the list.<BR> * Effect: The <code>List</code> is unaltered. The rear element is * returned. NoSuchElementException is thrown if <code>List</code> is empty. * @return the rear element with out altering the <code>List</code> * @exception NoSuchElementException */ public Object rear()throws NoSuchElementException; /**

Page 138: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 20 of 60

* Function: Inspects the <code>List</code> to see if the given * element is in the <code>List</code><BR> * Effect: The <code>List</code> is unaltered. Method returns a true * if Object is in the <code>List</code> * @return true if the element obj is in the <code>List</code> else * a false is returned. * @param obj is searched for its presence in the <code>List</code> */ public boolean contains(Object obj); ** * Function: Creates a facility to iterate through the <code>List</code><BR> * Effect: The <code>List</code> is unaltered. An Iterator ( a java class) * is returned to iterate through the <code>List</code><BR> * @return Iterator to the <code>List</code>. / public Iterator elements ( ); Listing 7.3 Node class: A linked list class that would implement the List interface (Listing 7.3) would need Node objects to be its data member. In our IntList, class the node consisted of an integer data and an IntList type of reference. However, we need the linked list classes we plan to develop to be of purpose that is more general. Therefore, it makes sense to have the data member as an Object. This is accomplished by creating a class called Node, whose field and constructor summary is given in Table 7.2. The Node class has no methods as none are needed. Field Summary

private java.lang.Object

item The field item is a java Object Type.

private Node next The field next is the reference to another Node, thereby making a Node as a self referential structure.

Page 139: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 21 of 60

Constructor Summary

Node() The argument-less constructor will initialize the item and link field both to null.

Node(java.lang.Object init_item) One argument constructor will take a list item as an argument and the item field of the Node will be initialized to it.

Node(java.lang.Object init_item, Node link) Two-argument constructor constructs Node with the specified item and link to another Node.

Table 7.2 The Listing 7.4 gives the code for the class Node. public class Node { /** * The field item is a java Object Type. This allows the classes * using Node as data member to store any Java object. */ private Object item; /** * The field next is the reference to another Node, thereby making a Node * as a self referential structure. */ private Node next; //constructors /** * The argument-less constructor will initialize the item and link field * both to null. */ public Node() { this(null); } /** * One argument constructor will take a list item as an argument * and the item field of the Node will be initialized to it. The next * field is set to null. * @param init_item is the initial value of item. */

Page 140: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 22 of 60

public Node(Object init_item) { this(init_item, null); } /** * Two argument constructor, constructs Node with the specified * item and link to another Node. * @param init_item is the initial value of item. * @param link is the initial value of next. */ public Node(Object init_item, Node link) { this.item = init_item; this.next = link; } } Listing 7.4 Singly Linked List: The intList we built earlier in this chapter was a singly linked list. Now we build a singly linked list, which can contain any java object as its member. Our singly linked list will contain Node class, but we make the node class as the inner class to our singly linked list class. This is done so that we do not have to alter Node class to provide accessor and setter methods to alter and get value of private data members item and next (Listing 7.4). Later on, we would use the Node class to build linked stacks and queues. Table 7.3 gives the summary of fields, constructors and methods for the class SinglyLinkedList and the detailed code is given in the Listing 7.5.

Inner Class Summary protected

class SinglyLinkedList.Node

Field Summary protected

SinglyLinkedList.Node front

protected int numberElements

protected

SinglyLinkedList.Node rear

Constructor Summary

SinglyLinkedList()

Page 141: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 23 of 60

Method Summary void addAfter(java.lang.Object obj,

java.lang.Object target) Insert obj after target object in the list

void addBefore(java.lang.Object obj, java.lang.Object target) Insert obj before target object in the list

void addFront(java.lang.Object obj)

Function: Transforms the List by adding an object to its front Effect: List size is increaded by one Postcondition: List size = List Size + 1

void addRear(java.lang.Object obj)

Function: Transforms the List by adding an element to its rear. Effect: List size is decreaded by one Postcondition: List size = List Size + 1

boolean contains(java.lang.Object obj)

Function: Inspects the List to see if the given element is in the List Effect: The List is unaltered.

java.lang.Object elementAfter(java.lang.Object target) Return object after target object in the list Throw NoSuchElementException if target not in the list.

java.lang.Object elementBefore(java.lang.Object target) Return object before target object in the list Throw NoSuchElementException if target not in the list.

java.util.Iterator elements() Function: Creates a facility to iterate through the List Effect: The List is unaltered.

java.lang.Object front() Function: Observes the List and returns the front element without altering the list. Effect: The List is unaltered.

protected SinglyLinkedList.Node

getNode(java.lang.Object value)

For internal use only. boolean isEmpty()

Function: Observes the List to see if it is empty or not. Effect: returns true if List is empty else returns false.

Page 142: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 24 of 60

void makeEmpty()Function: Transforms the List to an empty container Effect: Removes all objects from List Precondition: List has objects in it Postcondition: List is empty.

protected SinglyLinkedList.Node

nodeBefore(SinglyLinkedList.Node someNode) For internal use only.

java.lang.Object rear() Function: Observes the List and returns the rear element without altering the list. Effect: The List is unaltered.

void removeAfter(java.lang.Object target) Delete object after target object in the list Throw NoSuchElementException if target not in the list.

void removeBefore(java.lang.Object target) Delete object before target object in the list Throw NoSuchElementException if target not in the list.

void removeFront() Function: Transforms the List by removing an element from its front Effect: List size is decreaded by one Postcondition: List size = List Size - 1

void removeRear() Function: Transforms the List by removing an element from its rear Effect: List size is decreaded by one Postcondition: List size = List Size - 1

int size() Function: Observes and determines the number of objects in the List container Effect: Returns the number of elements in the List

Table 7.3 Figure 7.10 shows the relationship between List interface and the class SinglyLinkedList in the UML diagram.

Page 143: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 25 of 60

FIG. 7.10 import java.util.*; import java.io.*; import javax.swing.*; public class SinglyLinkedList implements List{ //The Node is declared as an inner class with protected data //members and constructors protected class Node implements Serializable{ /** * The field item is a java Object Type. This allows the classes * using Node as data member to store any Java object. */ protected Object item; /** * The field next is the reference to another Node, thereby making a Node * as a self referential structure. */ protected Node next; //constructors /** * the argument-less constructor will initialize the item

Page 144: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 26 of 60

and link field * both to null. */ protected Node() { this(null); } /** * One argument constructor will take a list item as an argument * and the item field of the Node will be initialized to it. The next * field is set to null. * @param init_item is the initial value of item. */ protected Node(Object init_item) { this(init_item, null); } /** * Two argument constructor, constructs Node with the specified * item and link to another Node. * @param init_item is the initial value of item. * @param link is the initial value of next. */ protected Node(Object init_item, Node link) { this.item = init_item; this.next = link; } }//end of inner class Node //Other fields protected Node front; protected Node rear; protected int numberElements; //Implementing the interface methods public void makeEmpty() { while(front != null) { Node previous = front; front = front.next; previous = null; } this.numberElements = 0; rear = null; } public void addFront(Object obj) { //Create a node with next pointing to front Node newNode = new Node(obj,front);

Page 145: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 27 of 60

this.front = newNode; //If list was originally empty then front and rear must be same if(this.numberElements == 0) this.rear = this.front; //Increase the number of elements by one this.numberElements++; } public void addRear(Object obj) { Node newNode = new Node(obj,null); if(this.numberElements == 0) { this.front = newNode; this.rear = newNode; } else { rear.next = newNode; rear = newNode; } this.numberElements++; } public void removeFront(){ if(isEmpty()) throw new NoSuchElementException("The list is empty"); else{ this.front = this.front.next; this.numberElements--; if(this.numberElements == 0) rear = null; } } public void removeRear() { if(isEmpty()) throw new NoSuchElementException("The list is empty"); else if(this.numberElements == 1){ this.front = null; this.rear = null; } else{ Node previous = front; while(previous.next != rear) previous = previous.next; previous.next = null; rear = previous; } this.numberElements--; }

Page 146: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 28 of 60

public boolean isEmpty() { return this.numberElements == 0; } public int size() { return this.numberElements; } public Object front() { if(isEmpty()) throw new NoSuchElementException("The list is empty"); else return front.item; } public Object rear() { if(isEmpty()) throw new NoSuchElementException("The list is empty"); else return rear.item; } public boolean contains(Object obj) { Node current = front; while(current != null && !current.item.equals(obj)) current = current.next; return current != null; } public Iterator elements() { Vector V = new Vector(); Node current = front; while(current != null) { V.addElement(current.item); current = current.next; } return V.iterator(); } /** Insert obj after target object in the list */ public void addAfter(Object obj, Object target) { Node itemNode = getNode (target); if(itemNode == null) throw new NoSuchElementException( "From addAfter method. The target node does not exist\n"); else if (!this.contains(obj)){ Node newNode = new Node (obj, itemNode.next);

Page 147: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 29 of 60

itemNode.next = newNode; numberElements++; if (this.rear == itemNode) rear = newNode; } else { JOptionPane.showMessageDialog(null, "The node to be added already present in the list\n"); } } /** Insert obj before target object in the list */ public void addBefore(Object obj, Object target) { Node itemNode = getNode (target); if(itemNode == null) throw new NoSuchElementException( "From addBefore method. The target node does not exist\n"); else if (!this.contains(obj)){ itemNode = getNode (target); Node newNode = new Node (obj, itemNode); if (this.front == itemNode) this.front = newNode; else{ Node beforeNode = nodeBefore(itemNode); beforeNode.next = newNode;} numberElements++;} else{ JOptionPane.showMessageDialog(null, "The node to be added already present in the list\n");}} /** Delete object after target object in the list * Throw NoSuchElementException if target not in the list. * Throw NoSuchElementException if target is last in the list. */ public void removeAfter(Object target) { // Exercise } /** Delete object before target object in the list * Throw NoSuchElementException if target not in the list. * Throw NoSuchElementException if target is first in the list. */ public void removeBefore(Object target) { // Exercise } // Queries /** Return object after target object in the list * Throw NoSuchElementException if target not in the list. * Throw NoSuchElementException if target is last in the list. */ public Object elementAfter(Object target) { if (!this.contains(target) || getNode(target) == this.rear) throw new NoSuchElementException("removeAfter::obj does not exist or is last in list"); else

Page 148: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 30 of 60

return getNode(target).next.item; } /** Return object before target object in the list * Throw NoSuchElementException if target not in the list. * Throw NoSuchElementException if target is first in the list. */ public Object elementBefore(Object target) { if (!this.contains(target) || getNode(target) == this.front) throw new NoSuchElementException("removeBefore::obj does not exist or is first in list"); else return nodeBefore(getNode(target)).item; } // Internal methods /** * For internal use only. * This function, available only within this class * returns the node associated with value. If value is * not present in the list getNode returns null */ protected Node getNode (Object value) { Node node = front; Node result = null; while (node != null) { if (node.item.equals(value)) { result = node; break; } node = node.next; } return result; } /** * For internal use only. * This function, available only within the class * returns the node just before someNode. If someNode is null or * the only node present in the list, this function returns null. */ protected Node nodeBefore (Node someNode) { if (someNode != null && someNode != front) { Node previous = front; while (previous.next != someNode) previous = previous.next; return previous; } else return null; } }

//Listing 7.5 Discussion of Methods from SinglyLinkedList Class We discuss some of the important methods in the class SinglyLinkedList.

Page 149: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 31 of 60

Method makeEmpty( ): As stated in the specification, the task of makeEmpty ( ) method is to delete the list. The following PowerPoint presentation shows as to how the method does that.

Microsoft PowerPoint Presentation

Assume that the Figure 7.11 shows the SinglyLinkedList.

FIG. 7.11 In the condition shown in Figure 7.11, the front is storing an address of 1000, so it is not null. Therefore, the loop is entered. Inside the loop a fresh Node called previous is created to point to where front is pointing (Figure 7.12).

Page 150: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 32 of 60

FIG. 7.12 Then the next program statement moves the reference called “front” to point to where the front.next is pointing currently (Figure 7.13).

FIG. 7.14

Page 151: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 33 of 60

In order to delete the first node now the previous is set to null (Figure 7.15).

FIG. 7.15 Then the garbage collector collects the first node. This process is repeated two more times to delete the second and third node (Figure 7.16).

FIG. 7.16

Page 152: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 34 of 60

Node previous is now set to point to the last node and it stores an address of 4000 (Figure 7.17).

FIG. 7.17 Next two program statements set the front and previous both to null. The loop is exited, but the reference rear is still pointing to the last node. The first statement outside the loop sets the rear to null and the last node is then garbage collected (Figure 7.18).

Page 153: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 35 of 60

FIG. 7.18 Before setting the rear to null, the number of elements in the list is set to zero. Method addRear (Object obj) AS seen in the specifications (Listing 7.3), the purpose of the method addRear ( ) is to add the element obj to the list, so that it becomes the last element in it. The power point presentation given below illustrates the mechanism of adding a node to the rear of list dynamically.

Microsoft PowerPoint Presentation

When an object is to be added to the list, then at that time list may be empty or it may not be. When the method addRear ( ) is invoked, first a newNode is created which has the Object obj as its data member item and the next field of newNode is set to null (Figure 7.19).

Page 154: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 36 of 60

FIG. 7.19 If the list is empty, then numberElements will be zero and the assertion: numberElements == 0 is true. Therefore, the task in the scope of if structure is executed. The first statement in the if structure sets the front reference to point to newNode (Figure 7.20).

FIG. 7.20 The next statement sets the rear reference to point to newNode as well (Figure 7.21).

Page 155: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 37 of 60

FIG. 7.21 The else clause is executed if the list already has one or more elements. Figure 7.22 shows schematically, a possible scenario.

FIG. 7.22 The first statement in the else clause makes the rear.next point to where newNode is pointing (Figure 7.23).

Page 156: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 38 of 60

FIG. 7.23 Since this newNode is now the rear node in the list, the next statement makes the rear reference also point to where newNode is pointing (Figure 7.24).

FIG. 7.24

Page 157: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 39 of 60

The task with is else clause is thus completed. After exiting the else clause or if clause the size of list given by field numbereElements is increased by one. The method addFront ( ) works in a manner similar to addRear ( ). Method removeRear ( ) Method removeRear ( ) takes no arguments and simply removes the rear element from the list. The PowerPoint presentation below shows the mechanism of method removeRear ( ) dynamically.

Microsoft PowerPoint Presentation

The method has three if / else blocks. Obviously if list is empty, then there is no element available for removal. Thus, the first block throws a NoSuchElementException (Figure 7.25).

FIG. 7.25 Now the removal scheme is different depending upon whether the list has only one element or more than one. If list has only one element, then the block 2 is entered (Figure 7.26).

Page 158: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 40 of 60

FIG. 7.26 In block 2, the front is set to null and then rear is set to null. These two executions remove all the references from the only node in the list, and the node is garbage collected (Figure 7.27).

FIG. 7.27 If the list has more than one element than the block 3 is entered (Figure 7.28).

Does list have only one element? Then enter the else if block.

Page 159: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 41 of 60

FIG. 7.28 The first statement sets a new reference called previous point to the front node (Figure 7.29).

FIG. 7.29

Page 160: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 42 of 60

Now the pretest condition in the loop checks whether the reference previous.next is at last node or not? (Figure 7.30).

FIG. 7.30 The purpose of the loop is to set the reference previous at the node before the last node. In Figure 7.30, the reference previous.next is not at the last node. Therefore, the loop is entered. Inside the loop, the reference previous is moved by one node (Figure 7.31).

Page 161: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 43 of 60

FIG. 7.31 This time the loop pretest evaluates false because the previous.next is pointing to where rear reference is pointing. Therefore, the loop is exited. The first statement after the loop breaks the linkage between the last node and the preceding. The rear node is then also set to point to where previous is pointing and the node that was last is garbage collected (Figure 7.32).

Page 162: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 44 of 60

FIG. 7.32 This ends the block 3 and then the next statement reduces the list size by one. The method removeFront ( ) works exactly the way method removeRear ( ) does. Method elements ( ) The method elements ( ) returns an Iterator to the linked list, which can be used to traverse the list. Java has an interface called Iterator which has the methods summarized by the table 7.4. Method Summary boolean hasNext()

Returns true if the iteration has more elements. Object next()

Returns the next element in the iteration. void remove()

Removes from the underlying collection the last element returned by the iterator (optional operation).

Table 7.4 The method hasNext ( ) returns a true if the class implementing the Iterator interface has more elements as the collection of elements in the class is iterated. The method next ( ) returns the next element in the collection as an Object. Method remove can remove the last element returned by the Iterator. Proper exceptions are

Page 163: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 45 of 60

thrown to ascertain that next ( ) and remove ( ) will work properly. The java Vector class inherits a method called iterator ( ) from its super class AbstractList. The method iterator() when invoked by any object of type AbstractList will return an Iterator to that object. Notice that Iterator is an interface and iterator ( ) is a method. The elements ( ) method for class SinglyLinkedList then works as follows.

The elements( ) method which implements the above algorithm is shown below in the Figure 7.33.

Method elements ( )

public Iterator elements( ){Vector V = new Vector( );Node current = front;while(current != null) {V.addElement(current.item);current = current.next;}return V.iterator( );}

FIG. 7.33 The Iterator returned by the method elements ( ) can now invoke the methods listed in Table 7.4 on the SinglyLinkedList. Method addAfter (Object obj, Object target) The method addAfter ( ) adds the Object, obj, after the Object called target in the list. If the target node does not exist then NoSuchElementException is thrown. If the node to be added (obj) already exists in the list, then node is not added and a message is displayed. The following PowerPoint presentation illustrates the dynamics of method addAfter ( ).

• Declare a local Vector of size numberElements, where the numberElements is the size of SinglyLinkedList.

• Insert all the elements from the List into this vector. • Return an Iterator to this vector, using the method iterator ( ).

Vector V declared.

All SinglyLinkedList elements loaded into the Vector V

Method iterator returns an Iterator to Vector V.

Page 164: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 46 of 60

MethodaddAfter.ppt

As shown by Figure 7.34 the method addAfter ( ) has three if /else if /else blocks.

FIG. 7.34 Inside the method, first the method getNode ( ) is called with target node reference as an argument. itemNode would be null if target node does not exist. Otherwise, itemNode will actually be the reference to the target node. If itemNode is null then first block is executed, which throws a NoSuchElementException . The second block tests whether the node to be added, obj, is already in the list. If node is not in the list, then program execution enters the block2. The first statement in block2 creates a new node called newNode, which takes obj as its item field and itemNode.next as its next field (Figure 7.35).

Page 165: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 47 of 60

FIG. 7.35 Note that itemNode.next will point to the node after the target node. That creates a linkage between the node we wish to add and the node after the target node. Next step is to break the existing linkage between the target node and the node after it (Node with address 4000 in the Figure 7.35). The target node must then point to the newNode. Making itemNode.next point to newNode accomplishes this (Figure 7.36).

Page 166: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 48 of 60

FIG. 7.36 If target node was not the rear node, then all the necessary links to add the Object, obj, after the target node are now made. Then next statement increments the list size by one. If by some chance, the target not would be the last node in the list, then the rear reference would still be pointing to the target node (Figure 7.37).

Page 167: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 49 of 60

FIG. 7.37 It is required that rear reference point to newNode. The program statement Rear = newNode accomplishes that and the addition of newNode to the list is complete (Figure 7.38).

Page 168: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 50 of 60

FIG. 7.38 If program does not execute blocks one and two then it executes the block3, which displays a message that the node obj is already in the list and will not be added. Method addBefore (Object obj, Object target ) The method addBefore ( ) adds the node obj, before the target node. Similar to method addAfter ( ), the method has three if / else –if/ else blocks (Figure 7.39).

MethodaddBefore.ppt

Page 169: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 51 of 60

FIG. 7.39 The method executes the blocks one and three in the manner described earlier for method addAfter ( ). Therefore, we only discuss the execution of block2. The program enters block2 if the node obj is not in the list. The first statement in the block2 creates a new node, which takes the obj for its item field and its next field points to target or itemNode. Remember that itemNode is a reference to the target node (Figure 7.40).

Page 170: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 52 of 60

FIG. 7.40 Now there is a possibility that front node is the target node. Figure 7.41 shows this situation.

Page 171: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 53 of 60

FIG. 7.41 Since the newNode must be the new front node, we must now move the reference front to pint to newNode. Figure 7.42 shows this movement.

Page 172: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 54 of 60

FIG. 7.42 However, it is possible that we do have a situation similar to Figure 7.40, where the target node is not front node. Then it is necessary that node before the target node point to newNode. The following two steps accomplish this process. First, an ad-hoc Node beforeNode is created and is made to point to node before the target node (or itemNode in this case) (Figure 7.43).

Page 173: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 55 of 60

FIG. 7.43 The method nodeBefore ( ) takes the itemNode or target node as an argument and returns a reference to the node before it. Then it is a matter of making the beforeNode.next point to the newNode to complete the necessary linkages (Figure 7.44).

Page 174: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 56 of 60

FIG. 7.44 Finally, the number of elements in the list is incremented by one. Testing of Singly Linked List class Listing 7.6 shows the class TestSinglyLinkedList, where the main method contains the code to test some of the methods of the SinglyLinkedList class. import javax.swing.*; import java.util.*; public class TestSinglyLinkedList { public static void main(String [ ] args) { SinglyLinkedList myList = new SinglyLinkedList( ); String Input = ""; boolean done = false; while (!done) { Input = JOptionPane.showInputDialog( "Please enter a number character or string to add to the " + "front of linked list. Click cancel when done ."); if(Input== null) done = true;

Adding to the front of the list.

Page 175: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 57 of 60

else { Input = Input.trim(); myList.addFront(Input); } } done = false; while (!done) { Input = JOptionPane.showInputDialog( "Please enter a number character or string to add to the " + "rear of linked list. Click cancel when done ."); if(Input== null) done = true; else { Input = Input.trim(); myList.addRear(Input); } } //printing the list Iterator iter = myList.elements(); String Out = ""; while(iter.hasNext()) { Out += (iter.next()).toString(); Out += "\n"; } JOptionPane.showMessageDialog(null, "Your List is \n" + Out); JOptionPane.showMessageDialog(null, "The front element in the list is\n" + myList.front()); JOptionPane.showMessageDialog(null, "The rear element in the list is\n" + myList.rear()); JOptionPane.showMessageDialog(null, "The list size is\n" + myList.size()); System.exit(0); } } Listing 7.6 The beauty of Object based linked list lies in the fact that in order to add, remove, or print an element in the list we do not have to worry about its data type. Almost all objects can be added, removed or printed easily. The first while loop adds the objects to the front of the list, whereas the second one to the rear. We then get an Iterator to the list and print it. Finally, we test few other list methods for their

Page 176: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 58 of 60

functionality. Figure 7.45 shows the JOptionPane screen shots when the user enters the data as shown in Figures 7.45 A and B.

FIG. 7.45A

FIG. 7.45B Figure 7.45C shows the resulting list.

User enters strings: best , is, list, This and then clicks cancel.

User enters strings: In, Santa Monica, College, on, October, 4, 2000 and then clicks cancel.

FIG. 7.45C

Page 177: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 59 of 60

Then some of the list methods are executed automatically and the resulting screens are shown by the Figures 7.45D to 7.45F

FIG. 7.45D

FIG. 7.45E

FIG. 7.45F

Summary Of Some Facts

1. Linked Lists are dynamic structures, which are best suited for frequent additions and removals of list items. 2. Simplest form of linked list would have a head node. Each node in the list

stores a reference to its next member. The tail node stores a null reference. 3. In java, all structured containers are anonymous and they all contain only

atomic containers. 4. A linked list class may contain an inner class Node, which facilitates the

creation of a generic linked list class.

Page 178: E_Book_JavaDataStructure_SatishSinghal

Topic 7 Linked Lists Page 60 of 60

5. A singly linked list class has its node pointing only to the node after them. This allows the list iteration only by forward movement in the list. This movement restriction comes with an advantage that the number of links to be made or broken when a node is added or removed is kept to minimum.

6. In addition to the head node pointing to the first node (called front), a reference to the last node (called rear) may be added to the singly linked list for added functionality.

7. Methods can be added to the Singly Linked List class to search the list for a certain key, navigate through the list using an Iterator, adding or removing the nodes before and after a target node – and helper methods to facilitate the above.

8. In later algorithmic analysis we would show that for searching for a key, the linked lists can work no faster than a Big O of 1 ( O(N) ), whereas arrays can work faster than that having a Big O of log2N or O (log2N).

Page 179: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 1 of 42

CS 20 B : Java Data Structures

Topic 8 : Linked Stacks And Queues Author: Satish Singhal Ph. D.

Version 1.0 Dynamic implementation of stacks and queue data structures may be beneficial in those situations, where their sizes may grow or shrink rapidly during a particular application. We can easily learn from the SinglyLinkedList class implementation we discussed in the previous topic to design the linked stack and queue structures. In this process, we still implement the interfaces Stack and Queue, but we modify the storage of actual Objects by including the Node class as an inner class, and having appropriate references to navigate the data structure. We first discuss the linked stack and then the linked FIFO queue. Linked Stack In order to design the linked version of stack, we could implement the interface StackInterface, the way we did in the array version. There is one difference, however. Since linked version does not have an upper limit on the size, we may not need the method isFull ( ) and the class StackOverflowException. Therefore, our design of StackInterface must change slightly. This altered design is given in Listing 8.1. public interface StackInterface { public void push(Object item); // Effect: Adds item to the top of this stack. // Postcondition: If (this stack is full) // an unchecked exception that communicates // 'push on stack full' is thrown // else // item is at the top of this stack. public void pop() throws StackUnderflowException; // Effect: Removes top item from this stack // Postconditions: If (this stack is empty) // an unchecked exception that communicates // 'pop on stack empty' is thrown // else // top element has been removed from this stack. public Object top() throws StackUnderflowException; // Effect: Returns a reference to the element on top of this stack // Postconditions: If (this stack is empty)

Page 180: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 2 of 42

// an unchecked exception that communicates // 'top on stack empty' is thrown // else // return value = (top element of this stack). public boolean isEmpty(); // Effect: Determines whether this stack is empty. // Postcondition: Return value = (this stack is empty) } Listing 8.1 Notice that our altered design only changes in the sense that compared to previous version, we just remove the methods isFull ( ), and push method no longer needs to throw an exception. Listing 8.2 shows the code for LinkedStack class implementing the interface of Listing 8.1. import java.util.*; import java.io.*; import javax.swing.*; public class LinkedStack implements StackInterface { //The Node is declared as an inner class with protected data //members and constructors protected class Node implements Serializable { /** * The field item is a java Object Type. This allows the classes * using Node as data member to store any Java object. */ protected Object item; /** * The field next is the reference to another Node, thereby making a Node * as a self referential structure. */ protected Node next; //constructors /** * the argument-less constructor will initialize the item and link field * both to null. */ protected Node() { this(null); }

Page 181: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 3 of 42

/** * One argument constructor will take a list item as an argument * and the item field of the Node will be initialized to it. The next * field is set to null. * @param init_item is the initail value of item. */ protected Node(Object init_item) { this(init_item, null); } /** * Two argument constructor, constructs Node with the specified * item and link to another Node. * @param init_item is the initial value of item. * @param link is the initial value of next. */ protected Node(Object init_item, Node link) { item = init_item; next = link; } }//end of inner class Node //Other fields protected Node top = null; protected int size = 0; public void push(Object obj) { Node newNode = new Node(obj,top); top = newNode; size++; } public void pop() throws StackUnderflowException { if(isEmpty()) throw new StackUnderflowException( "Pop attempted on an empty stack."); else { Node oldNode = top; top = top.next; size--; oldNode = null;

Page 182: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 4 of 42

} } public Object top() throws StackUnderflowException { if(isEmpty()) throw new StackUnderflowException( "Top attempted on an empty stack."); else return top.item; } public boolean isEmpty() { return top == null; } public int getSize() { return size; } }

Listing 8.2 Compared to the array implementation, we add an extra field size in the LinkedStack class, to indicate the size of stack at a particular time. We also add the method getSize ( ), which returns the size of the stack. The Listing 8.3, class TestLinkedStack.java tests the functioning of linked stack. import javax.swing.*; public class TestLinkedStack { public static void main(String [ ] args) { LinkedStack Stk1 = new LinkedStack(); boolean done = false; String Input = ""; while(!done) { Input = JOptionPane.showInputDialog("Please enter the " + " string, characters and numbers to push on the stack " + " Click cancel when done."); if(Input== null) done = true; else {

Page 183: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 5 of 42

Input = Input.trim(); Stk1.push(Input); } } JOptionPane.showMessageDialog(null, "The size of stack is " + Stk1.getSize()); JOptionPane.showMessageDialog(null, "The top element on stack is " + Stk1.top()); String Out = ""; while(!Stk1.isEmpty()) { Out = Out + " " + (String)(Stk1.top()); Stk1.pop(); } JOptionPane.showMessageDialog(null, "Printing the stack after popping it.\n" + Out); Stk1.top(); Stk1.pop(); System.exit(0); } } Listing 8.3 In Listing 8.3, first an object of type LinkedStack Stk1 is created. Notice that when the stack is instantiated, the constructor would set the reference top to null and the size to zero. A loop is entered to push the data on to the stack. A JOptionPane is used to get user input (Figure 8.1).

Figure 8.1

"Method push.ppt"

After entering the required data the user clicks, cancel button to stop data entry. In the call to push ( ) method, each time an item is pushed on to the stack, first a newNode is created as Node newNode = new Node (item, top);

User enters Santa Monica College October 2002

Page 184: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 6 of 42

This makes the newNode have the obj as value contained in item and next filed pointing to where top is currently pointing. When the push is called, first time the situation is similar to the Figure 8.2.

Figure 8.2 At that point the next field of newNode stores a null as top also stores a null. Then next program line makes the top point to where newNode is pointing (Figure 8.3).

Figure 8.3

Page 185: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 7 of 42

This completes the addition of first node in the stack. The list size is then incremented by statement size++; If stack already has some elements in it, then the situation may look like Figure 8.4.

Figure 8.4 The next field of newNode will then point to where top reference is pointing. Then it is just a matter of making the top reference point to where newNode is pointing (Figure 8.5).

Page 186: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 8 of 42

FIG. 8.5 Then the list size is incremented by one to make the list size four. The main method in class TestLinkedStack prints the list size after the list is built. (Figure 8.6).

FIG. 8.6 The top element of the stack is then displayed (Figure 8.7). Since the element 2002 was added last, that is the one displayed call to method top ( ) (Figure 8.7).

FIG. 8.7

Page 187: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 9 of 42

The stack is then popped and as it is popped a string of elements in it is built and displayed (Figure 8.8).

FIG. 8.8 Since the word Santa Monica was added as a unit the first, it is the last element on the stack. The last pop statement will throw a StackUnderflowException. Linked FIFO Queue Similar to the situation of linked stack, the linked FIFO queue would not need the method isFull and the exception class QueueOverflowException. Listing 8.4 shows the modified QueueInterface. //---------------------------------------------------------------------------- // QueueInterface.java // // Interface for a class that implements a queue of Objects. // A queue is a first-in, first-out structure. //---------------------------------------------------------------------------- public interface QueueInterface { public void enqueue(Object item); // Effect: Adds item to the rear of this queue. // Precondition: This queue is not full. // Post-condition: item is at the rear of this queue. public Object dequeue( ) throws QueueUnderflowException; // Effect: Removes front element from this queue and returns it. // Precondition: This queue is not empty. // Post-conditions: Front element has been removed from this queue. // Return value = (the removed element) public boolean isEmpty( ); // Effect: Determines whether this queue is empty. // Post-condition: Return value = (this queue is empty) } Listing 8.4 The class LinkedQueue that implements the modified QueueInterface given above is shown in Listing 8.5.

Page 188: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 10 of 42

import java.io.*; public class LinkedQueue implements QueueInterface { //The Node is declared as an inner class with protected data //members and constructors protected class Node implements Serializable { /** * The field item is a java Object Type. This allows the classes * using Node as data member to store any Java object. */ protected Object item; /** * The field next is the reference to another Node, thereby making a Node * as a self referential structure. */ protected Node next; //constructors /** * the argument-less constructor will initialize the item and link field * both to null. */ protected Node() { this(null); } /** * One argument constructor will take a list item as an argument * and the item field of the Node will be initialized to it. The next * field is set to null. * @param init_item is the initail value of item. */ protected Node(Object init_item) { this(init_item, null); } /** * Two argument constructor, constructs Node with the specified * item and link to another Node. * @param init_item is the initial value of item. * @param link is the initail value of next. */ protected Node(Object init_item, Node link)

Page 189: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 11 of 42

{ this.item = init_item; this.next = link; } }//end of inner class Node //Other fields private int numItems = 0; // number of items on the queue private Node front = null; private Node rear = null; // Method enqueue adds an element to the rear of this queue public void enqueue(Object obj) { Node newNode = null; if(numItems == 0) { newNode = new Node(obj,null); front = newNode; rear = newNode; numItems++; } else { newNode = new Node(obj,null); rear.next = newNode; rear = newNode; numItems++; } } // Removes an element from the front of this queue public Object dequeue( ) throws QueueUnderflowException { if(isEmpty( )) throw new QueueUnderflowException ("Dequeue attempted on an empty queue"); else { Node oldNode = front; front = front.next; this.numItems--; Object Value = oldNode.item; oldNode.next = null; oldNode = null;

Page 190: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 12 of 42

if(this.numItems == 0) rear = null; return Value; } } public boolean isEmpty() // Checks if this queue is empty { return (numItems == 0); } int getSize() { return this.numItems; } } Listing 8.5 The class LinkedQueue has three fields, which represent the number of elements in the queue at any time and the reference to the front and rear node. Figure 8.8 shows the schematic view of the Linked Queue with three elements in it.

Schematic View of Linked Queue

1000

front

item

1

next2000

item

2

next3000

item

3

nextnull

1000 2000 3000

3000

rearnumItems = 3

FIG. 8.8 Obviously, the blank queue with zero elements in it will have the front, rear references set to null, and numItems set to zero. Let us now discuss the methods enqueue and dequeue for the LinkedQueue data structure. The following PowerPoint presentation displays the dynamics of the method enqueue.

Page 191: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 13 of 42

Methodenqueue.ppt

The method enqueue ( ) has two blocks as if and else control structure (Figure 8.9).

FIG. 8.9 The block#1 is executed if the LinkedQueue is empty. Otherwise the block#2 is executed. When LinkedQueue is empty and program control is on the first statement inside the block#1, then the situation is shown by Figure 8.10.

Page 192: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 14 of 42

FIG. 8.10 A newNode, which has the obj as its item field and null as its next field is created. Then the next three statements set both front and rear references to point where newNode is pointing, and numItems is incremented to one (Figure 8.11).

Page 193: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 15 of 42

FIG. 8.11 When the LinkedQueue is not empty, then it is similar to Figure 8.8. Then inside method enqueue the block#2 is executed (Figure 8.9). When the first statement inside the else block is executed, Figure 8.12 depicts the situation.

Page 194: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 16 of 42

FIG. 8.12 Since the newNode is added to the end of the queue, the next two statements make the next field of current last node as well as the rear reference point to where newNode is pointing. Finally, the numItems is incremented by one (Figure 8.13).

Page 195: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 17 of 42

FIG. 8.13 Method dequeue ( ) Method dequeue ( ) is required to throw a QueueUnderflow exception if the LinkedQueue is empty. Otherwise, it must remove the element from the front of the queue and readjust the front reference. If queue has only one element when dequeue is called, then it must readjust the rear reference as well. The PowerPoint presentation below shows that how the method dequeue ( ) works. The method dequeue ( ) has two blocks of code embedded in if/else control structure (Figure 8.14).

Page 196: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 18 of 42

FIG. 8.14 If the queue is empty, then the block#1 is executed and QueueUnderflowException is thrown. The else block is executed when queue is not empty. Since removal is from front (FIFO protocol), to remove the front node, an additional reference, oldNode is assigned to the very first node (Figure 8.15).

Page 197: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 19 of 42

FIG. 8.15 In order to remove the first node, the front reference is made to point to where front.next is pointing presently and numItems is decremented by one (Figure 8.16).

Page 198: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 20 of 42

FIG. 8.16 Since we need to save the item field of previous front node, we use an additional object called Value to save it (Figure 8.17).

Page 199: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 21 of 42

FIG. 8.17 Then we set the next field of previous first node to null to break its linkage from the queue linked list. We also set the oldNode to point to null and return the Value (Figure 8.18).

Page 200: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 22 of 42

FIG. 8.18 If queue contained only one item, then the if structure before the return statement is executed (Figure 8.19). This would set the rear reference to null as well.

Page 201: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 23 of 42

FIG. 8.19 The return mechanism is similar to the one shown in Figure 8.18. Testing the LinkedQueue We write a class TestLinkedQueue in order to test the functionality of linked queue. The code for this class is given in listing 8.6. import javax.swing.*; public class TestLinkedQueue { public static void main(String [ ] args) { LinkedQueue Queue1 = new LinkedQueue(); boolean done = false; String Input = ""; while(!done) { Input = JOptionPane.showInputDialog("Please enter the " + " string, characters and numbers to enter in the queue " + " Click cancel when done."); if(Input== null) done = true; else

Page 202: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 24 of 42

{ Input = Input.trim(); Queue1.enqueue(Input); } } JOptionPane.showMessageDialog(null, "The size of queue is " + Queue1.getSize()); String Out = ""; while(!Queue1.isEmpty()) { Out = Out + " " + (String)(Queue1.dequeue()); } JOptionPane.showMessageDialog(null, "Printing the queue after dequeuing it.\n" + Out); Queue1.dequeue(); // Will cause a QueueUnderflowException System.exit(0); } } Listing 8.6 The code in Listing 8.6 is very similar to the one we used to test LinkedStack class. We take user input to build a queue, print its size and then print the whole queue. We finally make a call to dequeue to activate the QueueUnderflowException. The program prompts the user to enter the objects in the queue (Figure 8.20).

FIG. 8.20 Figure 8.21 prints the size of the queue.

User enters A October 21 2002 Day in California

Page 203: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 25 of 42

FIG. 8.21 Finally, the Figure 8.22 shows the screen shot, which prints the queue.

FIG. 8.22 Deque Deque is a double-ended queue data structure. In contrast to FIFO queue, deque allows addition or removal of elements from both front and rear. This property allows the deque to be used either as a FIFO queue or as a stack. To use a deque as a FIFO queue, one can add the elements to rear and remove from front. However, to use the deque as a stack one can add and remove only from front of rear. The Deque class can be written as a restricted Singly Linked List. If we remove all the methods from SinglyLinkedList class we discussed in topic seven, which add, or remove elements at any location other than the front and rear, then we end up with the code for a Deque. Listing 8.7 shows the code for the Deque class. import java.util.*; import java.io.*; public class Deque implements List { //The Node is declared as an inner class with protected data //members and constructors protected class Node implements Serializable

Page 204: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 26 of 42

{ /** * The field item is a java Object Type. This allows the classes * using Node as data member to store any Java object. */ protected Object item; /** * The field next is the reference to another Node, thereby making a Node * as a self referential structure. */ protected Node next; //constructors /** * the argument-less constructor will initialize the item and link field * both to null. */ protected Node() { this(null); } /** * One argument constructor will take a list item as an argument * and the item field of the Node will be initialized to it. The next * field is set to null. * @param init_item is the initail value of item. */ protected Node(Object init_item) { this(init_item, null); } /** * Two argument constructor, constructs Node with the specified * item and link to another Node. * @param init_item is the initial value of item. * @param link is the initail value of next. */ protected Node(Object init_item, Node link) { this.item = init_item; this.next = link; } }//end of inner class Node //Other fields

Page 205: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 27 of 42

protected Node front; protected Node rear; protected int numberElements; //Implementing the interface methods public void makeEmpty() { while(front != null) { Node previous = front; front = front.next; previous = null; } this.numberElements = 0; rear = null; } public void addFront(Object obj) { //Create a node with next pointing to front Node newNode = new Node(obj,front); this.front = newNode; //If list was originally empty then front and rear must be same if(this.numberElements == 0) this.rear = this.front; //Increase the number of elements by one this.numberElements++; } public void addRear(Object obj) { Node newNode = new Node(obj,null); if(this.numberElements == 0) { this.front = newNode; this.rear = newNode; } else { rear.next = newNode; rear = newNode; } this.numberElements++; }

Page 206: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 28 of 42

public void removeFront() throws NoSuchElementException { if(isEmpty()) throw new NoSuchElementException("The list is empty"); else { this.front = this.front.next; this.numberElements--; if(this.numberElements == 0) rear = null; } } public void removeRear() throws NoSuchElementException { if(isEmpty()) throw new NoSuchElementException("The list is empty"); else if(this.numberElements == 1) { this.front = null; this.rear = null; } else { Node previous = front; while(previous.next != rear) previous = previous.next; previous.next = null; rear = previous; } this.numberElements--; } public boolean isEmpty() { return this.numberElements == 0; } public int size() { return this.numberElements; }

Page 207: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 29 of 42

public Object front() throws NoSuchElementException { if(isEmpty()) throw new NoSuchElementException("The list is empty"); else return front.item; } public Object rear() throws NoSuchElementException { if(isEmpty()) throw new NoSuchElementException("The list is empty"); else return rear.item; } public boolean contains(Object obj) { Node current = front; while(current != null && !current.item.equals(obj)) current = current.next; return current != null; } public Iterator elements() { Vector V = new Vector(); Node current = front; while(current != null) { V.addElement(current.item); current = current.next; } return V.iterator(); } }//end of class Listing 8.7 The restricted nature of deque as a limited Singly Linked List is clear from the code in Listing 8.7. Only transforming methods left are the addFront ( ), addRear ( ) , removeFront ( ), and removeRear ( ). The Deque can be used as a stack by using either the methods addFront ( ) and removeFront ( ) or addRear ( ) and removeRear ( ). It can also be used as a FIFO queue by using combination of addRear ( ) and removeFront ( ) methods. Listing 8.8 shows a class and corresponding code to test the Deque as a stack and FIFO queue both.

Page 208: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 30 of 42

import javax.swing.*; public class TestDeque { public static void main(String [] args) { //Test Deque as a stack. We add and remove from front only Deque Deq1 = new Deque(); boolean done = false; String Input = ""; while(!done) { Input = JOptionPane.showInputDialog("Please enter the " + " string, characters and numbers to push on the stack (Using Deque as a stack) " +" Click cancel when done."); if(Input== null) done = true; else { Input = Input.trim(); Deq1.addFront(Input); } } JOptionPane.showMessageDialog(null, "The size of stack is " + Deq1.numberElements); JOptionPane.showMessageDialog(null, "The top element on stack is " + Deq1.front()); String Out = ""; while(!Deq1.isEmpty()) { Out = Out + " " + (String)(Deq1.front()); Deq1.removeFront(); } JOptionPane.showMessageDialog(null, "Printing the deque being used as a stack after popping it.\n" + Out); //Testing Deque as a FIFO queue. We add to the rear and remove from front testQueue(); System.exit(0); } public static void testQueue() { Deque Queue1 = new Deque();

Page 209: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 31 of 42

boolean done = false; String Input = ""; while(!done) { Input = JOptionPane.showInputDialog("Please enter the " + " string, characters and numbers to enter in the queue " + " Click cancel when done."); if(Input== null) done = true; else { Input = Input.trim(); Queue1.addRear(Input); } } JOptionPane.showMessageDialog(null, "The size of queue is " + Queue1.numberElements); String Out = ""; while(!Queue1.isEmpty()) { Out = Out + " " + (String)(Queue1.front()); Queue1.removeFront(); } JOptionPane.showMessageDialog(null, "Printing the queue after dequeuing it.\n" + Out); } } Listing 8.8 Note that it become client’s responsibility to call the methods addFront ( ), addRear ( ), removeFront ( ) and removeRear ( ) in proper combination to use the Deque either as a Stack or as a FIFO queue. Doubly Linked List In a doubly linked list, each node can point to its predecessor as well as its successor. Therefore, the Node class used for a doubly linked list has an extra self-referential data member. Schematically a doubly linked list will look similar to Figure 8.23.

Page 210: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 32 of 42

FIG. 8.23 The presence of references to predecessor and successor node in each node allows bi-directional traverse in a doubly linked list. This makes the navigation through a doubly linked list faster than the singly linked counterpart does. However, in a doubly linked list, the insertion and removal of a node requires making or breaking twice as many links. Consider a doubly linked list shown in Figure 8.24.

FIG 8.24 The node “Jones” points to nodes “Baker” as well as to “Smith”. In order to remove node “Jones”, we would need to have the next field of “Baker” point to “Smith”, and have the before field (the left arrow) of “Smith” point to “Baker”. Then we would need to break the linkage of “Jones” with both “Baker” and “Smith” (Figure 8.25).

Page 211: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 33 of 42

FIG. 8.25 The similar deletion in a singly linked list will require breaking or establishing only half as many links. The corresponding reference changes when inserting a node in a doubly linked list are shown in Figure 8.26.

FIG. 8.26 Listing 8.9 shows the code for a doubly linked list class and a main method to test the key class methods. import java.util.*; import java.io.*; import javax.swing.*; public class DoublyLinkedList implements List { //The Node is declared as an inner class with protected data //members and constructors protected class Node implements Serializable {

Broken links.

Page 212: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 34 of 42

/** * The field item is a java Object Type. This allows the classes * using Node as data member to store any Java object. */ protected Object item; /** * The field next is the reference to successor Node. */ protected Node next; /** * The field previous is the reference to predecessor Node. */ protected Node before; //constructors /** * the argumentless constructor will initialize the item and link field * both to null. */ protected Node() { this(null); } /** * One argument constructor will take a list item as an argument * and the item field of the Node will be initialized to it. The next * field is set to null. * @param init_item is the initail value of item. */ protected Node(Object init_item) { this(init_item, null); } /** * Two argument constructor, constructs Node with the specified * item and link to prdecessor Node. * @param init_item is the initial value of item. * @param link_prev is the initail value of before. */ protected Node(Object init_item, Node link_next) { this(init_item,link_next, null); } /** * Three argument constructor, constructs Node with the specified

Page 213: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 35 of 42

* item and link to predecessor and successor Node. * @param init_item is the initial value of item. * @param link_prev is the initail value of before. * @param link_next is initail value of next */ protected Node(Object init_item, Node link_next, Node link_prev) { this.item = init_item; this.before = link_prev; this.next = link_next; } }//end of inner class Node //Other fields protected Node front; protected Node rear; protected int numberElements; /** Insert obj after target object in the list */ public void addAfter(Object obj, Object target) { Node itemNode = getNode(target); if (itemNode==null) throw new NoSuchElementException("addAdter: The node does not exist"); else { // Backlink to itemNode and forward link to itemNode.next Node newNode = new Node (obj, itemNode.next, itemNode); if (itemNode.next != null) itemNode.next.before = newNode; else rear = newNode; itemNode.next = newNode; numberElements++; } } /** Insert obj before target object in the list */ public void addBefore(Object obj, Object target) { Node itemNode = getNode(target); if(itemNode == null) throw new NoSuchElementException("addBefore:: target node does not exist"); else

Page 214: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 36 of 42

{ Node beforeNode = itemNode.before; // Backlink to beforeNode forward link to itemNode Node newNode = new Node (obj, itemNode, beforeNode); if (front == itemNode) front = newNode; else beforeNode.next = newNode; itemNode.before = newNode; numberElements++; } } // Queries /** Return object after target object in the list * Throw NoSuchElementException if target not in the list. * Throw NoSuchElementException if target is last in the list. */ public Object elementAfter(Object target) { if (!this.contains(target) || getNode(target) == this.rear) throw new NoSuchElementException("removeAfter::obj does not exist or is last in list"); else return getNode(target).next.item; } /** Return object before target object in the list * Throw NoSuchElementException if target not in the list. * Throw NoSuchElementException if target is first in the list. */ public Object elementBefore(Object target) { if (!this.contains(target) || getNode(target) == this.front) throw new NoSuchElementException("removeBefore::obj does not exist or is first in list"); else return getNode(target).before.item; } // Internal methods /** * For internal use only.

Page 215: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 37 of 42

* This function, available only within this class * returns the node associated with value. If value is * not present in the list getNode returns null */ protected Node getNode (Object value) { Node node = front; Node result = null; while (node != null) { if ((node.item).equals(value)) { result = node; break; } node = node.next; } return result; } //Imlementing the interface methods public void makeEmpty() { while(front != null) { Node Temp = front; front = front.next; Temp = null; } this.numberElements = 0; rear = null; } public void addFront(Object obj) { //Create a node with next pointing to front Node newNode = new Node(obj,front,null); if(this.numberElements == 0) rear = newNode; else front.before = newNode; front = newNode; //Increase the number of elements by one this.numberElements++; }

Page 216: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 38 of 42

public void addRear(Object obj) { Node newNode = new Node(obj,null, rear); if(this.numberElements == 0) { this.front = newNode; } else { rear.next = newNode; } rear = newNode; this.numberElements++; } public void removeFront() throws NoSuchElementException { if(isEmpty()) throw new NoSuchElementException("The list is empty"); else { if(front.next !=null) front.next.before =null; front = front.next; this.numberElements--; if(this.numberElements == 0) rear = null; } } public void removeRear() throws NoSuchElementException { if(isEmpty()) throw new NoSuchElementException("The list is empty"); else if(this.numberElements == 1) { this.front = null; this.rear = null; } else { Node previous = rear.before; previous.next = null;

Page 217: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 39 of 42

rear = previous; } this.numberElements--; } public boolean isEmpty() { return this.numberElements == 0; } public int size() { return this.numberElements; } public Object front() throws NoSuchElementException { if(isEmpty()) throw new NoSuchElementException("The list is empty"); else return front.item; } public Object rear() throws NoSuchElementException { if(isEmpty()) throw new NoSuchElementException("The list is empty"); else return rear.item; } public boolean contains(Object obj) { Node current = front; while(current != null && !current.item.equals(obj)) current = current.next; return current != null; } public Iterator elements() { Vector V = new Vector(); Node current = front; while(current != null)

Page 218: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 40 of 42

{ V.addElement(current.item); current = current.next; } return V.iterator(); } /** Delete object after target object in the list * Throw NoSuchElementException if target not in the list. * Throw NoSuchElementException if target is last in the list. */ public void removeAfter(Object target) throws NoSuchElementException { Node itemNode = getNode(target); if (itemNode == null) throw new NoSuchElementException("Element is not in the list"); else if (itemNode == rear) throw new NoSuchElementException("No element after the last element in the list"); else { if(itemNode.next == rear) { itemNode.next = null; rear.before = null; rear = itemNode; } else { Node targetNode = itemNode.next; itemNode.next = targetNode.next; targetNode.next.before = itemNode; targetNode.next = null; targetNode.before = null; targetNode = null; } numberElements--; } } /** Delete object before target object in the list * Throw NoSuchElementException if target not in the list. * Throw NoSuchElementException if target is first in the list. */ public void removeBefore(Object target) throws NoSuchElementException {

Page 219: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 41 of 42

Node itemNode = getNode(target); if (itemNode == null || itemNode == front) throw new NoSuchElementException( "Element is not in the list or it is the first element in the list."); else { Node Prev = itemNode.before; if(Prev == front) { front = itemNode; itemNode.before = null; Prev.next = null; Prev = null; } else { Prev.before.next = itemNode; itemNode.before = Prev.before; Prev.next = null; Prev.before = null; Prev = null; } this.numberElements--; } } static public void main(String[] args) { DoublyLinkedList myList = new DoublyLinkedList(); myList.addFront(new Integer(3)); myList.addFront(new Integer(2)); myList.addFront(new Integer(1)); myList.addRear(new Integer(4)); myList.addRear(new Integer(6)); myList.addRear(new Integer(8)); myList.addBefore(new Integer(5), new Integer(6)); myList.addAfter(new Integer(7), new Integer(6)); Iterator iter = myList.elements(); while (iter.hasNext()) System.out.println(iter.next()); System.out.println("Element before 6 is " + myList.elementBefore(new Integer(6))); System.out.println("Elment after 6 is " + myList.elementAfter(new Integer(6))); //Test removeAfter

Page 220: E_Book_JavaDataStructure_SatishSinghal

Linked Stacks and Queues Page 42 of 42

myList.removeAfter(new Integer(1)); Node Temp = myList.getNode(new Integer(2)); if(Temp == null) System.out.println("Node not found"); else System.out.println("Node found"); iter = myList.elements(); while (iter.hasNext()) System.out.println(iter.next()); //Test removeBefore myList.removeBefore(new Integer(3)); Temp = myList.getNode(new Integer(1)); if(Temp == null) System.out.println("Node not found"); else System.out.println("Node found"); iter = myList.elements(); while (iter.hasNext()) System.out.println(iter.next()); } }//end of class Listing 8.9 One would note that now the inner class Node has an extra self-referential field called before, which may be used to point to a predecessor node. The DoublyLinkedList class has most of the same method as in SinglyLinkedList class. However, the method nodeBefore ( ) from SinglyLinkedList class is no longer needed as in a doubly linked list one can have a bidirectional travel from any member node. The output of the main method is shown in the Figure 8.27.

FIG. 8.27

Page 221: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 1 of 22

CS 20 B : Java Data Structures

Topic 9 : Recursion Author: Satish Singhal Ph. D.

Version 1.0 Recursion is a construct that allows a method to call itself, thereby iterating the same piece of code, with out using a looping structure. Therefore, the definition of a recursive method includes a call to itself. In Listing 9.1 we show the definition of a method recursive ( ) which calls itself. public void recursive( ) { recursive( ); } Listing 9.1 The above example shows a recursive method, though it is quite useless, as method calls itself endlessly – ending in an infinite recursion – and method does not really do anything useful. In fact, if the above method is called from any java methods, the program will crash with a StackOverflowError. This indicates that recursion is processed by using a stack data structure we have discussed previously. Well Behaved Recursion The recursion example shown in Listing 9.1 is not a well-behaved recursion. We therefore, need to understand the properties that make recursion well behaved and useful. We discuss this in terms of essential properties, terminologies, and steps in executing recursion. Essential Properties and Terminology: We discuss the properties for recursive methods as follows: Property #1: A recursive method, algorithm, or implementation is defined in terms of itself. A classic example for recursion is taken from mathematics, where we find the factorial of a positive integer. We define, N! = N * (N – 1) ! Since (N-1)! Is defined as, (N-1)! = (N-1) * (N-2)!, the process continues until the term on right hand side becomes zero, and factorial of zero is one, at which point the evaluation can stop. We can therefore, write a recursive method to find the factorial of an integer as follows (Listing 9.2). public int factorial(int Num) { return Num * factorial(Num – 1); }

Listing 9.2

Page 222: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 2 of 22

The implementation of Listing 9.2 has the same problem, as did the Listing 9.1. It would cause an infinite recursion. We therefore need a logical way to stop the recursion. This leads us to the second property, which every well-behaved recursive construct must have. This requires that at least one parameter passed to a recursive method have a sentinel value, which is used to stop the recursion. If recursive method does not take any parameters, then sentinel parameter must be used inside it. Note that sentinel value would be an impossible value for the parameter. Property #2 A well-behaved recursive method, algorithm, or implementation must have a sentinel parameter. The recursion would continue until the sentinel value of the parameter is reached, at which point a conditional test may be used to stop the recursion. For example for the method in Listing 9.2, the parameter Num would have a sentinel value of zero. When Num reaches a zero value, the recursive call is not made. Rather at that point, the method must return the factorial of zero, which is one. We can make the Listing 9.2 a well-behaved recursion by incorporating the property #2 and re-writing it as Listing 9.3. public int factorial(int Num) { if(Num > 0) return Num * factorial(Num – 1); else return 1; } In Listing 9.3, the property #2 is enforced by testing for a sentinel value for the parameter Num. Method factorial calls itself only for the non-sentinel values for the parameter Num. At the same time, with each recursive call, the argument to the method is brought closer to the sentinel value of zero, since the recursive call is made with argument Num-1. Property # 3 The third property for a well-behaved recursion is closely related to the property #2 and has already been included in the Listing 9.3. It can be stated as: A recursive method, algorithm, or implementation must drive the value of its sentinel parameter in a way to eventually stop the recursion. The property #3 is applied in Listing 9.3, because the each recursive call to factorial ( ) is made with the value of Num reduced by one, pushing the parameter Num successively towards the sentinel value of zero. When the Num reaches the value of Zero, the else part of the method factorial ( ) is executed and recursion is stopped. Sometimes the sentinel value is also referred to as a base case.

Listing 9.3

Only executed for non-sentinel values of Num

Argument to the method factorial ( ) reduces by one with every recursive call, thereby each time bringing it closer to the sentinel value of zero.

Page 223: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 3 of 22

Additional Terminology: The following terms are often used to describe recursion: Steps in Executing Recursion: We show the steps of going into the recursion one level at a time and then backing out of it, by using the Visual C++ compiler and calling the function factorial from the main function. The logic in java remains exactly same. Listing 9.4 shows the program. #include <iostream> using namespace std; int factorial(int Num) { if(Num>0) return Num*factorial(Num-1); else return 1; } void main() { int Num = 0; cout<<"Please enter the positive integer, whose factorial you need "; cin>>Num; int result = factorial(Num); cout<<"The factorial of "<<Num<<" is "<<result<<endl; } Listing 9.4 Figure 9.1 shows the program status, just before the call to function factorial is made from the main ( ). The bottom left pane shows the variable values, and the function call stack is shown on the bottom right pane.

Recursion Level: Each recursive method call has its own level. The first recursive call is when we enter the recursion at level one, the second call at level two and so on. Going in/Backing out: When we enter the recursion at certain level we call that “going in”. Entry to each subsequent level is going in at a deeper level. The “going in” stops when the sentinel or base case has been reached. Then the process of “backing out” from the piled up recursion calls begins. A well-behaved recursion goes into n levels (one level at a time) and then “backs out “ of it (one level at a time).

Page 224: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 4 of 22

FIG. 9.1 The value of Num entered by the user is four, and the variable result has no value at this time. When we execute the program further, the control is transferred to the function factorial (Figure 9.2).

Page 225: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 5 of 22

FIG. 9.2 The function call stack on the bottom right pane clearly shows that the control is now transferred to function factorial. Figure 9.3 shows the situation, when we are ready to “go into” the second level of recursion.

FIG. 9.3

First level call. Num = 4

Page 226: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 6 of 22

Further execution starts the recursive calls to the function factorial (Figure 9.4).

FIG. 9.4 The Figures 9.5 to 9.7 shows the process of going in up to level five when the Num becomes zero.

“going in” at level # 2, Num = 3.

Page 227: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 7 of 22

FIG. 9.5

Going in at level 3. Num = 2

Page 228: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 8 of 22

FIG. 9.6

Going in at level 4. Num = 1.

Page 229: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 9 of 22

FIG. 9.7

Going in at level 5. Num = 0. The sentinel is reached.

Page 230: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 10 of 22

The Figure 9.8 shows when the program is ready to back out of the recursion. For the value of Num = 0, the backing out of level five will provide a return value of one.

FIG. 9.10 In process of backing out, each return value is used at each level on the stack that was there while going in. For example the level 4 which had Num = 1, gets a return value of one as we back out from level 5 (Figure 9.11).

Program control is on last line of function factorial. It is ready to back out of level 5, with a return value of one.

Page 231: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 11 of 22

FIG. 9.11 The figures 9.12 to 9.14 show the state where the recursion is ready to back out of levels 3, 2, and 1. The return value keeps changing at each level.

Backing out of level 5 provided a return value of 1. Now ready to back out from the level 4, where Num was equal to 1. Note that 1! Is also 1.

Page 232: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 12 of 22

FIG. 9.12

Backing out of level 4 also provided a return value of 1, because the factorial of 1 is also 1. Now ready to back out from the level 3, where Num was equal to 2. Note that 2! s also 2.

Page 233: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 13 of 22

FIG. 9.13

Backing out of level 3 provided a return value of 2, because the factorial of 2 is also 2. Now ready to back out from the level 3, where Num was equal to 3. Note that 3! Is 6.

Page 234: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 14 of 22

FIG. 9.14 When the recursion finally backs out of level one, the program control is given back to main (Figure 9.15) and a value of 24 for the factorial of four is returned.

Backing out of level 2 provided a return value of 6, because the factorial of 3 is 6. Now ready to back out from the level 1, where Num was equal to 4. Backing out of level 1 takes the control back to main.

Page 235: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 15 of 22

FIG. 9.15 This completes the process of the call to a recursive function factorial. The java version of Listing 9.4 is given in Listing 9.5. public class tryFact { private static int factorial(int number) { if (number> 0) return (number * factorial (number - 1)); else return 1; } public static void main(String[] args) { System.out.println("5! = " + factorial(5)); } } Listing 9.5 We can summarize the process of “going in” into a recursion and backing out by using the Figure 9.16.

Page 236: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 16 of 22

FIG. 9.16 In Figure 9.16, the call number on vertical left axis gives us the level of recursion. The downward arrows from top left corner to the bottom right corner indicate the progress of “going in” into the recursion. The arrows from bottom right towards the top left corner indicate the direction of “backing out” from the recursion, when at level five, the sentinel has been reached for the parameter number. Printing a Singly Linked List Recursively In Chapter 7, we discussed the singly linked list and, there we used a piece of code to print linked list iteratively (using the while loop). Here we would like to show as to how to use a recursive solution to print the linked list. The code fragment to print the SinglyLinkedList iteratively is reproduced in Listing 9.6. Iterator iter = myList.elements( ); While(iter.hasnext( )) { System.out.println(iter.next( ) ); }

“Going in” into recursion

“backing out “ from recursion

Recursion level

Listing 9.6

Page 237: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 17 of 22

The method printListRecursive ( ) , which uses recursion to print SinglyLinkedList class is given in Listing 9.7. public static void printListRecursive(Iterator iter) { if(iter.hasNext( )) { System.out.println (iter.next( )); PrintListRecursive (iter); } } Listing 9.7 The sentinel is reached in the method printListRecursive ( ) when hasNext ( ) returns a false, and the recursion stops. Double Recursion When a recursive method makes two recursive calls to itself that is called a double recursion. In Listing 9.8, inside class Recursion2, we have two methods, inOrderPrint ( ) and permute ( ) which include two calls to themselves. /** A simple class to illustrate double recursion*/ import java.io.*; public class Recursion2 { // illustrate double recursion by printing values public void inOrderPrint (int index) { if (index > 0) { inOrderPrint(index/2); System.out.println("Index = " + index); inOrderPrint(index/2); } } // compute and display all permutations of an array of characters // uses a variation of double recursion public void permute (char[] inArray, int size) { int index = size - 1;

Learning Exercise 9.1: Write a method printIntList, which will take the head of the IntList as an argument (taught in topic 7), and print this integer list recursively. Remember that head will also be an Object of type IntList.

Page 238: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 18 of 22

char temp; if (size > 1) { permute(inArray, index); for (int i = index - 1; i >= 0; i--) { temp = inArray[index]; inArray[index] = inArray[i]; inArray[i] = temp; permute(inArray, index); temp = inArray[index]; inArray[index] = inArray[i]; inArray[i] = temp; } } else { for (int j = 0; j < inArray.length; j++) System.out.print(inArray[j]); System.out.println(); } } public static void main (String[] args) throws IOException { char[] inArray; int index; Recursion2 recurse = new Recursion2(); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Enter an index (int) for doubly-recursive print: "); index = (new Integer(br.readLine())).intValue(); recurse.inOrderPrint(index); System.out.print("Enter a string for permutation (keep it short): "); String str = br.readLine(); inArray = new char[str.length()]; str.getChars(0, str.length(), inArray, 0); // copy str into inArray recurse.permute(inArray, inArray.length); } } Listing 9.8

Page 239: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 19 of 22

We discuss, only the detailed logic of the method inOrderPrint, leaving the explanation for the other method for exercise. Assume that inOrderPrint is being called with an integer argument of seven. Then the method call will look like as follows from the main: InOrderPrint ( 7 ); Figure 9.17 explains the logic of double recursion taking place in method inOrderPrint ( ).

In Figure 9.17, the recursive calls to inOrderPrint ( ) are shown from left to right and progress is shown with red arrows. Green arrows show the process of backing out of recursion. The calls to method inOrderPrint ( ) is abbreviated as IOP ( ). When first call is made to inOrderPrint ( ) with an argument of seven, the call is “going in “ the recursion, shown by top left box IOP( 7 ). Since seven is greater than

IOP(7)

IOP(7/2 = 3)

IOP(3/2 = 1)

prints Index = 1

IOP(1/2 = 0)

prints Index = 3

IOP1/2 = 0)

Recursion stops

IOP(3/2 = 1) IOP(1/2 = 0)

prints Index = 1

prints Index = 7

IOP(1/2 = 0)

IOP(7/2 = 3)

Results are similar to the branch on right. It will print 1, 3, 1 .. in that order

FIG. 9.17

Page 240: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 20 of 22

zero, the code inside the if structure is executed. The first statement inside the if block is a recursive call to inOrderPrint ( ). Therefore the recursive call made is shown by IOP ( 7/ 2 = 3). However, since three is still greater than zero, the next recursive call is made and shown as IOP ( 3/2 = 1) in Figure 9.17. Since one is still greater than zero, the final call made is recursive call with argument of zero shown as IOP ( ½ = 0). When the call is made to InOrderPrint ( ) with an argument of zero, the recursion stops. The portion of the Figure 9.17, reproduced in Figure 9.18 below, shows this progress of the first sequence of recursion. FIG. 9.18 Now the first set of recursion has stopped, therefore the process of backing out of it must begin. Note that in “backing out”, the program must get back to the code line where it left to pursue recursion! This means that the backing out must begin by executing the code line: System.out.println ("Index = " + index); The green arrows show the process of backing out (Figure 9.19). Since the index was (3/2 = 1) one when the last recursion was done, the program first prints “Index = 1”. Following the printing of “Index = 1”, the next statement makes the downward recursive call to inOrderPrint with an argument of (1/2 = 0). This call is shown FIG. 9.19 by the blue box with IOP (1/2 = 0 ) in it. This recursion also stops, since method argument is zero. Then in process of backing out of inOrderPrint (3/2), shown by top blue box, the program gets back to where it left of to make recursive call IOP(7/2 = 3). Then the statement

IOP(7)

IOP(7/2 = 3)

IOP(3/2 = 1) IOP1/2 = 0)

IOP(7)

IOP(7/2 = 3)

IOP(3/2 = 1)

prints Index = 1 prints

Index = 3

IOP1/2 = 0)

prints Index = 7

IOP(1/2 = 0)

Page 241: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 21 of 22

System.out.println ("Index = " + index); Is executed with index = 3, and program prints “Index = 3”. The Figure 9.20 below shows the further progress of this branch of recursion. FIG. 9.20 The recursive call after the print statement is made with an argument of three shown by the amber box with IOP(3/2 = 1) in Figure 9.20. This will lead to a recursive call inOrderPrint (1/2 = 0). This is shown with the box right to the box with IOP(3/2 = 1) in it. That again stops the recursion. In backing out from the recursion shown by the rightmost red box in Figure 9.20, one gets back to the program line where it left. That leads to backing out and printing “Index = 1” shown by bottom most amber box in Figure 9.20. The further recursive call with the argument IOP (1/2 = 0) shown by the bottom most red box stops the recursion again. Program then backs out of the recursive call IOP(7/2 = 3), made by top amber box in Figure 9.20. The Figure 9.21 shows this process separately.

IOP(7/2 = 3)

prints Index = 3

IOP(3/2 = 1) IOP(1/2 = 0)

prints Index = 1

IOP(1/2 = 0)

IOP(7)

IOP(7/2 = 3)

prints Index = 3

prints Index = 7

IOP(7/2 = 3)

FIG. 9.21

Page 242: E_Book_JavaDataStructure_SatishSinghal

Topic 9 Recursion Page 22 of 22

The program gets back to where it left off to go into Recursive call IOP (7), and then executes the statement; System.out.println ("Index = " + index); and prints “Index = 7”. After that program executes the lat recursive call shown by the bottom most yellow box in Figure 9.21 as IOP(7/2 = 3). This recursion will yield the results similar to the ones, shown in Figures 9.18 to 9.20 and print the following three statements: Index = 1 Index = 3 Index = 1. The overall program will print: Index = 1 Index = 3 Index = 1. Index = 7 Index = 1 Index = 3 Index = 1.

Learning Exercise 9.2. Given the methods preOrderPrint ( ) and postOrderPrint ( ) below, explain as to what will be printed by the following calls from the main method to these methods. preOrderPrint(7); postOrderPrint(7); public void preOrderPrint(int index ) { if(index>0){ System.out.println(“Index = “ + index); preOrderPrint(index/2); preOrderPrint(index/2); } } public void postOrderPrint(int index ) { if(index>0){ postOrderPrint(index/2); postOrderPrint(index/2); System.out.println(“Index = “ + index); } }

Page 243: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 1 of 63

CS 20 B : Java Data Structures

Topic 10 : Trees Author: Satish Singhal Ph. D.

Version 1.0 So far, in this course, we have discussed linear data structures. That means that in a linear data structure, unless an element is first or last, every element would have a unique predecessor and a successor. Linear data structures – specially linked lists are very inefficient for searching for some key. The average time to search increases linearly with the size of the linked list. Generally, in terms of big Oh notation of algorithms, this would be an O (n) algorithm, where n is the size of the list. Trees are the non-linear data structures, which can make the process of searching a key much faster than O (n) algorithms. Trees can also allow improved algorithmic efficiencies in inserting and deleting the members from the list. Depending on the scheme to travel a tree, a tree node may not have the same node as its predecessor or successor. This is the reason that tree structure is called a non-linear data structure. Examples of Trees: Most common example of a tree data structure is a directory tree in a Windows or Unix filing system. Starting from the root directory, the folders with in folders span the directory tree. (Figure 10.1)

Page 244: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 2 of 63

Because of inheritance relationship, the classes in java are also arranged in a tree-like manner. One example of the tree-like nature of java classes is shown in Figure 10.2.

Root

Level 1

Level 2

Level 3

FIG. 10.1

FIG. 10.2

Page 245: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 3 of 63

Class Object is the root class or root of the tree. AbstractCollection is the child of Object but has two children, AbstractList and AbstractSet. The AbstractList has three children, AbstractSequenceList, ArrayList, and Vector. The organization chart of a small company or store may also take a tree structure. For example, a hypothetical pizza store called Jake’s Pizza may have the organization tree similar to the Figure 10.3.

FIG. 10.3 From the examples shown before, we notice that in tree data structure the root is on the top. Therefore, one can look at it as an inverted tree. Figure 10.4 shows a generic tree where each node is marked by an alphabet.

Page 246: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 4 of 63

Generic TreeA

B C

F

D

GE H J

Definitions and Terminology: We use the Figure 10.4 to define various terms used to study the tree data structure.

FIG. 10.4

Root: The root of a tree is the node, which has no parent or no predecessor. In Figure 10. 4, the node A is the root of the tree. Children Nodes: All nodes other than the root node are also children nodes because they have parent or predecessor. Parent Node: A node that has children is called a parent node. In Figure 10.4, the nodes A, B, and D are parent nodes. Note that a parent node can be a child to another node. For example, the node B is a child of A, but parent of E and F. In a generic tree, a parent node can have any number of Children. Siblings: The siblings are the children from same parent node. Nodes B, C, and D are siblings. Leaf Nodes or External Nodes: The nodes without children are called leaf nodes. The nodes C. E. F. G. H, and J are leaf nodes. Internal Node: A node that has at least one child is called internal node. Nodes A, B, and D are internal nodes. Degree of a Node: Degree of a node is the number of children the node has. For example, the degree of node A is three. Level or Depth of a Node: The numbers of edges to be traversed from the root to reach a node indicate its level or depth. The level of root node is zero. The level of node E is two. Height of tree: The maximum depth or levels possible in a tree give its height. The height of tree in Figure 10.4 is two.

Page 247: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 5 of 63

The following PowerPoint presentation uses the Figure 10.3, Jake’s Pizza shop organization tree to illustrate some of the definition we discussed earlier.

TreeDefinitions.ppt

Every node of a tree may be considered a sub-tree. For example, Figure 10. 5 and 10.6 show left and right sub-trees of the root node for the tree of Figure 10.3.

FIG. 10.5

Page 248: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 6 of 63

FIG. 10.6 Binary Tree: A general tree shown in Figure 10.4 is simplified further by introducing a constraint, that each node in the tree can only have maximum, two children. This results in a binary tree. Figure 10.6 shows a typical binary tree.

Binary TreeA

B

E

C

G

D

H J

F

null

FIG. 10.6

Right child of A Left child

of A

Leaf nodes. All leaf nodes have two null children.

Page 249: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 7 of 63

Using Figure 10.6, we can define a binary tree as follows: • A Node in a binary tree has two children, one on left and

one on right. • Leaf node or external node has two null children. • An internal node must have one or two non-null children. • An empty binary tree is a null reference. • The degree of a node in a binary tree may be 0, 1, or 2,

depending on whether it has zero, one or two children. • Every node in a binary tree would be considered a tree in

itself, which would have a left sub-tree and a right sub-tree (Figures 10.5 and 10.6).

• For the leaf nodes, the sub-trees are null. Binary Tree Interface We design our binary tree data structure, first by designing the binary tree interface. The Table 10.1 below describes, briefly, the various methods contained in the interface BinaryTree, which would be implemented by a binary tree data structure class.

Method Summary double avgpathLength()

Method avgpathLength returns the average path length for the tree.

boolean isEmpty() Method isEmpty returns a true or false depending upon, whether tree is non-null or not.

void makeEmpty() Method makeEmpty creates a null binary tree.

int maxLevel() Considering the root node at the zero level, the method maxLevel returns the maximum level possible in the tree.

int size() Method size returns the number of members in the tree structure.

java.util.Iterator traverseInorder() Method traverseInorder returns an Iterator, which can be used to traverse the given binary tree in-order.

java.util.Iterator traversePostorder() Method traversePreorder returns an Iterator, which can be used to traverse the given binary tree post-order.

Page 250: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 8 of 63

java.util.Iterator traversePreorder() Method traversePreorder returns an Iterator, which can be used to traverse the given binary tree pre-order.

Table 10.1 The Listing 10.1 gives the java code for the interface BinaryTree. import java.io.*; import java.util.*; /**The interface BinaryTree expresses the design for the Binary Tree * Data structure and its key behaviors. */ public interface BinaryTree extends Serializable { /** * Method makeEmpty creates a null binary tree. */ public void makeEmpty(); /** * Method isEmpty returns a true or false depending upon, whether tree is * non-null or not. * @return true if tree is empty, else returns false. */ public boolean isEmpty(); /** * Method size returns the number of members in the tree * structure. * @return the number of members in the tree structure. */ public int size(); /** * Method traverseInorder returns an Iterator, which can be used to traverse * the given binary tree in-order. * @return Iterator to traverse the tree in-order. */ public Iterator traverseInorder(); /** * Method traversePreorder returns an Iterator, which can be used to traverse * the given binary tree pre-order. * @return Iterator to traverse the tree pre-order. */ public Iterator traversePreorder(); /** * Method traversePreorder returns an Iterator, which can be used to traverse * the given binary tree post-order. * @return Iterator to traverse the tree post-order.

Page 251: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 9 of 63

*/ public Iterator traversePostorder(); /** * Considering the root node at the zero level, the method maxLevel * returns the maximum level possible in the tree. * @return the maximum level or depth of the tree. */ public int maxLevel(); /** * Method avgpathLength returns the average path length for the tree. * @return the double value for the average path length for the binary tree. */ public double avgpathLength(); Listing 10.1 Traversal of a Binary Tree: The traversal of a linear list is non-recursive. Depending upon, whether one is traversing forward or backwards, every node is crossed just once. In traversing a binary tree, however, a node may be crossed more than once. Therefore, traversing a binary tree is a recursive process. Though, in tree traverse, we may cross a node more than once, still we would like to “do something” at that node only once. This “doing something “ at a node is called “visiting” a node. The visiting a node may be an activity as simple as printing the values in the node. The tree traverse will begin with the root node. However, after that we have many choices. Do we visit the left node first or right node? Do we first go to the tree leaves or the internal nodes? In a linear list, there are only two ways to traverse a list – forward and backward. One would see that in a tree, there are many ways to traverse it. The type of traversal depends upon, the relative order in which we visit a root node and its sub-tree. Three common types of binary tree traversals are important. We discuss them using the binary tree in Figure 10.6. In-order Traversal In words, the process of in-order traverse is as follows: 1. Traverse the left sub-tree of a node 2. Visit the node itself 3. Traverse the right sub-tree of the node. The algorithm for in-order travel is as follows: traverseInOrder (left sub-tree) visit (node) traverseInOrder (right sub-tree) The java applet shown on the Internet link below shows the dynamics of in-order travel for a binary tree. Tree Traverse Java Applet

Page 252: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 10 of 63

Note that the algorithm for in-order traverse is very similar to double recursive method inOrderPrint (int index), which we discussed in topic nine for recursion. Now let us see as to what results when we apply this algorithm to traverse the tree in Figure 10.6. Our traversal begins at root node A. The in-order algorithm expands as follows: 1. Traverse left sub-tree of A or traverse LA = {sub-tree B} 2. Visit A 3. Traverse right sub-tree of A or traverse RA = {sub-tree C} The step one above expands further as follows: LA is a binary tree in itself whose traversal will break down as follows: 1.1 Traverse left sub-tree of B or traverse LB = {sub-tree D} 1.2 Visit B 1.3 Traverse right sub-tree of B or traverse RB = {sub-tree E} The step 1.1 expands as follows: 1.11 Traverse left sub-tree of D, which is null. So no further traverse is needed. 1.12 Visit D. This means print D if that is what the visit involves. 1.13 Traverse right sub-tree of D, which is null. So no further traverse is needed. Now the step 1.1 is complete, so we get back to step 1.2. 1.2 Visit B. This means print B if that is what the visit involves. Now we go to step 1.3. 1.31 Traverse left sub-tree of E or LE = {sub-tree G} 1.32 Visit E 1.33 Traverse right sub-tree of E or RE = {null} We cannot go to step 1.32, unless we complete the step 1.31 first. We expand the step 1.31 as follows: 1.311 Traverse left sub-tree of G of LG = { null } . So traversal stops. 1.312 Visit G. This means print G if that is what the visit involves. 1.313 Traverse right sub-tree of G of RG = { null }. So traversal stops. Now we have completed 1.31. therefore we get back to 1.32. 1.32 Visit E. This means print E if that is what the visit involves. Now we expand the step 1.33 as follows: 1.33 Visit the right sub-tree of E, which is null. So traversal stops. All the sub-steps from step one are now completed. So now, we go to step 2, which is simply: 2. Visit A. This means print A if that is what the visit involves. Now we go to step 3 which included traversing the sub-tree C. The step 3 will break down as follows:

Page 253: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 11 of 63

3.1 Traverse left sub-tree of C, which is null, therefore the traversal stops. 3.2 Visit C. This means print C if that is what the visit involves. 3.3 Traverse right sub-tree of C or RC = { sub-tree F }. Since 3.1 stops, we only need to expand the 3.3 further. We expand 3.3 as follows: 3.31 Traverse left sub-tree of F or LF = { sub-tree H } 3.32 Visit F. 3.33 Traverse right sub-tree of F or RF = { sub-tree J } Again, we cannot go to 3.32 unless the 3.31 is complete. We expand the step 3.31 as follows: 3.311 Traverse left sub-tree of H, which is null, hence the traverse stops. 3.312 Visit H. This means print H if that is what the visit involves. 3.313 Traverse right sub-tree of H, which is null, hence the traverse stops. We have now completed the step 3.31, so we can now process to step 3.32. 3.32. Visit F. This means print F if that is what the visit involves. Now we complete the step 3.33. The step 3.33 expands as follows: 3.331 Traverse left sub-tree of J, which is null, so the traverse stops. 3.332 Visit J. This means print J if that is what the visit involves. 3.333. Traverse right sub-tree of J, which is null, so the traversal stops. If we distill the visit order for the in-order traversal then applied to binary tree of Figure 10.6, we get the following order. D B G E A C H F J Figure 10.7 on next page shows the process of in-order traverse for the binary tree of Figure 10.6 graphically. The link below shows the animated GIF for in-order travel. inorder.html

Page 254: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 12 of 63

Binary TreeA

B

E

C

G

D

H J

F

null

IOT(sub B) IOT(sub D)

IOT(null)

Visit or Print D

IOT(null)

Visit or print B

IOT( sub E) IOT(sub G)

IOT(null) Visit or Print G

IOT(null)

Visit or Print E

IOT(A)

IOT(null)

Visit or Print A

IOT( sub C) IOT(null)

Visit or Print C

IOT(sub F) IOT(sub H) IOT(null)

Visit or Print H

IOT(null)

Visit or Print F

IOT( sub J)

IOT(null) Visit or Print J

IOT(null)

inOrderTravel (left sub-tree) Visit the node inOrderTravel (right sub-tree)

In-order traverse of a binary tree FIG. 10.7

D B G E A C H F J

Page 255: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 13 of 63

In Figure 10.7, we show stepping into recursion or stacking of recursive calls by red arrows and backing out of recursion or normal program flow by green arrows. When an in-order traverse is begun on binary tree with root A, since A has a left sub-tree B, the recursive call is made to in-order traverse B (or IOT ( sub B)). But B also has a left sub-tree D and recursive call made is IOT( sub D). However, the left sub-tree of D is null, which stops the recursion. In backing out of recursive call to left sub-tree of D, we get back to normal program flow, which requires printing D ( or visiting D). Since right sub-tree of D is also null, the second part of double recursion of D also stops. We are now finished with the sub-tree D. Since we entered into sub-tree D, from node B, we back-out from sub-tree D to normal program flow for sub-tree B, which requires visiting node B or printing it. Then B has aright sub-tree called E, which has a left sub-tree G. Since left sub-tree of G is null, the recursion stops. In backing out of recursion from left sub-tree for G, we print G and then right sub-tree of G is also null stopping the second part of recursion for sub-tree G. The process described above continues and the in-order traverse yields a print order or visit order as: D B G E A C H F J. Post-order Traversal In words the post-order traversal can be described as follows: 1. Traverse left sub-tree of a node. 2. Traverse right sub-tree of a node 3. Visit the node. The post-order traverse algorithm is as follows: traversePostorder (left sub-tree) traversePostorder (right sub-tree) Visit the node itself. The java applet shown on the Internet link below shows the dynamics of post-order travel for a binary tree. Tree Traverse Java Applet We can draw a figure similar to the Figure 10.7 to show the post-order tree traversal. However, a slightly different pictorial representation may clarify the post-order traversal better. Figure 10.8 A, and B shows the post-order traversal for the binary tree of Figure 10. 6. The link below shows the animated GIF for post-order traversal. postorder.html It is clear from the post-order algorithm, that for each leaf node or for a sub-tree with no children, it simply results in visiting the node.

Page 256: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 14 of 63

Post-Order Traverse

Binary TreeA

B

E

C

G

D

H J

F

null

postOrderTravel (left sub-tree) postOrderTravel (right sub-tree) Visit the node FIG. 10.8 A

Post-order visit Sequence: D G E B H J F C A

Page 257: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 15 of 63

POT(A)

Visit/print A

POT(B)

POT(C)

POT(D)

null null

Visit/print D

POT(E)

POT(G)

nullnull

Visit/print G null

Visit/print E

Visit/print B

null

Visit/print C

POT(F)

Visit/print F

POT(H)

Visit/print H

null null

POT(J)

Visit/print J

nullnull

FIG. 10.8 B

Page 258: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 16 of 63

In Figure 10.8 B, each colored ellipse is a copy of the post-order travel method call. As we enter the deeper into inner ellipses, we enter into deeper levels of recursion. The post-order algorithm requires that for each tree or sub-tree we recursively traverse the left sub-tree, then the right sub-tree and then finally visit the node, whose sub-trees were traversed. Referring to Figure 10.8 A and B, we start with the root of tree the node A. This is shown by the call POT(A) in Figure 10.8 B. The left sub-tree of A is B; therefore, we call traverse post-order the sub-tree B (shown as POT (B)). However, the left sub-tree of B is D, so we traverse post-order the sub-tree D (shown as POT (D)). Node D is a leaf node, which has null left and right sub-trees. Therefore recursion stops. This puts the program control on the last non-recursive line, which requires visiting the node D or printing it. This completes the traversal of left sub-tree of B. Then the second line of post-order traversal algorithm requires traversing the right sub-tree of B. This is shown by the POT(E) in Figure 10.8 B. The sub-tree E has a leaf node G as its left sub-tree. Therefore, the traversal of leaf node G results in visiting or printing G next. The right sub-tree of E is null, therefore the recursion stops, and the third algorithm visits or prints the node E. Now we have completed the right sub-tree of B, so the last line of algorithm visits or prints the node B. This completes the traversal of the left sub-tree of A. Then the second line of algorithm requires the post-order traversal of the right sub-tree of A. This traversal is shown by the lower half of the Figure 10.8 B. This traversal begins with the pre-order traversal of sub-tree C (POT( C ) ). The left sub-tree of C is null, therefore the recursion stops in that direction. But the right sub-tree of C is F, therefore we begin post-order traversal of F, shown by POT(F). However, F has a left leaf node H, and POT(H) results in visit to node H or printing it. Then the right sub-tree of F is a leaf node J, therefore POT(J) results in visiting or printing J. After we finish the traversal of left and right nodes of sub-tree F we visit or print the node F. Finishing POT(F) completes the traversal of right sub-tree of C, and finally the last algorithm line visits or prints C. The end of call POT( C ) finishes the traversal of sub-tree C, and then the last line of algorithm visits or prints node A. Looking at all the highlighted node visit sequence, for the post-order traverse, the tree traversal sequence is: D G E B H J F C A. Using Figure 10.8 B, we can easily understand this sequence if we just follow the blue and green algorithm progress arrows in Figure 10.8 B. Each circle in Figure 10.8 B shows the scope of each recursive method call. Obviously, the last step in each method call has to be visiting or printing the node, which was passed as a method argument to begin with. The post-order call to leaf nodes simply results in visiting or printing them. Number of recursive call inside each circle depends upon the number of descendents each node has.

Page 259: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 17 of 63

Pre-order Traverse

In words the pre-order traversal can be described as follows: 1. Visit the node. 2. Traverse left sub-tree of a node. 3. Traverse right sub-tree of a node The post-order traverse algorithm is as follows: visit the node itself. traversePreoder (left sub-tree) traversePreorder (right sub-tree) The java applet shown on the Internet link below shows the dynamics of pre-order travel for a binary tree. Tree Traverse Java Applet The pre-order traversal algorithm is somewhat simpler compared to the post-order, as in this case, we visit a node before we recurs to its left or right sub-tree. The dynamic GIF link below also shows the pre-order traversal in a binary tree. preorder.html We use Figures 10.9 A and B to explain the pre-order traversal further. Pre-Order Traverse

Binary TreeA

B

E

C

G

D

H J

F

null

Visit the node preOrderTravel (left sub-tree) preOrderTravel (right sub-tree)

Pre-order Visit sequence: A B D E G C F H J

FIG. 10.9 A

Page 260: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 18 of 63

POT(A)

POT(B

POT(C

POT(D)

null null

Visit or print D

POT(E

POT(G)

nullnull

Visit or print G

null

Visit or print E

Visit or print B

null

Visit or print C POT(F)

Visit/print F

POT(H)

Visit/print H

null null

POT(J)

Visit/print J

null null

Visit or print A

FIG. 10.9 B

Page 261: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 19 of 63

Using Figure 10.9, we describe pre-order traversal as follows: The algorithm requires that when we enter a tree or sub-tree, first we visit the node, then we call the algorithm recursively on left sub-tree and then on right sub-tree. Entering the binary tree of Figure 10.9 A, we encounter node A, so we visit or print node A. The left sub-tree of A is B, therefore applying the algorithm to B we visit or print B. B has a left sub-tree which is a leaf node D, which simply gets visited as no further recursion is necessary for a leaf node. Therefore we just Visit or print D. Now to complete the second recursive call using sub-tree B, we apply the algorithm to the right sub-tree of B, which is E. This results in first visiting or printing E. However, E has a left sub-tree G, which is a leaf node. The application of pre-order algorithm to G simply results in visiting or printing G. This completes the recursive call to the left sub-tree of A. Now we progress to the last line of algorithm for the tree A, which includes applying the pre-order algorithm to the right sub-tree of A, which is C. This results first in visiting or printing C. However, C has null left sub tree, so we directly go to its right sub-tree, which is traversing sub-tree F. This results in visiting or printing F. However, F has a leaf node H as its sub-tree, which simply is visited or printed. In addition, the process finally completes by visiting or printing the right leaf node of F, which is J. The overall visit sequence for pre-order traversal becomes: A B D E G C F H J Mapping the tree traversal into appropriate iterators: It was easy to create an iterator for a linear list like linked list. If you would recall, the method elements ( ) of singly linked list returned to us an object of type java.util.Iterator which contained the elements from the list placed in a java.uti.Vector, with list element order being preserved. The class java.util.AbstractList, which is a super class of class java.util.Vector, has a method called iterator that simply returns an Iterator to the particular vector. In order to traverse a binary tree in a given order such as in-order, post-order, or pre-order, we need a tree iterator, which would traverse the tree as easily as we could in case of linked list. This requires creating the following three, iterator objects for the binary tree.

1. An iterator object, which would traverse the binary tree in in-order sequence. Let us call the class for this object as TreeInorderIterator.

2. An iterator object, which would traverse the binary tree post-order sequence. Let us call the class for this object as TreePostorderIterator.

3. An iterator object, which would traverse the binary tree pre-order sequence. Let us call the class for this object as TreePreorderIterator.

Page 262: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 20 of 63

However, the above three iterators would have some common behavioral properties. For example, they will all need to implement the interface java.util.Iterator, and have a method to build the object java.uti.Vector, that would put elements from the tree into the vector in order required by the type of traversal. (For example, the TreePreorderIterator will require that the objects in the vector be placed in the pre-order traversal sequence.) To express our design for the three iterator classes we need we write an abstract class TreeIterator, which implements the interface java.util.Iterator, which will have the methods that could be implemented by the derived iterator classes. We express this design in the Table 10.3, and give the source code of class TreeIterator in Listing 10.3. However, writing of this abstract class in any meaningful way is not possible with out introducing another necessary concept – the binary search tree. Binary Search Tree. A general binary tree has only one restriction, that every parent can only have maximum two children. This concept is further simplified by placing restriction in the manner in which left or right child to a parent node is chosen. This restriction is implemented by introducing the following constraint:

1. In a binary search tree, it is needed, that left non-null child of a node is lower in the natural order than the parent is, whereas the right child is higher in the order. This also requires that no two elements in a binary search tree (BST) be identical. Thus, a BST behaves like a mathematical set.

For atomic data such as int, float, char etc. the natural order is evident. Java classes can implement an interface java.lang.Comparable involving implementation of its method compareTo, to specify as to what would constitute the natural order for the objects of that class. Therefore, following the requirement two below helps to design highly versatile binary search tree classes in java.

2. Every object in a binary search tree is an object of type java.lang.Comparable.

The above constraints make it easy to formulate a strategy to construct or build a binary search tree. All java wrapper classes, such as Integer, Double, and Character etc., implement the interface java.lang.Comparable, as they have a natural order. Let us see as to how we would build or insert Integer objects in a binary search tree. We demonstrate the process by using this Java Applet link. Later on, we write more details of this process. Let us assume that we wish to build a binary search tree (BST) for the following Integer objects: 50, 40, 60, 70, 20, 10, 25, 65, 90, and 15. The process is shown by the following dynamic PowerPoint presentation.

Page 263: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 21 of 63

BuildingBinarySearchTree.ppt

Figure 10.10 shows various stages of inserting data values in the binary search tree. The first node to be added is 50, which is added as a root node (Figure 10.10A).

FIG. 10.10 A The next node to be added is 40, which is less than 50. Therefore, the node 40 must be added as a left sub-tree to root node 50 (Figure 101.10B).

FIG. 10.10 B Node 60 is added as the right sub-tree to 50, and node 70 is added as the right sub-tree to 60. Node 20 is added as the left sub-tree to 40 (Figure 10.10C).

Added the root node. Ready to add node = 40.

Added nodes 50 and 40

Page 264: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 22 of 63

Following the procedure that starting from root node if the value to be added is lower than any node then we keep traveling to left until we reach a leaf node. If the value is lower than the leaf node then we add it to its left. Otherwise, we add it to its right. The reverse procedure is to be followed if the value is higher than the root node. This procedure completes the construction of binary search tree (Figures 10.10 D and E).

FIG. 10.10 D

Added Nodes 50, 40, 60, 70, and 20. Ready to add node 10.

FIG. 10.10C

Inserted all items except 90 and 15.

Page 265: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 23 of 63

FIG. 10.10 E Class SearchTreeNode A java class, which would follow the properties of a binary search tree node, is given in Table 10.2 and Listing 10.2.

Field Summary (package private)

java.lang.Comparable contents

contents is the object in the SearchTreeNode, which contains the data part of the node.

(package private) SearchTreeNode

left left is the self-referential reference pointing to the left child of the node.

(package private) SearchTreeNode

right

right is the self-referential reference pointing to the right child of the node.

Constructor Summary (package private)

SearchTreeNode() The argument-less constructor initializes all fields to null creating a null tree.

(package private)

SearchTreeNode(java.lang.Comparable obj)

The single argument constructor takes an object of type

Tree complete

Page 266: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 24 of 63

java.lang.Comparable and initializes the contents to it. (package private)

SearchTreeNode(java.lang.Comparable obj, SearchTreeNode init_left) Two argument constructor initializes the contents to the first argument, and reference left to the second argument.

(package private)

SearchTreeNode(java.lang.Comparable obj, SearchTreeNode init_left, SearchTreeNode init_right) Three argument constructor initializes the contents to the first argument, and reference left to the second argument, and reference right to third argument.

Method Summary (package private)

boolean isInternal()

Tests whether a node is internal or not. java.lang.String toString()

Over-rides super class java.lang.Object class method toString( ) to return the string representation of contents.

Table 10.2 import java.io.*; class SearchTreeNode implements Serializable // Has package access only { /**

• contents is the object in the <code>SearchTreeNode</code>, • which contains

* the data part of the node. */ Comparable contents; /** * left is the self-referential reference pointing to the * left child of the node. */ SearchTreeNode left; /** * right is the self-referential reference pointing to the * right child of the node. */ SearchTreeNode right; /** * The argument-less constructor initializes all fields to null creating a * null tree. */ SearchTreeNode() { this(null); } /** * The single argument constructor takes an object of type

Page 267: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 25 of 63

* <code>java.lang.Comparable</code> and initializes the contents to it. * @param obj is the value of the field contents. */ SearchTreeNode(Comparable obj) { this(obj, null); } /** * Two argument constructor initializes the contents to the first * argument, and reference left to the second argument. * @param obj is the value of the field contents. * @param init_left is the value of left */ SearchTreeNode(Comparable obj,SearchTreeNode init_left ) { this(obj, init_left, null); } /** * Three argument constructor initializes the contents to the first * argument, and reference left to the second argument, and reference * right to third argument. * @param obj is the value of the field contents. * @param init_left is the value of left * @param init_right is the value of right */ SearchTreeNode(Comparable obj,SearchTreeNode init_left, SearchTreeNode init_right) { contents = obj; left = init_left; right = init_right; } /** * Over-rides super class java.lang.Object class method toString( ) to return * the string representation of contents. * @return the <code>String</code> representation of field contents */ public String toString() { return contents.toString(); } /** * Tests whether a node is internal or not. * @return true if node is an internal node else returns false. */ boolean isInternal() { return (left != null || right != null); }

} Listing 10.2

Page 268: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 26 of 63

Tree Iterator Class Now we write an abstract class called TreeIterator, which has the fields and methods that would be inherited by the three iterator classes we would write. The iterator classes needed to traverse a Binary Search Tree (BST) would be the ones, which would allow us to traverse a BST in in-order, pre-order or post-order. Table 10.3 gives the design description of the class TreeIterator.

Field Summary protected int index

Field index keeps the location up to which the elements from Vector v have been returned when calling the method next( ).

protected int size Field size gives the number of elements in the Vector v

protected java.util.Vector

v The field v is the Vector which contains the elements of type SearchTreeNode in a sequence traversed by the particular iterator (in-order, pre-order, or post-order).

Constructor Summary TreeIterator(SearchTreeNode root)

The constructor takes the root - an object of type SearchTreeNode and places its elements in the Vector v, in the order required by a particular Iterator.

Method Summary protected

abstract void buildVector(SearchTreeNode node)

Abstract method buildVector is to be implemented by the subclass.

protected void finalize() Method finalize removes all elements from the vector v and prepares for the garbage collection.

boolean hasNext() Returns true if the iteration has more elements.

java.lang.Object next() Returns the next element in the iteration.

void remove() The remove method from the interface java.util.Iterator is not needed, so it has a null implementation.

Table 10.3 Listing 10.3 gives the java code for the abstract class TreeIterator. import java.util.*; import java.io.*; abstract class TreeIterator implements Iterator {

Page 269: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 27 of 63

/** * The field v is the <code>Vector</code> which contains the elements * of type <code>SearchTreeNode</code> in a sequence traversed by the * particular iterator (in-order, pre-order, or post-order). */ protected Vector v; /** * Field index keeps the location up to which the elements from * Vector v have been returned when calling the method next(). */ protected int index; /** * Field size gives the number of elements in the Vector v */ protected int size; /**The constructor takes the root - an object of type * <code>SearchTreeNode</code> and places its elements in the * <code>Vector</code> v, in the order required by a particular * Iterator. * @param root is the root node for the tree to which the * Iterator is assigned. */ public TreeIterator(SearchTreeNode root) { v = new Vector(); buildVector(root); v.trimToSize(); index = 0; size = v.size(); } /** * The remove method from the interface java.util.Iterator is * not needed, so it has a null implementation. */ public void remove() { //Not used. Null implementation } /** * Returns true if the iteration has more elements. * (In other words, returns true if method next( ) would return an * element rather than throwing an exception.) * @return true if the iterator has more elements. */ public boolean hasNext() { return index<size; } /** * Returns the next element in the iteration. * @return the next element in the iteration. * @throws NoSuchElementException - iteration has no more

Page 270: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 28 of 63

elements. */ public Object next() { Object obj = v.elementAt(index); index++; return obj; } /** * Abstract method buildVector is to be implemented by the * subclass. */ protected abstract void buildVector(SearchTreeNode node); /** * Method finalize removes all elements from the vector v * and prepares for the garbage collection. */ protected void finalize() { v.removeAllElements(); } }

Listing 10.3 Concrete Tree Iterator classes The purpose of three concrete tree iterator classes is to obtain an iterator object to a BST where each node is of type SearchTreeNode, where depending upon the type of iterator, the tree may be traversed in-order, post-order or pre-order. Thus the tree iterator classes, which extend the abstract class TreeIterator are: TreeInorderIterator, TreePostOrderIterator, and TreePreOrderIterator. Table 10.4 shows the design details of TreeInorderIterator class, and java code is given in Listing 10.4. Fields inherited from class TreeIterator index, size, v

Constructor Summary TreeInorderIterator(SearchTreeNode root)

The constructor takes the root of the binary search' tree as an argument and passes to its super class to complete the constructor call.

Method Summary protected

void buildVector(SearchTreeNode node)

The method buildVector build the in-order vector for the tree whose root node is passed as an argument to the constructor.

Methods inherited from class TreeIterator finalize, hasNext, next, remove

Page 271: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 29 of 63

Methods inherited from class java.lang.Object , clone, equals, getClass, hashCode, notify, notifyAll, registerNatives, toString, wait, wait, wait

Table 10.4 The java code for the class TreeInorderIterator is shown in Listing 10.4. class TreeInorderIterator extends TreeIterator{ /** * The constructor takes the root of the bianry search' * tree as an argument and passes to its super class to * complete the constructor call. * @param root is the root node for the tree for which the * iterator is generated. */ public TreeInorderIterator(SearchTreeNode root) { super(root); } /** * The method buildVector build the in-order vector for the tree whose * root node is passed as an argument to the constructor. * @param node is the root node of the tree for which * in-order traverse iterator is created. */ protected void buildVector(SearchTreeNode node) { if(node !=null) { buildVector(node.left); v.addElement(node.contents); buildVector(node.right); } } }

Listing 10.4 The design of classes TreePostorderIterator and TreePreorderIterator is similar and the java code for those classes is shown in Listing 10.5 and 10.6. class TreePostorderIterator extends TreeIterator{ /** * The constructor takes the root of the binary search' * tree as an argument and passes to its super class to * complete the constructor call. * @param root is the root node for the tree for which the * iterator is generated. */ public TreePostorderIterator(SearchTreeNode root) { super(root); } /** * The method buildVector build the preorder vector for the tree

Page 272: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 30 of 63

whose * root node is passed as an argument to the constructor. * @param node is the root node of the tree for which * preorder traverse iterator is created. */ protected void buildVector(SearchTreeNode node) { if(node !=null) { buildVector(node.left); buildVector(node.right); v.addElement(node.contents); } } }

Listing 10.5 class TreePreorderIterator extends TreeIterator{ /** * The constructor takes the root of the binary search' * tree as an argument and passes to its super class to * complete the constructor call. * @param root is the root node for the tree for which the * iterator is generated. */ public TreePreorderIterator(SearchTreeNode root) { super(root); } /** * The method buildVector build the preorder vector for the tree whose * root node is passed as an argument to the constructor. * @param node is the root node of the tree for which * preorder traverse iterator is created. */ protected void buildVector(SearchTreeNode node) { if(node !=null) { v.addElement(node.contents); buildVector(node.left); buildVector(node.right); } } }

Listing 10.6 It is important to note that in coding the method buildVector ( ), the three tree iterator classes follow the respective algorithms discussed for the in-order, post-order, and pre-order traverse.

Page 273: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 31 of 63

Binary Search Tree class Now we have all the apparatus in place to write a binary search tree class called BinarySearchTree, which would implement interface BinaryTree of Listing 10.1. This class would have a root node of type SeachTreeNode and other necessary fields and methods to add, remove elements to the BST. Let us first discuss briefly, the functionality needed in the Binary Search Tree class we wish to write. Like any other data structure, the class for Binary search tree must provide for:

1. Insert nodes in the BST according to the insertion rule that every left child is smaller and every right child is larger than the parent node.

2. Be able to delete a node with a key from the BST and still maintain the property #1 above.

3. Be able to destroy entire tree. 4. Be able to find, whether certain key exists in the BST. 5. Get a reference to certain key in the tree. 6. Get in-order, pre-order, and post-order iterators to the BST,

allowing traveling the BST in the desired order. 7. Have methods to calculate BST properties, such as average

path length, maximum tree level. 8. Perform queries on BST, such as return the number of

elements in the tree or find whether tree is empty or not. The Table 10.5 gives the summary of design of BST class. One would notice that the class includes many private methods. The purpose of including private methods is to modularize the program as much as possible, allowing the public methods to look un-cluttered and clean. Obviously, all private methods are only usable with in the Binary Search Tree class.

Field Summary protected int maxLevel

Gives the maximum level for the BinarySearchTree. protected int numberElements

Gives the number of elements in the BinarySearchTree. protected

SearchTreeNode root The field root is the root node of BinarySearchTree.

Constructor Summary BinarySearchTree() Argument-less constructor for BinarySearchTree.

Page 274: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 32 of 63

Method Summary void add(java.lang.Comparable obj)

Adds the node obj to the BinarySearchTree. double avgPathLength()

Computes the average path length for the BinarySearchTree.

private void computeMaxLevel(SearchTreeNode node, int level)

Internal method to compute the meximum level private double computePathLength(SearchTreeNode node,

double pathLength) Internal method to compute the path length of a treeor sub-tree.

boolean contains(java.lang.Comparable obj)

Finds whether the BinarySearchTree contains the Object obj.

private SearchTreeNode

deleteNode(SearchTreeNode node, java.lang.Comparable item) Called internally by the method remove, to remove the node containing the key item, from the tree with root node.

private SearchTreeNode

deleteRightMost(SearchTreeNode node) Internal method to delete the right most node of a tree or sub-tree.

private void destroy(SearchTreeNode node)

Internal method called by makeEmpty to delete all nodes in a tree.

java.util.Iterator elements() Creates a in-order Iterator for the BinarySearchTree.

java.lang.Comparable get(java.lang.Comparable obj)

Gets a reference to the Object in the BinarySearchTree.

private SearchTreeNode

insertNode(SearchTreeNode node, java.lang.Comparable item) Recursive internal method, inserts the item at its proper location in the BinarySearchTree.

boolean isEmpty() Performs the query whether the BinarySearchTree is empty or not.

static void main(java.lang.String[] args)

Page 275: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 33 of 63

void makeEmpty() Creates an empty null BinarySearchTree.

int maxLevel() Computes the maximum level for the BinarySearchTree.

void remove(java.lang.Comparable obj)

Removes the node with key obj from the BinarySearchTree.

private java.lang.Comparable

rightMost(SearchTreeNode node)

Called internally by the method deleteNode to find the right most node for the tree or sub-tree with root node.

SearchTreeNode root()Returns the reference to the root of the BinarySearchTree.

int size() Performs the query as to how many elements are in the BinarySearchTree.

java.util.Iterator traverseInorder() Creates an in-order Iterator for the caller tree.

java.util.Iterator traversePostorder() Creates an Post-order Iterator for the caller tree.

java.util.Iterator traversePreorder() Creates an pre-order Iterator for the caller tree.

Table 10.5 The source code for the class BinarySearchTree is given in Listing 10.7. import java.util.*; public class BinarySearchTree implements BinaryTree { /** * The field root is the root node of <code>BinarySearchTree</code>. */ protected SearchTreeNode root = null; /** * Gives the number of elements in the <code>BinarySearchTree</code>. */ protected int numberElements = 0; /** * Gives the maximum level for the <code>BinarySearchTree</code>. */ protected int maxLevel; /** * Argument-less constructor for <code>BinarySearchTree</code>. */ public BinarySearchTree() {

Page 276: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 34 of 63

//no code needed } /** * Adds the node obj to the <code>BinarySearchTree</code>. * @param obj is added to the tree. */ public void add(Comparable obj) { root = insertNode(root, obj); numberElements++; } /** * Removes the node with key obj from the <code>BinarySearchTree</code>. * @param obj is removed from the <code>BinarySearchTree</code>. */ public void remove(Comparable obj) { root = deleteNode(root,obj); this.numberElements--; if(this.numberElements == 0) root = null; } /** * Creates an empty null <code>BinarySearchTree</code>. */ public void makeEmpty() { destroy(root); root = null; this.numberElements = 0; } /** * Performs the query whether the <code>BinarySearchTree</code> is empty * or not. * @return true if tree is empty, else returns false. */ public boolean isEmpty() { return root == null; } /** * Performs the query as to how many elements are in the * <code>BinarySearchTree</code>. * @return the number of elements in the <code>BinarySearchTree</code>. */ public int size() { return this.numberElements; } /** * Finds whether the <code>BinarySearchTree</code> contains the * Object obj. * @param obj is the <code>Object</code> to be searched in the

Page 277: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 35 of 63

* tree. * @return true if the <code>Object</code> obj is in the tree, else * returns false. */ public boolean contains(Comparable obj) { if(this.numberElements == 0) return false; SearchTreeNode current = root; while(current != null) { if(obj.compareTo(current.contents) < 0) { current = current.left; } else if(obj.compareTo(current.contents) > 0) { current = current.right; } else break; } return current != null; } /** * Gets a reference to the <code>Object</code> in the <code>BinarySearchTree</code>. * which is identical to the <code>Comparable</code> object obj. * @param obj is the <code>Comparable</code> object to be searched for. * @return the reference to the tree object identical to obj if found, else * returns a null. */ public Comparable get(Comparable obj) { if(this.numberElements == 0) return null; SearchTreeNode current = root; boolean found = false; while(current != null && !found) { if(obj.compareTo(current.contents) == 0) found = true; else { if(obj.compareTo(current.contents) < 0) current = current.left; else current = current.right; } } return current.contents; } /**

Page 278: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 36 of 63

* Creates a in-order Iterator for the <code>BinarySearchTree</code>. * @return the <code>Iterator</code> that will traverse the <code>BinarySearchTree</code> * in-order. */ public Iterator elements () { return traverseInorder(); } /** * Creates an in-order Iterator for the caller tree. * @return the object of class <code>TreeInorderIterator</code> allowing * to traverse the tree in-order. */ public Iterator traverseInorder() { return new TreeInorderIterator(root); } /** * Creates an pre-order Iterator for the caller tree. * @return the object of class <code>TreePreorderIterator</code> allowing * to traverse the tree pre-order. */ public Iterator traversePreorder() { return new TreePreorderIterator(root); } /** * Creates an Post-order Iterator for the caller tree. * @return the object of class <code>TreePostorderIterator</code> allowing * to traverse the tree Post-order. */ public Iterator traversePostorder() { return new TreePostorderIterator(root); } /** * Computes the maximum level for the <code>BinarySearchTree</code>. * @return the integer value of the maximum level for the * <code>BinarySearchTree</code>. */ public int maxLevel() { this.maxLevel = 0; computeMaxLevel(root,0); return this.maxLevel; } /** * Computes the average path length for the <code>BinarySearchTree</code>. * @return the double value as the average path length for the

Page 279: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 37 of 63

* <code>BinarySearchTree</code>. */ public double avgPathLength() { if(this.numberElements == 0) return 0; else return computePathLength(root, 0)/this.numberElements; } /** * Returns the reference to the root of the <code>BinarySearchTree</code>. * @return the reference to the <code>BinarySearchTree</code> root. */ public SearchTreeNode root( ) { return root; } /** * Recursive internal method, inserts the item at its proper * location in the <code>BinarySearchTree</code>. * @param node is the root of the sub-tree * @param item is the <code>Comparable</code> object to be inserted. * @return the root of the modified tree or sub-tree. */ private SearchTreeNode insertNode(SearchTreeNode node, Comparable item) { if(node == null) return new SearchTreeNode(item); else if(item.compareTo(node.contents)<0) { node.left = insertNode(node.left,item); return node; } else if(item.compareTo(node.contents)>0) { node.right = insertNode(node.right,item); return node; } else throw new UnsupportedOperationException( "add:obj already in binary search tree."); } /** * Called internally by the method remove, to remove the node * containing the key item, from the tree with root node. * @param node is the root of the sub-tree. * @param item is the <code>Comparable</code> object to be deleted. * @return the root of the modified tree or sub-tree. */

Page 280: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 38 of 63

private SearchTreeNode deleteNode(SearchTreeNode node, Comparable item) { if(node == null) throw new NoSuchElementException( "remove:: obj not in binary search tree."); if(item.compareTo(node.contents)<0) node.left = deleteNode(node.left,item); else if(item.compareTo(node.contents)>0) node.right = deleteNode(node.right,item); else if(item.compareTo(node.contents) == 0) { if(node.left == null) node = node.right; else if(node.right == null) node = node.left; else { Comparable replaceWithValue = rightMost(node.left); node.contents = replaceWithValue; node.left = deleteRightMost(node.left); } } return node; } /** *Called internally by the method deleteNode to find the right most * node for the tree or sub-tree with root node. * @param node is the root of the tree or sub-tree * @return the right most object for the tree or sub-tree. */ private Comparable rightMost(SearchTreeNode node) { if(node.right == null) return node.contents; else return rightMost(node.right); } /** * Internal method to delete the right most node of a tree or * sub-tree. * @param node is the root of the tree or sub-tree * @return the node of tree after deletion. */ private SearchTreeNode deleteRightMost(SearchTreeNode node) { if(node.right == null) return node.left; else { node.right = deleteRightMost(node.right); return node; }

Page 281: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 39 of 63

} /** *Internal method called by makeEmpty to delete all nodes in a tree. *@param node is the root of the tree or sub-tree. */ private void destroy(SearchTreeNode node) { if(node !=null) { destroy(node.left); destroy(node.right); node.contents = null; node = null; } } /** * Internal method to compute the path length of a tree or sub-tree. * @param node is the root of tree or sub-tree * @param pathLength is the distance from the root to the node * @return the path length */ private double computePathLength(SearchTreeNode node, double pathLength) { if (node !=null) { return pathLength + computePathLength(node.left , pathLength +1) + computePathLength(node.right, pathLength + 1); } else return 0; } /** * Internal method to compute the meximum level * @param node is the root of the tree or sub-tree * @param level is the level of the node */ private void computeMaxLevel(SearchTreeNode node, int level) { if(node != null) { computeMaxLevel(node.left, level+1); computeMaxLevel(node.right, level+1); if(node.right == null && node.left == null && level >maxLevel) maxLevel = level; } }

Listing 10.7

Page 282: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 40 of 63

Discussion of key methods of BinarySearchTree class We would now like to discuss the key methods that provide vital functionality to the class BinarySearchTree. Methods add ( ) and insertNode ( ) Table 10.6 below gives the brief description, parameters, and return types for methods add ( ) and insertNode ( ). void add(java.lang.Comparable obj)

Adds the node obj to the BinarySearchTree. private

SearchTreeNode insertNode(SearchTreeNode node, java.lang.Comparable item) Recursive internal method, inserts the item at its proper location in the BinarySearchTree.

Table 10.6 Method add( ) calls the method insertNode ( ) to perform the task of adding a node obj, to the BST. The codes for both methods are reproduced in the Listing 10. 8. public void add(Comparable obj){ root = insertNode(root, obj); numberElements++; }

private SearchTreeNode insertNode (SearchTreeNode node, Comparable item) { if(node == null) return new SearchTreeNode(item); else if(item.compareTo(node.contents)<0) { node.left = insertNode(node.left,item); return node; } else if(item.compareTo(node.contents)>0) { node.right = insertNode(node.right,item); return node; } else throw new UnsupportedOperationException( "add:obj already in binary search tree."); }

Listing 10.8 The method add takes the Comparable object obj, and calls the method insertNode. The method insertNode takes the root of the BST and obj as arguments, and after inserting the obj in the BST as a node returns the root of the modified tree. Therefore, it is instructive to see as to how the method insertNode works. We do this by a code walk-through in the method insertNode. The code walk through is illustrated by the following PowerPoint presentation and subsequently described in text.

Block #1

Block #2

Block #3

Block #4

Page 283: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 41 of 63

MethodinsertNode.ppt

It is important to understand that how and when different if/else blocks are executed in the method insertNode. insertNode takes the root of the BST or root of a sub-tree and the item to be added as arguments. The block#1 is executed when the root of the tree is null. This would happen when the item is being attached to the leaf node or the root of the tree is being created. The block#1 represents the sentinel or the base case for the recursive method insertNode. The block #2 is executed when item being attached is smaller than the item Object in the node. The left sub-tree of node is then transformed. The block#3 executes when the item being attached is larger than the item Object in the node. The right sub-tree of the node is then modified. Block #4 executes when item is already present in the BST. Imagine that the first node to be added has the item value equal to 300 (Figure 10.11).

FIG. 10.11 The block #1 is then executed and BST with the root containing item as 300 is created. Block #2 will execute if the next item to be added is less than 300 and this item will be added as the left child of the root. Alternatively, the block #3 will execute to add an item larger than 300 as the right child of the root node. Each of blocks #2 and #3 make a recursive call to method insertNode. The overall mechanism may be illustrated well if we show the process, by adding a new item to the BST with some nodes already attached (Figure 10.12).

Page 284: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 42 of 63

FIG. 10.12 Figure 10.12 shows a BST to which we wish to add item 260. Since 260 is smaller than the item value in root node, which has 300, the block #1 of method insertNode executes (Figure 10.13).

FIG. 10.13

Page 285: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 43 of 63

As soon as the next line in block #2 executes, the recursive calls begin. The Figure 10.14 below best shows the system of these recursive calls. FIG. 10.14 In Figure 10.14 IN indicates the call to the method insertNode. The first call is made to the method insertNode from with in the method add. This call is shown in the top most box as; root = IN (node-300, 260);

root = IN (node-300, 260); return modified tree-300 or root

300>260. Execute block#2

300.left = node-200 = IN (node-200, 260); return modified subtree-200

200.right = node-250 = IN (node-250, 260); return modified subtree-250

200<260. Execute block#3

250<260. Execute block#3

250.right = node-275 = IN (node-275, 260); return modified subtree-275

275>260. Execute block#2

275.left = IN (null, 260); 275.left = node-260

Base case reached. 260 gets attached as left node to node-275.

Going in Backing out

Page 286: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 44 of 63

node-300 means that root node of the tree is being passed as an argument. Since 260 is smaller than 300, the recursive call at level 2 is made when block #2 is executed as: 300.left = node-200 = IN (node-200, 260); The above call leads to recursive call at level 3. Since 260 is larger than 200, the block #3 executes now as: 250.right = node-275 = IN (node-275, 260); This call leads to the recursive call at level 4. Since 260 is smaller than 275, the block #2 is executed then as: 275.left = IN (null, 260); In the last call, the sentinel is met and base case is reached. The method insertNode now returns a node with item field being equal to 260. Now the program backs out of the recursion. Returning to the call at level three returns the root of sub-tree node-275. Backing out to level two returns the root of sub-tree 250. The root of sub-tree 200 is returned when recursion backs out to level one. Finally the root of entire tree is returned to the method add. The Figure 10.15 shows the modified tree.

FIG. 10.15 The items larger than the root node 300 will be added to the right branch of tree and the process would be similar to the recursion described above. An exception is thrown if attempt is made to add an item already present in the tree.

Page 287: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 45 of 63

Methods remove ( ), deleteNode ( ), rightmost( ), and deleteRightMost ( ) Before we discuss the public and private methods used for deleting a BST node, we must understand the process and logic of removing a node from the BST. In deleting a node from a BST, three separate possible cases are discussed. Case 1: Deleting a leaf node: A leaf node only has no null children; therefore, it is simply clipped from the tree by setting reference to it from its parent to null. Figure 10.16 shows the process of clipping a leaf node.

FIG. 10.16 In above Figure 10.6, in order to delete leaf node Z, one simply set the reference from R to Z to null, thus clipping the node Z from the tree. Case 2: Deleting a node with one child: The process of deleting the node with one child is shown in Figure 10.17. Here we wish to delete the node R, which is the child of Q, but has its own child Z.

Page 288: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 46 of 63

FIG. 10.17 To delete node R, we first assign a temporary reference to R. Then we break the linkage of R from its parent Q, and make Q point to its grandchild Z. Finally we set temporary reference to R to null, thus deleting R. Case 2: Deleting a node with two children: Deleting a node with two children is more complex and involves the following steps.

• Find the descendent of the node to be removed, which is “just smaller” than the node to be removed. For example in Figure 10.18, if we wish to remove the node Q, the node just smaller than Q is P. The node, which is “just smaller”, will be found, first by going down one level left and then going as far right as possible. In Figure 10.18, we go down one level left of Q to node L and then the farthest right of L is P.

• Copy the value of this “just smaller” node into the item field of node to be removed. We copy the value of P into item field of Q, effectively turning node Q into P. Then we delete the leaf node P.

Temp Temp

NULL

Page 289: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 47 of 63

FIG. 10.18 There may be a situation, however, when the node that is “just smaller” than the node to be removed is not a leaf node. Consider for example the Figure 10.19, where we wish to delete the root node “300”.

FIG. 10.19

Page 290: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 48 of 63

The node just smaller than the “300” is “275”, which is not a leaf node. We proceed as follows:

• Swap values of item fields between node “275” and its child “260”. This situation is shown in Figure 10.20.

• Now copy the value of leaf node “275” into the item field of root

node “300”. This makes the “275” a root node (Figure 10.21).

FIG. 10.20

Page 291: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 49 of 63

FIG. 10.21 • Now we clip the leaf node “275” (Figure 10.22).

Now we discuss the details of methods remove, deleteNode, rightmost, and deleteRightMost. Table 10.7 gives the summary of these methods.

void remove(java.lang.Comparable obj)

Removes the node with key obj from the BinarySearchTree.

private SearchTreeNode

deleteNode(SearchTreeNode node, java.lang.Comparable item) Called internally by the method remove, to remove the node containing the key item, from the tree with root node.

private

java.lang.Comparable rightMost(SearchTreeNode node)

Called internally by the method deleteNode to find the right most node for the tree or sub-tree with root node.

private

SearchTreeNode deleteRightMost(SearchTreeNode node)

Internal method to delete the right most node of a tree or sub-tree.

Table 10.7 The java code for methods of Table 10.7 is reproduced in Listing 10.9.

FIG. 10.22

Page 292: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 50 of 63

public void remove(Comparable obj) { root = deleteNode(root,obj); this.numberElements--; if(this.numberElements == 0) root = null; } private SearchTreeNode deleteNode(SearchTreeNode node, Comparable item) { if(node == null) throw new NoSuchElementException( "remove:: obj not in binary search tree."); if(item.compareTo(node.contents)<0) node.left = deleteNode(node.left,item); else if(item.compareTo(node.contents)>0) node.right = deleteNode(node.right,item); else if(item.compareTo(node.contents) == 0) { if(node.left == null) node = node.right; else if(node.right == null) node = node.left; else { Comparable replaceWithValue = rightMost (node.left); node.contents = replaceWithValue; node.left = deleteRightMost(node.left); } } return node; } private Comparable rightMost(SearchTreeNode node) { if(node.right == null) return node.contents; else return rightMost(node.right); } private SearchTreeNode deleteRightMost(SearchTreeNode node) { if(node.right == null) return node.left; else { node.right = deleteRightMost(node.right); return node; } }// Listing 10.9

Block #1

Block #2

Block #3

Block #4

Page 293: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 51 of 63

The method remove takes the Comparable object obj and calls the method deleteNode, which takes the root of the tree and obj as argument and returns the root of the modified tree. Then the method remove reduces the number of elements by one. We illustrate the code walk through for method deleteNode by taking the case of removal of node 300 in Figure 10.19. We first show the code walk through for method deleteNode using the PowerPoint presentation and then we describe it by using pictures and text.

deleteNodeMethod.ppt

Method deleteNode has four if – else type blocks. They execute as follows (See Listing 10.9):

• The block #1 executes if tree to be deleted is null. In that case, a NoSuchElementException is thrown.

• If item to be deleted is smaller than the root of the tree, then block #2 is executed. In this case the deleteNode is called on left tree branch recursively. This makes sense because elements smaller than the root are located in the left sub-tree.

• If item to be deleted is larger than the root of the tree, then block #3 is executed. In this case the deleteNode is called on right tree branch recursively. This is because elements larger than the root are located in the right sub-tree.

• Block #4 is executed when the node has the item to be deleted. Figure 10.23 shows the details of block #4.

FIG. 10.23

Page 294: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 52 of 63

If left child of the node with item is null, then set the node equal to its right child. On the other hand, if the right child of the node with the item is null, then set the node equal to its left child. Most complex case occurs when a node has two children. We illustrate this case by taking the Figure 10.19 and deleting its root node. Since it has two children, the block #4 of deleteNode method will apply. In addition, since it is the root of the tree, the node is already found with the first call to the method deleteNode. In executing block #4, first a replacement value is found for the node to be deleted (Figure 10.24).

FIG. 10.24 We mentioned before that the replacement value for any non-leaf node is the value “just smaller” than the node to be replaced. The method rightmost finds this value and returns a reference to it. Then we replace the root node with this replacement value, which in this case happens to be 275 (Figure 10.25).

Page 295: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 53 of 63

FIG. 10.25 Now only task left is to delete right most node to the node left of root. This task is performed by the method deleteRightMost (Figure 10.26).

Figure 10.26 shows the final form of the tree, with root 300 deleted.

FIG. 10. 26

Page 296: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 54 of 63

Now only task left for us to see that how the methods rightmost and deleteRightMost work. Method rightmost (Listing 10.9) takes a tree or sub-tree root as an argument and returns the right most node of that tree. If the right child of root is null, then rightmost returns the contents of the root. Otherwise, the method calls itself recursively with the right child of the root as its argument. In Figure 10.24, the following call is made to the method rightmost: Comparable replaceWithValue = rightmost (node-200); Figure 10.27 shows the recursive calls stacked up when method rightMost(RM) is called with the node 200 as its argument.

Page 297: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 55 of 63

FIG. 10.27 The base case or sentinel is reached, when method rightmost is called with the node 275 as its argument. Since right child of node 275 is null, the method returns the Comparable object with value 275 in it. Backing out, three levels of recursion, a value of 275 is provided for the parameter replaceWithValue. Method deleteRightMost The method deleteRightMost takes a tree or sub-tree root as its argument, and deletes the right most node of that tree. In deleting the root node 300 from Figure 10.24, the call to deleteRightMost is made with an argument node 200. The method deletes the right most node of sub-tree with node 200 and returns a new reference to the root of sub-tree with node 200 as the root. The method deleteRightMost calls itself recursively with second call being similar to: node.right = deleteRoghtMost (node-250). Figure 10.28 shows the recursive calls to the method deleteRightMost(DRM).

replaceWithValue = RM (node-200) Return 275 as the replaceWithValue

Right node of 200 is 250.

Return RM (node-250) Return 275

Return RM (node-275) Return 275

Right node of 250 is 275.

Right node of 275 is null

Return RM (null) return 275

Base case reached. 260 gets attached as left node to node-275.

Going in Backing out

Page 298: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 56 of 63

FIG. 10.28 The base case is reached when the call is made with the method argument being node-275. Then the last recursive call returns the node 260. This effectively replaces 275 with 260, and sets both children of 260 to null. Then each backing out call returns the new value of the right sub-tree root, from where the call originated. Other BinarySearchTree Class Methods Method destroy The method makeEmpty calls the method destroy ( ) to delete all nodes in the tree and render its root as null. The tree destruction is done in post-order traversal. Hence the method destroy follows the post-order traversal algorithm. The code for method destroy is reproduced below in Listing 10.10. private void destroy(SearchTreeNode node) { if(node !=null) { destroy(node.left); destroy(node.right); node.contents = null; node = null; } }//Listing 10.10 The post-order logic of destruction of tree shown in Figure 10.19 is shown in Figure 10.29.

Node-200 = DRM (node-200) Return new reference to node 200

Right node of 200 is 250.

Node-200.right = DRM (node-250) Return new reference to node-250

Node250.right = DRM (node-275) Return node-260

Right node of 250 is 275.

Right node of 275 is null. Base case reached.

Page 299: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 57 of 63

D(300)

Set node300 to null

D(200)

D(400)

D(100)

null null

Set Node 100 to null

D(250)

D(275)

null

null

Set 275 to null

Set node 250 to null

Set node 200 to null

null

Set node 400 to null

FIG. 10.29

D(260)

nullnull

Set 260 to null

null

Page 300: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 58 of 63

The destruction involves setting each node and its contents to null as the tree is traversed in the post order sequence. The arrows in the Figure 10.29 show the destruction sequence. The destruction sequence is as follows: 100, 260, 275, 250, 200, 400, and 300. As expected, the root node is destroyed in the end. Method avgPathLength ( ) The method avgPathLength calculates the average path length for the BST. The average path length (APL) for a binary tree with n nodes is given as: APLn = TPLn/n ……………………………………. Equation 10.0 The TPLn is the total path length for all the nodes in the binary tree defined as: n

TPLn = Σ d [ i ] …………………………… Equation 10.1

i = 1 The total path length is found by summing over the depth of all nodes. The parameter d[ i ] in equation 10.1 is the depth of the node number i. You would recall that the depth of a node is its level. For example, the root node has a depth of zero. To illustrate the average path calculation, we apply the equations 10.1 and 10.2 to the tree of Figure 10.19. In Figure 10.19, we have the various nodes at various levels as given in Table 10.8. Node Depth or level 300 0 200 1 400 1 100 2 250 2 275 3 260 4 Table 10.8 For tree of Figure 10.19, the total path length is given as: TPLn = ( 0 + 1 + 1 + 2 + 2 + 3 + 4 ) = 13 Then according to Equation 10.1, the average path length is given as: APLn = 13 / 7 = 1.857. A shorter path length for the same tree generally results in a tree that is more balanced, which also allows for the faster searching of a key in it. Imagine that we insert the data of BST in Figure 10.19 in the following order: 100, 200, 250, 260, 275, 300, and 400. This will result in a BST, which is no better than a linked list (Figure 10.30).

Page 301: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 59 of 63

Linear Binary Search Tree

300

200

250

275

100

260

400

FIG. 10.30 The tree in Figure 10.30 looses all advantages of fast searching of a node that a more balanced form like Figure 10.19 would provide. We can calculate the average path length for the tree in Figure 10.30, to show that such length has increased compared to the value calculated earlier for the form in Figure 10.19. The average path length is given as: APLn = (0 + 1 + 2 + 3 + 4 + 5 + 6 )/7 = 21/7 = 3 The above average path length of three is almost 1.6 times of the path length calculated earlier for the more balanced tree given in Figure 10.19. The result is longer searching times. In fact, we try to improve upon the binary search tree shown in Figure 10.19, and try to make it more “balanced” – noting that a balanced tree is “more bushy” or, it sprouts equally in all directions. A more balanced form of the BST in Figure 10.19 is shown in Figure 10.31.

Page 302: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 60 of 63

Balanced Binary Search Tree

300200

250 275

260

400100

FIG. 10.31 The average path length for the tree in Figure 10.31 is given as: APLn = ( 0 + 1 + 1 + 2 + 2 + 2 + 2 ) / 7 = 10/7 = 1.43 Therefore, one can draw the conclusion, that for a given number of nodes in a BST, the shorter average path length will lead to a more balanced BST, which will allow faster search. The method avgPathLength ( ) calls the private method computePathLength ( ), later taking the root of the tree, and an initialized parameter of 0.0 as arguments. The computePathLength ( ) returns the average path length of the BST. The code for the method computePathLength ( ) is given in Listing 10. 11. private double computePathLength(SearchTreeNode node, double pathLength) { if (node !=null) { return pathLength + computePathLength(node.left , pathLength +1) + computePathLength (node.right, pathLength + 1); } else return 0; }//Listing 10.11 The method computePathLength calls itself recursively if the root of the tree or sub-tree is not null. The recursion ends when the tree root is null and then method returns the path length of zero. We apply the code of method in Listing 10.11 to the

Page 303: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 61 of 63

BST in Figure 10.31. The Figure 10.32 shows the system of recursive calls. CPL denotes the call to method computePathLength.

CPL(node-260,0)

return 0 + CPL(node-200,1) + CPL(node-300,1)

return 1 + CPL(node-100,2) + CPL(node-250,2)+ 1 + CPL(node-275,2) + CPL(node-400,2)

100, 250, 275, and 400 are all leaf nodes.

CPL(node-100,2), CPL(node-250,2) , CPL(node-275,2) + CPL(node-400,2) , all return a value of 2.

Both return 5 each

Returns 10

FIG. 10.32 The computePathLength method is called with the argument root node 260, and zero. If block is executed and the return value becomes the zero plus the two recursive calls to computePathLength with arguments as root nodes 200 and 300, and one. The third box from the top in the Figure 10.32 shows the further recursive calls. The calls CPL (node-100, 2), CPL (node-250, 2), CPL (node-275, 2), and CPL (node-400, 2) – all return a value of two, because they all take the leaf nodes as the first argument. When the computePathLength is called with leaf node as the first argument, it simply returns the second parameter as the return value. Therefore, in backing out from the third box from the top in Figure 10.32, a value of five plus five is returned. Finally the value returned to the first call is ten – same value that we calculated on page 60 earlier. Testing the BinarySearchTree Class We write a simple class TestBST to test the BinarySearchTree class developed earlier and displayed in Listing 10.7. The Listing 10.12 shows the class TestBST.

Page 304: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 62 of 63

import java.util.*; public class TestBST { public static void main(String [ ] args) { BinarySearchTree myTree = new BinarySearchTree(); myTree.add(new Double(10)); myTree.add(new Double(12)); myTree.add(new Double(5)); myTree.add(new Double(7.5)); myTree.add(new Double(15)); myTree.add(new Double(18)); myTree.add(new Double(16)); myTree.add(new Double(20)); System.out.println("Average path Length = " + myTree.avgPathLength()); System.out.println("maximum level = " + myTree.maxLevel()); for(Iterator iter = myTree.elements(); iter.hasNext(); ) { System.out.println(iter.next()); } myTree.remove(new Double(10)); myTree.remove(new Double(15)); myTree.remove(new Double(16)); System.out.println("Deleted nodes 10, 15, and 16"); for(Iterator iter = myTree.elements(); iter.hasNext(); ) { System.out.println(iter.next()); } System.out.println("Average path Length = " + myTree.avgPathLength()); System.out.println("maximum level = " + myTree.maxLevel()); } }//Listing 10.12 The main method in the class first adds the nodes 10, 12, 5, 7.5 etc., and then prints them. The selected nodes are then removed and the tree is printed again. Figure 10.33 shows the results.

Page 305: E_Book_JavaDataStructure_SatishSinghal

Topic 10 recursion Page 63 of 63

FIG. 10.33 The tree is printed using an in-order iterator, which prints the tree elements in the ascending order. The tree can also be printed in pre or post order. The result of pre-order printing will be : 10.0 5.0 7.5 12.0 15.0 18.0 16.0 20.0 Moreover, the post-order printing will print the tree as: 7.5 5.0 16.0 20.0 18.0 15.0 12.0 10.0.

Page 306: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 1 of 61

CS 20 B : Java Data Structures

Topic 11: Hashing Author: Satish Singhal Ph. D.

Version 1.0 In searching for a key in a linear list like an unsorted array or linked list the average search, time increases linearly with the size of the list. In big Oh notation, this type of searching algorithm is called an O (N) algorithm, where N is the number of elements in the list. In a sorted array, however, the average search time can be reduced if it is sorted sequentially. In a sorted array, using the binary search, the average search time is proportional to the log2N, instead of being proportional to N. In fact that is one big advantage if Binary Search trees, that in optimally balanced state, they provide the advantage of linked list, i. e. to be able to use fragmented memory, and that of sorted array, that is search being faster than the linked list. Hashing allows us to make search faster – more like closer to O (1), which means that search is almost instantaneous. Many critical situations require that the search for some information be almost instantaneous. For example, when an emergency 911 operator receives a phone call, they need to match the phone number with the street address almost instantaneously, so that they can dispatch the help right away. Even an O (log2N) algorithm will be too slow for an emergency response system. Similarly, when an air traffic controller gets a mayday call, he must get all the flight information for that flight number instantaneously1. Hash tables are very useful in doing almost instantaneous searches. How is an instantaneous search possible? We give one example first. Consider a small company with 100 employees, who wishes to keep records on all employees, in a data file. Suppose each employee has a unique identification number (ID) from zero to 99. We can read employee data from the file into an array of employee class objects, with array being sorted based on employee ID number. In such an array, we have an instantaneous access to an employee record, once we know the employee ID number. This is because each array index also represents the ID of an employee. Searching such an array would conform to an algorithm of O (1). In such case, the array index functions as the key for each array element. It may be impractical, however, to have employee ID that run from zero to 100. It is more common to have a uniform system of assigning ID’s – for example a five-digit ID number. In such a system the largest ID, number would be 99999. If we tried to keep each employees record in the array element, with index equal to employee ID number, then for 100 employees, we would need to create an array with 100000 elements. Using a data structure 1000 times larger than actual need is certainly

1 This would be the case when the aircraft is not on the radar; rather the information is received over radio control.

Page 307: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 2 of 61

wasteful. What one can do in such case is to use only last two digits of employee ID number and match that to the array index. For example, the record of employee with ID 4755 can be kept in the array cell with index 55. If our class is called Employee, and List is a reference to an array of objects of type Employee, then array cell number List [55] will be used to store the record for the employee with the ID number 4755. Similarly, the record of the employee with ID number 81278 will be kept in the array cell with the index 78, using the List [78] as the storage location. Notice that in this case the records are not sorted numerically based on the ID number. Rather they are sorted based on some function of ID number or their key value. Obviously, we would need a function, which would take the ID number as an argument, and return this key, which is a function of the ID number. A function or method that returns a key for an object of class is called a hashing function. The formal definition of a hash method is as follows: For example in the if the class Employee, would have a hashing method hash( ), such that the value returned by the method hash is simply last two digits of the employee ID. We write such a hash method in Listing 11.1. public class Employee { //Other fields and methods public int hash( ) { return (id_num% max_items); } } //Listing 11.1 The Figure 11.1 shows the action of hashing method written in Listing 11.1.

A hashing method provides a unique key for an object. If two objects are identical, then hashing method must provide the same key for them.

Page 308: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 3 of 61

FIG. 11.1 For example for max_items equal to 100, and for an employee with an ID number 50704, the hash function will return a key which is 50704 % 100 = 04. This allows us to place the record of employee number 50704, in the cell with array index 04, and retrieve it instantaneously using the last two digits of employee ID number. As in previous data structures, we would like to define an interface Hashable, which can be implemented by the objects, which can be placed in a hash table. We express the design of interface Hashable in Table 11.1, and its code in Listing 11.2.

Method Summary

int compareTo(java.lang.Object value)

Compares this object with the specified object for order.

int hash() Method hash returns the integer which can be used as a key to the Hashable object.

Table 11.1: The design of methods in interface Hashable We make the interface Hashable extend the interface Comparable, so that the Hashable objects also become Comparable as well. This will allow us to place

Page 309: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 4 of 61

Hashable objects in a binary search tree or a linked list if needed. Listing 11.2 gives the code for the interface Hashable. public interface Hashable extends Comparable { int hash( ); int compareTo(Object value) throws ClassCastException; } Listing 11.2 We initiated the design of class Employee in the Listing 11.1. The complete design of the class Employee is given below in Table 11.2. public class Employee extends java.lang.Object implements Hashable

Field Summary

private int id_num id_num is the integer which stores the employee identification number

private static Hashable[]

List List is the static array of Hashable items.

private static int

max_items

max_items is the maximum number of items in the hash table

private java.lang.String

Name Name field stores the name of the employee.

private static int

num_items

num_items gives the number of items currently in the hash table

Constructor Summary

Employee() Argument-less constructor sets id_num to zero and Name to blank.

Employee(int init_id) The one argument constructor sets the id_num equal to the constructor argument and the Name to blank.

Employee(int init_id, java.lang.String Init_Name)

The two-argument constructor sets the id_num equal to the first argument and Name equal to the second argument.

Page 310: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 5 of 61

Method Summary

(package private) static void

() The static block initializes all the elements in the array List by calling the argument-less constructor for the class Employee.

int compareTo(java.lang.Object Item)

Compares this object with the specified object for order.

static int convert(int num)

The method convert takes an integer as argument and converts it into another integer exactly similar to the way the method hash code provides the hash code for Employee.

void delete(Hashable Item)

Deletes a given item from the hash table

boolean equals(java.lang.Object Obj)

Indicates whether some other object is "equal to" this one.

static void findEmployee()

Method findEmployee finds and prints the Employee information based on the ID number provided by the user.

static Employee getEmployee()

The static method getEmployee takes the user input for the data for one employee and returns the reference to constructed Employee object.

int hash() The method hash returns a hash code for the object.

void insert(Hashable Item)

Inserts a given item in the hash table at its proper location.

static void main(java.lang.String[] args)

static void print() prints the hash table.

Hashable retrieve(Hashable Item)

Retrieves a given Item from the hash table and returns a reference to it.

static int size() Returns the current size of the hash table

Page 311: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 6 of 61

java.lang.String toString()

Returns a string representation of the Employee.

Methods inherited from class java.lang.Object

clone, finalize, getClass, hashCode, notify, notifyAll, registerNatives, wait, wait, wait

Table 11.2

The complete java documentation for class Employee class are given in the file Employee.html.

The source code for the class Employee is given in Listing 11.3.

import java.util.*; import javax.swing.*; public class Employee implements Hashable { private int id_num; private String Name; private static int num_items; private static final int max_items = 100; private static Hashable [ ] List = new Hashable[max_items]; static { for(int index=0; index<max_items; index++) List[index] = null; } public Employee() { this(0); } public Employee(int init_id) { this(init_id, ""); } public Employee(int init_id, String Init_Name) { this.id_num = init_id; this.Name = Init_Name; } public int hash( ) {

Page 312: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 7 of 61

return (id_num % max_items); } public boolean equals(Object Obj)throws ClassCastException { if(Obj instanceof Employee) { Employee Temp = (Employee)(Obj); if(this.id_num == Temp.id_num && this.Name.equals(Temp.Name)) return true; else return false; } else throw new ClassCastException("equals:The Obj is not type Employee!"); } public static Employee getEmployee() { Employee Temp = new Employee(); boolean done = false; while(!done) { String Input = JOptionPane.showInputDialog ("Please enter the " + "Employee 5 digit ID number . "); Input = Input.trim(); int val=0; try { val= Integer.parseInt(Input); } catch(NumberFormatException ex) { JOptionPane.showMessageDialog(null, "Illegal ID number entered"); continue; } if(val>=0) Temp.id_num = val; else { JOptionPane.showMessageDialog(null, "Negative ID number entered"); continue; } boolean done_name = false; while(!done_name) { Input = JOptionPane.showInputDialog("Please enter the " + "Employee Name . "); Input = Input.trim(); if(Input.equals("") || Input == null) { JOptionPane.showMessageDialog(null, "No name entered."); done_name = false;

Page 313: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 8 of 61

} else { Temp.Name = Input; done_name = true; } } done = true; } return Temp; } public String toString() { return "ID = " + new Integer(this.id_num).toString() + " Name = " + this.Name; } public int compareTo(Object Item)throws ClassCastException { if(Item instanceof Employee) { Employee Temp = (Employee)(Item); if(id_num<Temp.id_num) return -1; else if(id_num>Temp.id_num) return 1; else return 0; } else throw new ClassCastException("CopmpareTo:The Item is not type ” + “Employee."); } public static int convert(int num) { return num % max_items; } public Hashable retrieve(Hashable Item) { int location = Item.hash(); return (Hashable)List[location]; } public void insert(Hashable Item) { int location = Item.hash(); List[location] = Item; num_items++; } public void delete(Hashable Item) { List[Item.hash()]= null;

Page 314: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 9 of 61

num_items--; } public static int size() { return num_items; } public static void print() { String Str = "The number of items in Employee list are " + size(); Str += "\nNow printing the hash table.\n"; for(int index=0; index<max_items; index++) { if(List[index] != null) { Str+= List[index].toString(); Str+="\n"; } } System.out.println(Str); } //Main method public static void main(String [] args) { Employee Temp = new Employee(); boolean done = false; while(!done) { //get the Employee Temp = Employee.getEmployee(); //Insert the Employee in Hash table Temp.insert(Temp); String Input = JOptionPane.showInputDialog("More Employees? " + "Enter [Y]es or [N]o . "); Input.trim(); if(Input.equals("Y") || Input.equals("y")) done = false; else if(Input.equals("N") || Input.equals("n")) done = true; else { JOptionPane.showMessageDialog(null, "Unexpected Response."); continue; } } //print the hash table print(); //Search the hash table findEmployee(); System.exit(0); }

Page 315: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 10 of 61

public static void findEmployee() { boolean done = false; while(!done) { String Input = JOptionPane.showInputDialog("Please enter the " + " 5 digit ID number for the Employee to be searched. "); Input = Input.trim(); int key = Integer.parseInt(Input); key = Employee.convert(key); Hashable Emp = Employee.List[key]; if( Emp != null) JOptionPane.showMessageDialog(null, " The Employee found is " + Emp); else JOptionPane.showMessageDialog(null,"No Employee with ID " + " Number " + Input + " found "); Input = JOptionPane.showInputDialog("More Employees to search? " + "Enter [Y]es or [N]o . "); Input.trim(); if(Input.equals("Y") || Input.equals("y")) done = false; else if(Input.equals("N") || Input.equals("n")) done = true; else { JOptionPane.showMessageDialog(null, "Unexpected Response."); continue; } } } }

//Listing 11.3 Other details of class Employee The class Employee has two, instance variable: id_num, which holds the identification number of an Employee and Name, which holds the name of the Employee. Other fields may be added as needed. The class has a static array called List, which would store all the Employee objects. The maximum size of the array is 100. The array and its size are static data members. The array List is type Hashable, and all its members are initialized to null, by the static block followed by the array memory allocation. The num_items is the static data member giving the size of the hash table. The method hash( ), which returns the hash code for any Employee object has already been discussed. The method convert may be needed to convert the five-digit employee ID number to the employee key, and it uses the code similar to the method

Page 316: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 11 of 61

hash ( ) to perform the conversion. Method compareTo returns a positive integer, negative or zero depending upon whether the object in the argument of the method has the id_num field larger than, smaller than, or equal to the calling object. The method equals returns a true if both non-static fields id_num and Name for the caller and called object are same, otherwise it returns false. The method getEmployee gets the employee data from the user and returns the object after constructing it. The method retrieve, takes a Hashable item as argument, and finds it in the hash table and returns it if it is in the table, else a null would be returned. The method inserts, a Hashable item in the hash table, and method delete would delete it. Method print, prints the entire hash table and method findEmployee will find an employee in the hash table by taking the user input for a five-digit ID number. Testing the class Employee To test class Employee, we enter the following employee data in the hash table (Table 11.3). The program prints the hash table and searches it for the ID number for a given employee. If employee is found, then employee record is printed. Otherwise a message that employee is not in the hash table is printed.

ID Number Name 31300 Adam 49001 Bertha 52202 Cynthia 12704 Darla 65606 Erin Table 11.3 Figure 11.2 shows the screen shots of the program in Listing 11.3.

FIG. 11.3 A

User enters the Employee ID number.

Page 317: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 12 of 61

FIG. 11.3 B

FIG. 11.3 C

FIG. 11.3 D

User enters the Employee name.

Program prompts user to input more data. If user types Y, then process of data entry continues.

After the data of Table 11.3 are entered, the program prints the size and contents of the hash table.

Page 318: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 13 of 61

FIG. 11.3 E

FIG. 11.3 F First, the user is prompted to supply the five-digit ID number (Figure 11.3A). If the valid ID number is entered, then the user is prompted to enter the employee name (Figure 11.3B). User is prompted to enter more data (Figure 11.3D), and if the data of Table 11.3 are entered, then the size and contents of hash table are printed (Figure 11.3D). For searching an employee, the program prompts user to enter a five-digit ID number (Figure 11.3E). If the Employee is found, then Employee record is printed (Figure 11.3F). Collisions One would notice that in Table 11.3 no two employees had same last two digits for their ID number. What would have happened if we wish to enter another employee with following information (Table 11.4)?

ID Number Name 62902 Heather In that case, the method hash would have returned a value of 02 as the hash code for employee Heather, and the insert method would have over-written the record of Heather over the record of employee Cynthia with ID number 52202. When the hashing procedure tries to store two or more distinct objects, at the same location in

User enters the Employee ID number to be searched in the hash table.

Search Results displayed.

Page 319: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 14 of 61

the table, the process creates what is called “collision”(Figure 11.4). A good hashing procedure must minimize the collisions and if they do occur, it must resolve them.

FIG. 11.4 Open addressing and linear probing to resolve collisions The PowerPoint presentation below describes the details of linear probing technique for resolving collisions.

OpenAddressing.ppt

Techniques called open addressing and liner probing may be used to resolve the collisions and insert, retrieve, and delete Objects from the hash table. The linear probing works as follows:

• Put one object in each array slot. • When there is collision, then find the next available

empty array cell and put the object there. Assume that the hash table is in the state shown by Figure 11.5. If data of Table 11.3 are the only one’s entered so far, then no collisions occurred yet.

Page 320: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 15 of 61

Now we wish to enter employee Heather with ID number 69202 (Figure 11.6).

FIG. 11.5

Page 321: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 16 of 61

The hash method returns the same key as was for Cynthia, whose array cell is already occupied. The method insert looks for next empty spot past array index [02] and inserts the data for Heather in the first available empty spot at index [03] (Figure 11.7).

FIG. 11.6

Page 322: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 17 of 61

The methods insert, retrieve, and delete from class Employee now need to be modified according to the technique of linear probing discussed above. Figure 11.8 gives the pseudo-code and Listing 11.4 gives the source code for the method insert.

FIG. 11.7

Page 323: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 18 of 61

public void insert(Hashable Item) throws HashtableFullException { if(num_items == max_items) { throw new HashtableFullException("The hash table is full." + " The last element was not added."); } else { int location = Item.hash( ); while(List[location] !=null) location = (location +1) % max_items; List[location] = Item; num_items++; } } //Listing 11.4 We define a new Exception class called HashtableFullException so that if hash table is full, then error related to that is handled. This is a simple class extended from the java RuntimeException class. Figure 11.9 gives the pseudo-code for the method retrieve, and Listing 11.5 give the source code for it.

FIG. 11.8

Page 324: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 19 of 61

FIG. 11.9 public Hashable retrieve(Hashable Item) { int location = Item.hash( ); while(List[location].compareTo (Item) != 0) location = (location +1) % max_items; return (Hashable)List[location]; } //Listing 11.5 Listing 11.6 gives the modified delete method. public void delete(Hashable Item) { int location = Item.hash(); while(List[location].compareTo (Item) != 0) location = (location +1) % max_items; if(List[location] != null) { List[location] = null; num_items--; } else System.out.println("Item to be deleted is not in the list\n"); }//Listing 11.6

Page 325: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 20 of 61

A new Employee class needed to be written in order to illustrate the changes made to account for collisions. Class Employee3 is this new class and its source code is given in Listing 11.7. The methods insert, retrieve, and delete are now modified, and method convert is removed, as it is no longer useful. import java.util.*; import javax.swing.*; public class Employee3 implements Hashable { private int id_num; private String Name; private static int num_items; private static final int max_items = 100; private static Hashable [ ] List = new Hashable[max_items]; static { for(int index=0; index<max_items; index++) List[index] = null; } public Employee3() { this(0); } public Employee3(int init_id) { this(init_id, ""); } public Employee3(int init_id, String Init_Name) { this.id_num = init_id; this.Name = Init_Name; } public int hash( ) { return (id_num % max_items); } public boolean equals(Object Obj)throws ClassCastException { if(Obj instanceof Employee3) { Employee3 Temp = (Employee3)(Obj); if(this.id_num == Temp.id_num && this.Name.equals(Temp.Name)) return true; else return false; }

Page 326: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 21 of 61

else throw new ClassCastException("equals:The Obj is not type Employee3!"); } public static Employee3 getEmployee() { Employee3 Temp = new Employee3(); boolean done = false; while(!done) { String Input = JOptionPane.showInputDialog("Please enter the " + "Employee 5 digit ID number . "); Input = Input.trim(); int val=0; try { val= Integer.parseInt(Input); } catch(NumberFormatException ex) { JOptionPane.showMessageDialog(null, "Illegal ID number entered"); continue; } if(val>=0) Temp.id_num = val; else { JOptionPane.showMessageDialog(null, "Negative ID number entered"); continue; } boolean done_name = false; while(!done_name) { Input = JOptionPane.showInputDialog("Please enter the " + "Employee Name . "); Input = Input.trim(); if(Input.equals("") || Input == null) { JOptionPane.showMessageDialog(null, "No name entered."); done_name = false; } else { Temp.Name = Input; done_name = true; } } done = true; } return Temp; }

Page 327: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 22 of 61

public String toString() { return "ID = " + new Integer(this.id_num).toString() + " Name = " + this.Name; } public int compareTo(Object Item)throws ClassCastException { if(Item instanceof Employee3) { Employee3 Temp = (Employee3)(Item); if(id_num<Temp.id_num) return -1; else if(id_num>Temp.id_num) return 1; else return 0; } else throw new ClassCastException("CopmpareTo: The Item is not type Employee3."); } public Hashable retrieve(Hashable Item) { int location = Item.hash( ); while(List[location].compareTo (Item) != 0) location = (location +1) % max_items; return (Hashable)List[location]; } public void insert(Hashable Item) throws HashtableFullException { if(num_items == max_items) { throw new HashtableFullException("The hash table is full." + " The last element was not added."); } else { int location = Item.hash( ); while(List[location] !=null) location = (location +1) % max_items; List[location] = Item; num_items++; } } public void delete(Hashable Item) { int location = Item.hash(); //scan the table to find the exact location while(List[location].compareTo (Item) != 0) location = (location +1) % max_items;

Page 328: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 23 of 61

//If Item is indeed found if(List[location] != null) { List[location] = null; num_items--; } else System.out.println("Item to be deleted is not in the list\n"); } public static int size() { return num_items; } public static void print() { String Str = "The number of items in Employee list are " + size(); Str += "\nNow printing the hash table.\n"; for(int index=0; index<max_items; index++) { if(List[index] != null) { Str+= List[index].toString(); Str+="\n"; } } System.out.println(Str); } //Main method public static void main(String [] args) { Employee3 Temp = new Employee3(); boolean done = false; while(!done) { //get the Employee Temp = Employee3.getEmployee(); //Insert the Employee in Hash table Temp.insert(Temp); String Input = JOptionPane.showInputDialog("More Employees? " + "Enter [Y]es or [N]o . "); Input.trim(); if(Input.equals("Y") || Input.equals("y")) done = false; else if(Input.equals("N") || Input.equals("n")) done = true; else { JOptionPane.showMessageDialog(null, "Unexpected Response."); continue; } } //print the hash table

Page 329: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 24 of 61

print(); //Search the hashtable findEmployee(); //Deleteing employees from hash table deleteEmployee(); //printing after deletion System.out.println("Hash table after deletions is :"); print(); System.exit(0); } public static void deleteEmployee() { JOptionPane.showMessageDialog(null, "Please provide data to delete an employee."); Employee3 Temp = new Employee3(); boolean done = false; while(!done) { Temp = Employee3.getEmployee(); Temp.delete(Temp); String Input = JOptionPane.showInputDialog("More Employees to delete? " + "Enter [Y]es or [N]o . "); Input.trim(); if(Input.equals("Y") || Input.equals("y")) done = false; else if(Input.equals("N") || Input.equals("n")) done = true; else { JOptionPane.showMessageDialog(null, "Unexpected Response."); continue; } } } /** * Method findEmployee finds and prints the Employee information based * on the ID number provided by the user. */ public static void findEmployee() { Employee3 Temp = new Employee3(); boolean done = false; while(!done) { Temp = Employee3.getEmployee(); Hashable Emp = Temp.retrieve(Temp); if( Emp != null) JOptionPane.showMessageDialog(null, " The Employee found is " + Emp); else

Page 330: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 25 of 61

JOptionPane.showMessageDialog(null,"Employee not found"); String Input = JOptionPane.showInputDialog("More Employees to search? " + "Enter [Y]es or [N]o . "); Input.trim(); if(Input.equals("Y") || Input.equals("y")) done = false; else if(Input.equals("N") || Input.equals("n")) done = true; else { JOptionPane.showMessageDialog(null, "Unexpected Response."); continue; } } } }

//Listing 11.7 Though the class Employee3 is fully capable of handling collisions, and can work as well as any other hash table, the performance suffers after the collisions takes place. The reason is that once the collision takes place, the insertion, retrieval, and deletions – all follow an algorithm much slower than O ( 1) due to linear probing. Therefore the performance of most hash tables lie some where between O ( 1 ) to slower than O ( 1 ). Clustering – A side affect created by linear probing A good hash method fills the array elements in such a way that occupied array indices are distributed uniformly throughout the length of the array. For example if we were to fill an array of 100 elements with randomly chosen numbers from 0 to 999, with hash code given by the method number mod 100, then in the early stages of filling the table it may look like Figure 11.10.

Page 331: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 26 of 61

FIG. 11.10 In Figure 11.10, no collisions have taken place yet and load factor of hash table, which is defined, as percentage of array slots filled is only 6%, which is very low. Figure 11.11 shows the first instance of collision, where the number 757 had to be placed in the slot 58, because the number 357 already occupied slot number 57. The load factor of hash table in Figure 11.11 is 17%.

Page 332: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 27 of 61

FIG. 11.11 As hash table is loaded further, the collision frequency increases and consequently the clustering increases as well. After adding 43 elements or a load factor of 43%, we see that cluster around the index 57 keeps growing (Figure 11.12).

Page 333: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 28 of 61

FIG. 11.12 The efficiency of hash table operations such as inserting, retrieving and deleting becomes inconsistent due to clustering. For example in Figure 11.12, one would need three probing to retrieve the number 659, but only one probing to find a number in the areas, where no clustering took place. As more elements are added and the load factor of the hash table grows, the existing clusters grow and new clusters are formed – all resulting in degraded performance of the hash table. Re-hashing Re-hashing, where if the collision occurs, then the hash method computes a new hash value by using the previously calculated value as an input, may reduce some of the problems of linear probing. For example in Figure 11.13 the insertion of number, 14001 will create a hash code of 01, an array index, already occupied by the number 44001. Therefore we re-hash using the formula: (hash value + 3 ) % array size resulting in the new location being 4, which is also occupied. One more re-hashing would calculate the location for 14001 to be 07, which is also occupied. Finally (7 + 3 ) % 100 yields a location of 10, which is available.

Clustering

Page 334: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 29 of 61

FIG. 11.13 Re-hashing that use linear probing does not eliminate the problem of clustering, though it may reduce number of collisions. There are other techniques, such as quadratic probing and random probing (Figure 11.14), which may be used to reduce collisions and clustering. However, none of these solutions are as effective as a technique called buckets and chaining – which we discuss next. Buckets and Chaining So far, we limited ourselves to placing only one Object at a location indicated by the hash key. What will happen if we could place all the Objects with same key in the location indicated by the key? This could certainly be accomplished by using a two

•Quadratic probing: Resolving a hash collision by using the rehashing formula (HashValue + I2) % array size, where I is the number of times the rehash function has been applied. •Random probing: Resolving a hash collision by generating-random hash values in successive applications of the rehash function FIG. 11.14

Page 335: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 30 of 61

dimensional array or a linked list. When a two dimensional array is used, then the resulting structure is called a “bucket” structure (Figure 11.15).

FIG. 11.15 If the objects with the same key are placed in a linked list at the same key location, then the resulting structure is called a chain structure (Figure 11.16).

FIG. 11.16

Page 336: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 31 of 61

Java Support for Hashing Java provides number of classes that one can use to store objects and their keys. One such class is HashMap and another is Hashtable. The class HashMap is non-synchronized, whereas Hashtable is synchronized. If synchronization is not needed then the HashMap will work a bit faster than the Hashtable. Class Hashtable stores data in key, value pairs. It also requires that for every object to be stored, the key be unique. The key must be a java object. The class used as a key must have the equals and hashCode methods from java.lang.Object class overridden “properly”. The objects of the class being stored as value in the hash table do not need to have equals and hashCode methods overridden, although there is no harm in doing so. It is “required” that the object being used as key does not change, once the key – value pair has been placed in the hash table. Understand as to what could happen if key were altered after being placed in the hash table? This means that when you search for the value associated with the key that was altered, you would not be able to find that key and hence the value associated with it. Therefore, it is imperative that the classes whose objects are used as a key do not have any methods to alter the data members after the object is created. Better yet, the class for the key must be declared final, so the key objects are immutable. Table 11.4 shows the summary of constructors and methods for the class java.util.Hashtable.

Constructor Summary

Hashtable() Constructs a new, empty hashtable with a default initial capacity (11) and load factor, which is 0.75.

Hashtable(int initialCapacity)

Constructs a new, empty hashtable with the specified initial capacity and default load factor, which is 0.75.

Hashtable(int initialCapacity, float loadFactor)

Constructs a new, empty hashtable with the specified initial capacity and the specified load factor.

Hashtable(Map t) Constructs a new hashtable with the same mappings as the given Map.

Method Summary

void clear() Clears this hashtable so that it contains no keys.

Page 337: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 32 of 61

Object clone() Creates a shallow copy of this hashtable.

boolean contains(Object value)

Tests if some key maps into the specified value in this hashtable.

boolean containsKey(Object key)

Tests if the specified object is a key in this hashtable.

boolean containsValue(Object value)

Returns true if this Hashtable maps one or more keys to this value.

Enumeration elements() Returns an enumeration of the values in this hashtable.

Set entrySet() Returns a Set view of the entries contained in this Hashtable.

boolean equals(Object o)

Compares the specified Object with this Map for equality, as per the definition in the Map interface.

Object get(Object key)

Returns the value to which the specified key is mapped in this hashtable.

int hashCode() Returns the hash code value for this Map as per the definition in the Map interface.

boolean isEmpty() Tests if this hashtable maps no keys to values.

Enumeration keys() Returns an enumeration of the keys in this hashtable.

Set keySet() Returns a Set view of the keys contained in this Hashtable.

Object put(Object key, Object value)

Maps the specified key to the specified value in this hashtable.

void putAll(Map t)

Copies all of the mappings from the specified Map to this Hashtable These mappings will replace any mappings that this Hashtable had for any of the keys currently in the specified Map.

Page 338: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 33 of 61

protected void

rehash() Increases the capacity of and internally reorganizes this hashtable, in order to accommodate and access its entries more efficiently.

Object remove(Object key)

Removes the key (and its corresponding value) from this hashtable.

int size() Returns the number of keys in this hashtable.

String toString() Returns a string representation of this Hashtable object in the form of a set of entries, enclosed in braces and separated by the ASCII characters ", " (comma and space).

Collection values() Returns a Collection view of the values contained in this Hashtable

Table 11.4 The class java.util.Hashtable has many constructors and methods, whose details are best presented by the application programming interface (API) for this class by Sun Micro System. We would discuss the important methods here. Putting objects in Hashtable Method put takes a key and a value as arguments and places the pair of values in the hash table. The method putAll can take an object of type java.util.Map and place all the mapped values in the hash table. Method put returns a null if the value placed is not already present in the table. If a value corresponding to the key being placed in the table is already present, then that value is deleted and returned. Note, that this behavior does not mean that Hashtable class does not handle collisions. If you would recall, the collisions has to do with two objects having the same hash code. If two keys have, same hash code but the equals method deem them different, then they would be stored in the same bucket and collision is handled. However, Hashtable class does not allow two identical keys with the same hash code to be stored in the table – even though that is another example of collision. One would argue that we were able to do so in our early example, where using linear probing, we were able to store two employees with the same ID number in our home grown hash table. The reason has to do with the mechanism java uses to place key value pairs in the hash table and then retrieve them. Java depends upon the key for each value being unique in order to search that value in the hash table. If two unequal keys map to the same hash code, then java forms a linked list at that hash code location, placing multiple key, value pairs in one bucket (Figure 11.17).

Page 339: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 34 of 61

FIG. 11.17 What would happen if two entries in the bucket zero in Figure 11.17 had the same keys? There would be no way for java to find out as to which of the value to return when the user searches key with multiple values. In this sense, in java.util.Hashtable, the key behaves similar to the primary key in a relational database. No two keys may be identical. Retrieving the objects from the Hashtable If the toString( ) method is overridden in the classes used for Key and the Values, then the hash table can simply be printed by calling System.out.println and passing the hash table object as an argument. The objects stored as values may be printed by calling the method Values ( ), which returns the Collection of all value objects. Passing this Collection as an argument to System.out.println will print them. Alternatively the method elements ( ) will return an Enumeration to the Hashtable, which can be printed by using the methods hasMoreElements( ) and nextElement ( ). The method keys ( ) returns an Enumeration for all the keys in the hash table. Method get ( ) takes a key object as argument and returns the value object mapped to it. Removing the elements from the hash table Method remove ( ) takes a key as an arguments and removes that key and the value associated with it from the hash table. Method clear ( ) removes all the keys from the hash table, thus clearing it for new storage.

Page 340: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 35 of 61

Searching the hash table Method containsKey ( ) takes a key object as arguments and returns a true if the key is in the hash table, otherwise returns false. Method containsValue ( ) does the same thing for searching a value in the table. Method contains ( ), functions exactly the way method containsValue ( ) does. Methods related to the aggregate properties and operations on Hashtable: The method size ( ) returns an integer which gives the size of the Hashtable. Method isEmpty ( ) returns true if Hashtable is empty else returns a false. Method equals ( ) takes an Object as its argument and returns a true if this Object has the same contents as the Hashtable does. Method clone ( ) returns a shallow copy of the hash table, and method hashCode ( ) returns the hash code for the Hashtable object. Overriding the methods hashCode ( ) and equals ( ) for the class used as key in the hash table Putting objects in the Hashtable requires that the class associated with keys override the java.lang.Object class methods hashCode ( ) and equals ( ) properly. Details of what constitutes proper overriding of java.lang.Object class is given in the java API for the class Object. It is required that the equals method has the following five properties:

1. It is reflexive – meaning that for a reference x, x.equals (x) must return true.

2. It is symmetric – meaning that for any reference values x and y, y.equals (x) must return true only if x.equals (y) also returns true.

3. It is transitive – meaning that for any reference values x, y, and z; if x.equals (y) returns true and y.equals (z) returns true, then x.equals (z) must return true.

4. It is consistent – meaning that for any values of x and y multiple invocations of x.equals (y) must return true or false, provided that objects x and y are not modified between the calls to the method equals.

5. For a null reference value, the x.equals (null) must return false. Overriding method hashCode( ) for the key A hash-based collection may become corrupt if one only overrides the equals method for the key and not the hashCode method as well. The reason is that in the absence of overridden hashCode method, the Object class hashCode method is called, which only returns the memory address of an object. In that case, two keys, which are identical will map to two different locations in the hash table. When retrieving a value object attached with a certain key, java depends upon the value of the hash code for the same key being the same every time. However, if the value of the hash code were merely the memory address of the object, one would not be able

Page 341: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 36 of 61

to locate it in the hash table, as this address would be different each time. We illustrate this problem by using a class EmployeeKeyNoHashCodeMethod as the class for the key and the class Employee2 as the class for the values (Listings 11.8 and 11.9).

public final class EmployeeKeyNoHashCodeMethod { private int id_num; private String Name; public EmployeeKeyNoHashCodeMethod() { this(0); } public EmployeeKeyNoHashCodeMethod(int init_id) { this(init_id,null); } public EmployeeKeyNoHashCodeMethod(int init_id, String Init_Name) { id_num = init_id; Name = Init_Name; } public boolean equals(Object obj) { if(this == obj) return true; else if(!(obj instanceof EmployeeKeyNoHashCodeMethod)) return false; else { EmployeeKeyNoHashCodeMethod Temp = (EmployeeKeyNoHashCodeMethod)(obj); return id_num == Temp.id_num && Name.equals(Temp.Name); } } public String toString() { return "ID number = "+ id_num + " Name = " + Name+ " "; } }//Listing 11.8

Page 342: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 37 of 61

import java.util.*; import javax.swing.*; import EmployeeKeyNoHashCodeMethod; public class Employee2 { private EmployeeKeyNoHashCodeMethod Data; private long phone; public Employee2() { this(0); } public Employee2(int init_id) { this(init_id,null); } public Employee2(int init_id, String Init_Name) { this(init_id,Init_Name,0); } public Employee2(int init_id, String Init_Name, long init_phone) { Data = new EmployeeKeyNoHashCodeMethod(init_id,Init_Name); phone = init_phone; } public Employee2(EmployeeKeyNoHashCodeMethod Init_Data, long init_phone) { Data = Init_Data; phone = init_phone; } public static Employee2 getEmployee2() { Employee2 Temp = new Employee2(); EmployeeKeyNoHashCodeMethod Key = Employee2.getEmployeeKey(); long temp_phone = Employee2.getPhone(); Temp = new Employee2(Key,temp_phone); return Temp; } public String toString() { return Data.toString()+ " Phone Number = " + phone+ " \n"; } public static void findEmployee(Hashtable Employee2_List) { boolean done = false; while(!done) { //get the Employee JOptionPane.showMessageDialog(null, "Enter the data for” + “ employee search");

Page 343: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 38 of 61

EmployeeKeyNoHashCodeMethod Key = getEmployeeKey(); Employee2 Obj = (Employee2)(Employee2_List.get(Key)); if( Obj != null) JOptionPane.showMessageDialog(null, " The Employee found is " + Obj); else JOptionPane.showMessageDialog(null,"No Employee with key" + Key + " found "); String Input = JOptionPane.showInputDialog("More Employees to search? " + "Enter [Y]es or [N]o . "); Input.trim(); if(Input.equals("Y") || Input.equals("y")) done = false; else done = true; } } public static EmployeeKeyNoHashCodeMethod getEmployeeKey() { EmployeeKeyNoHashCodeMethod Temp = new EmployeeKeyNoHashCodeMethod(); boolean done = false; String Temp_Name = ""; String Input = ""; int val=0; while(!done) { Input= JOptionPane.showInputDialog("Please enter the " + "Employee 5 digit ID number . "); Input = Input.trim(); try { val= Integer.parseInt(Input); } catch(NumberFormatException ex) { JOptionPane.showMessageDialog(null, "Illegal ID number entered"); continue; } if(val<0) { JOptionPane.showMessageDialog(null, "Negative ID number entered"); continue; } boolean done_name = false; while(!done_name) { Input = JOptionPane.showInputDialog("Please enter the " + "Employee Name . "); Input = Input.trim(); if(Input.equals("") || Input == null) { JOptionPane.showMessageDialog(null, "No name entered.");

Page 344: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 39 of 61

done_name = false; } else { Temp_Name = Input; done_name = true; } } Temp = new EmployeeKeyNoHashCodeMethod(val,Temp_Name); done = true; } return Temp; } public static long getPhone() { boolean done = false; long temp_phone = 0; String Input = ""; while(!done) { Input = JOptionPane.showInputDialog("Please enter the " + "Employee phone number as xxxyyyy . "); Input = Input.trim(); if(Input.equals("") || Input == null) { JOptionPane.showMessageDialog(null, "No phone # entered."); done = false; } else { try { temp_phone = Long.parseLong(Input); } catch(NumberFormatException ex) { JOptionPane.showMessageDialog(null, "Illegal phone number entered"); done =false; } if(temp_phone <0) { JOptionPane.showMessageDialog(null, "Negative phone number entered"); done = false; } else { done = true; } } } return temp_phone; }

Page 345: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 40 of 61

public EmployeeKeyNoHashCodeMethod getKey() { return this.Data; } }

//Listing 11.9 The class Employee2 has two instance variables for an employee. These are: EmployeeKeyNoHashCodeMethod and phone number. The class EmployeeKeyNoHashCodeMethod has two instance variables – Name and ID number. Therefore, by the composition relationship, the class Employee2 ends up including the instance variables of class EmployeeKeyNoHashCodeMethod as well. Note that the class EmployeeKeyNoHashCodeMethod has an equals method, but no hashCode ( ) method. The class EmployeeKeyNoHashCodeMethod is used as a key for the objects of class Employee2. We write a driver class to test the classes in Listing 11.8 and 11.9. This class is EmployeeTest1 given in Listing 11.10.

import java.util.*; import javax.swing.*; public class EmployeeTest1 { public static void main(String [] args) { Hashtable Employee2_List = new Hashtable(); Employee2_List.put(new EmployeeKeyNoHashCodeMethod(31300,"Adam"), new Employee2(new EmployeeKeyNoHashCodeMethod(31300,"Adam"), 1112222)); Employee2_List.put(new EmployeeKeyNoHashCodeMethod(49001,"Bertha"), new Employee2(new EmployeeKeyNoHashCodeMethod(49001,"Bertha"), 2223333)); Employee2_List.put(new EmployeeKeyNoHashCodeMethod(52202,"Cynthia"), new Employee2(new EmployeeKeyNoHashCodeMethod(52202,"Cynthia"), 3334444)); //Print the hashtable. Prints both the Key and values System.out.println("Now printing the entire Hashtable you built."); System.out.println(Employee2_List); //Print only the values, not the keys System.out.println("Now printing only the values in the Hashtable."); System.out.println(Employee2_List.values()); //Searching the hash table System.out.println("Now printing the size of Hashtable you built."); System.out.println("Size of list = " + Employee2_List.size()); Employee2.findEmployee(Employee2_List); System.exit(0); } }//Listing 11.10

Page 346: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 41 of 61

We instantiate a Hashtable object Employee2_List and place in it, the values for three employees whose names and ID numbers are same as the first three employees in Table 11.3, except that now we have added the phone numbers also for each employee. There is no problem in placing the employees in the hash table. The Figure 11.18 below shows the screen shot of results obtained after the class EmployeeTest1 is executed.

FIG. 11.18 Problem comes when we try to search the hash table for a given employee. Even with the correct data for the key, that employee is not found. We enter the employee ID number for Bertha (49001) (Figure 11.19 A), and name Bertha (Figure 11.19B). However, the results are horrid, as the message comes that the employee Bertha with ID number 49001 is not found in the hash table (Figure 11.19C).

FIG. 11.19A

Page 347: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 42 of 61

FIG. 11.19B

FIG. 11.19C This is even more distressing, because when we print the hash table we clearly see the employee we are searching for is there. The reason is simple. The key for employee Bertha is mapped to a different hash location each time – first when we put it in the table, and second when we try to search the employee. This is so because in the absence of hashCode method in the class EmployeeKeyNoHashCodeMethod, the hash code is computed by the method in the Object class, which returns a different hash code even for the objects, which are identical. The solution is deceptively simple. Add a hashCode method in the class EmployeeKeyNoHashCodeMethod – even one as bad as the one shown in Listing 11.11.

public int hashCode( ) { return 59878489; } //Listing 11.11 The hashCode ( ) method such as above will return the same hash code for all keys. This will degrade a hash table into a linear linked list, because all the values will be stored in the same bucket, but it will at least work. Let is call the above hashCode method as minimal hashCode method, and the modified Employee key class which contains the method in Listing 11.11 as the class EmployeeKeyMinimalHashCodeMethod. Since this class is exactly like the class in

Page 348: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 43 of 61

Listing 11.9, we do not show its code here. We also however need to alter the classes Employee2 and EmployeeTest1. The employee class that contains EmployeeKeyMinimalHashCodeMethod as its data member and the class testing both of them are Employee4.java and EmployeeTest2.java – both available in the appendix at the end of this chapter. When the class EmployeeTest2 is executed the first part of the results are similar to the Figures 11.19A and 11.19B. However, the major change takes place in the search results. Now, when we search for the employee Bertha with ID number 49001, the method get from Hashtable class does find the entry for such employee (Figure 11.20).

FIG. 11.20 A good hashCode method. Having established that a bad hashCode method is better than no hashCode method at all, we must find a way to write a good hashCode method. Characteristics of a good hashCode method are similar to a good hashing method or hash function, which we have discussed all through this chapter. In summary a good hashCode method must:

• Provide the same hash code for the two objects that are equal. For example if: x.equals (y) is true, the also it must be that x.hashCode ( ) = y.hashCode ( ). This does not necessitate that two different objects cannot have same hashCode. After all that is what causes collisions. And it would be impossible to write a collision-free hashCode ( ) method. This provision only necessitates that identical objects have the same hash code.

• Provide an even distribution of the hash code values in the range zero to the Integer.MAX_VALUE, the latter being a constant in the java.lang.Integer class.

• Returns the hash value fast since the method is called each time a key value pair is placed in the Hashtable and also when the objects are searched with their keys.

• Have a simple code. Obviously some of these requirements conflict with each other and one need to come up with a good compromise.

Page 349: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 44 of 61

A good hashCode method would depend upon a good key class designed for an object. The hashCode method must use the object data fields, which do not need to change with time. For example in our employee class it is very unlikely that there would be a need to change employee name or ID number unless some very special circumstances required it. Therefore in all the employee key classes we designed, we put the name and ID number as the instance variables for the class which would act as the key for the employees. In addition, we make the EmployeeKey classes final, which makes the objects of key classes immutable. The Table 11.5 below shows as to how we can convert each data types in the key class into a corresponding hash code and then sum up the results.

Data Type Instance Name Hash code Multiply with a prime number

Data2 variable new Data(variable).hashCode( )

X

String Str Str.hashCode ( ) Y

Final hash code value = Σ X * (new Data (variable).hashCode ( ) + Y*Str.hashCode( )

Table 11.5 Advantage of multiplying with a prime number is that it would result in a better distribution of hash values over a wide range. Bigger prime numbers lead to wider distribution. Of course, one should be careful that hash value does not exceed the maximum allowable for int memory. For our Employee class we use a string for name and int for the ID number. Therefore, we write the hashCode method for the class EmployeeKey, to be used as a Key in the Hashtable as given in Listing 11.12

public int hashCode( ) { return 7*Name.hashCode( )+13*new Integer(id_num).hashCode( ); } //Listing 11.12 We have written class Employee5.java and EmployeeTest3.java to prepare and test the hash table for the objects of class Employee5. The class EmployeeTest3.java is given in Listing 11.13, whereas the summary of classes EmployeeKey.java and Employee5.java is given in Tables 11.6 and 11.7.

Field Summary

private int id_num Identification number of each employee.

2 Data could be int, float, short, char or any other java primitive type.

Page 350: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 45 of 61

private java.lang.String

Name Name of the each employee.

Constructor Summary

EmployeeKey() No argument constructor.

EmployeeKey(int init_id) One argument constructor.

EmployeeKey(int init_id, java.lang.String Init_Name)

Two argument constructor.

Method Summary

boolean equals(java.lang.Object obj)

Overrides the equals method in the java.lang.Object class

int hashCode()

Overrides the hashCode method in class java.lang.Object.

java.lang.String toString()

Overrides the toString method in java.lang.Object class.

Methods inherited from class java.lang.Object

, clone, finalize, getClass, notify, notifyAll, registerNatives, wait, wait, wait

Table 11.6

Field Summary

private EmployeeKey

Data Data is the Employee Key, which includes the employee name and ID number.

private phone

Page 351: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 46 of 61

long The phone is the 7 digit employee phone number entered as xxxyyyy.

Constructor Summary

Employee5() Argument-less constructor sets id_num to zero and Name to blank and phone number to zero.

Employee5(EmployeeKey Init_Data, long init_phone)

The constructor with arguments as EmployeeKey and long data types sets the ID number and name by calling the EmployeeKey constructor.

Employee5(int init_id) The one argument constructor sets the id_num equal to the constructor argument and the Name to blank and phone number to zero.

Employee5(int init_id, java.lang.String Init_Name)

The two argument constructor sets the id_num equal to the first argument and Name equal to the second argument.

Employee5(int init_id, java.lang.String Init_Name, long init_phone) The three arguemnt constructor initializes all three, the ID number, name and phone number.

Method Summary

static void deleteEmployee5(java.util.Hashtable List) Takes the hash table for the Employee5 as argument, and deletes the employee from the table.

static void findEmployee(java.util.Hashtable Employee5_List)

Takes the Employee Hashtable as an argument and finds and displays the results of search queries on the hash table.

static Employee5 getEmployee5()

The static method getEmployee5 takes the user input for the data for one employee and returns the reference to constructed Employee5 object.

Page 352: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 47 of 61

static EmployeeKey getEmployeeKey()

Helper method to get the employee key and return a reference to it.

EmployeeKey getKey()

Returns the Data part of the Employee5 object, which is of type EmployeeKey.

static long getPhone()

Helper method to get the employee phone number.

static void insertEmployee5(java.util.Hashtable List) Inserts the employees in the hash table.

java.lang.String toString()

Returns a string representation of the Employee.

Methods inherited from class java.lang.Object

, clone, equals, finalize, getClass, hashCode, notify, notifyAll, registerNatives, wait, wait, wait

Table 11.7 import java.util.*; import javax.swing.*; public class EmployeeTest3 { public static void main(String [] args) { //Creates a hash table of default size Hashtable List = new Hashtable(); String Input = ""; do { Input =JOptionPane.showInputDialog("[I]nsert, [D]elete, [S]earch, [P]rint "+ "employees? [C]opy employee list Or [E]xit?"); Input = Input.trim(); if(Input.equals("I")) { //Fill the hash table Employee5.insertEmployee5(List); } else if(Input.equals("D")) { Employee5.deleteEmployee5(List);

Page 353: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 48 of 61

} else if(Input.equals("S")) { Employee5.findEmployee(List); } else if(Input.equals("P")) { //Print the hashtable. Prints both the Key and values ShowOutput("Now printing the entire Hashtable you built."); ShowOutput(List.toString()); //Print only the values, not the keys ShowOutput("Now printing only the values in the Hashtable."); ShowOutput((List.values()).toString()); ShowOutput("Now printing the size of Hashtable you built."); ShowOutput("Size of list = " + List.size()); } else if(Input.equals("C")) { //Test the method putAll Hashtable Copy_List = new Hashtable(); Copy_List.putAll(List); ShowOutput("Now printing the copy of previous hash table."); ShowOutput(Copy_List.toString()); ShowOutput("Now testing the equals method of class Hashtable."); if(Copy_List.equals(List)) ShowOutput("The copy list and original lists are same."); else ShowOutput("The copy list and original lists are not same."); } else if(Input.equals("E")) break; else { JOptionPane.showMessageDialog(null,"Illegal input."); continue; } }while(Input.equals("I")||Input.equals("D")||Input.equals("P")|| Input.equals("S")||Input.equals("C")); //Test the enumeration Enumeration it = List.elements(); ShowOutput("Now printing all hash table elements sequentially"); String Out = ""; while(it.hasMoreElements()) Out+=(it.nextElement()).toString(); ShowOutput(Out);

Page 354: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 49 of 61

System.exit(0); } public static void ShowOutput(String Input) { JOptionPane.showMessageDialog(null, Input); } }Listing 11.13 The full code for the classes EmployeeKey.java and Employee5.java is given in the appendix at the end of the chapter. The class EmployeeTest3 tests the system of classes EmployeeKey, Employee5 and java.util.Hashtable systematically. The user is prompted to enter their choice of activity by a JOptionPane dialog box (Figure 11.21) to insert, delete, search an employee or print/copy the hash table.

FIG. 11.21 If an employee with certain key already exists in the hash table, that employee is not added and a message to that affect is displayed (Figure 11.22)

FIG. 11.22 The hash table built is displayed when user chooses to print it (Figure 11.23).

Page 355: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 50 of 61

FIG. 11.23 The classes used here test many other methods of class java.util.hashtable, although no effort is made to test them all as the purpose of current exercise was to create a model for using the java’s Hashtable class for data storage. Using this model the Hashtable class may be used for any other type of data storage and retrieval. Appendix import java.util.*; import javax.swing.*; public class Employee4 { private EmployeeKeyMinimalHashCodeMethod Data; private long phone; public Employee4() { this(0); } public Employee4(int init_id) { this(init_id,null); } public Employee4(int init_id, String Init_Name) { this(init_id,Init_Name,0); } public Employee4(int init_id, String Init_Name, long init_phone) { Data = new EmployeeKeyMinimalHashCodeMethod(init_id,Init_Name); phone = init_phone;

Page 356: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 51 of 61

} public Employee4(EmployeeKeyMinimalHashCodeMethod Init_Data, long init_phone) { Data = Init_Data; phone = init_phone; } public static Employee4 getEmployee4() { Employee4 Temp = new Employee4(); EmployeeKeyMinimalHashCodeMethod Key = Employee4.getEmployeeKey(); long temp_phone = Employee4.getPhone(); Temp = new Employee4(Key,temp_phone); return Temp; } //Must have a tostring method overridden to print the //Employee4 in the hashtable properly. public String toString() { return Data.toString()+ " Phone Number = " + this.phone+ " \n"; } public static void findEmployee(Hashtable Employee4_List) { boolean done = false; while(!done) { //get the Employee JOptionPane.showMessageDialog(null, "Enter the data for” + “ employee search"); EmployeeKeyMinimalHashCodeMethod Key = getEmployeeKey(); Employee4 Obj = (Employee4)(Employee4_List.get(Key)); if( Obj != null) JOptionPane.showMessageDialog(null, " The Employee found is " + Obj); else JOptionPane.showMessageDialog(null,"No Employee with key " + Key + " found "); String Input = JOptionPane.showInputDialog("More Employees to search? " + "Enter [Y]es or [N]o . "); Input.trim(); if(Input.equals("Y") || Input.equals("y")) done = false; else done = true; } } public static EmployeeKeyMinimalHashCodeMethod getEmployeeKey() { EmployeeKeyMinimalHashCodeMethod Temp = new EmployeeKeyMinimalHashCodeMethod(); boolean done = false; String Temp_Name = ""; String Input = "";

Page 357: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 52 of 61

int val=0; while(!done) { Input= JOptionPane.showInputDialog("Please enter the " + "Employee 5 digit ID number . "); Input = Input.trim(); try { val= Integer.parseInt(Input); } catch(NumberFormatException ex) { JOptionPane.showMessageDialog(null, "Illegal ID number entered"); continue; } if(val<0) { JOptionPane.showMessageDialog(null, "Negative ID number entered"); continue; } boolean done_name = false; while(!done_name) { Input = JOptionPane.showInputDialog("Please enter the " + "Employee Name . "); Input = Input.trim(); if(Input.equals("") || Input == null) { JOptionPane.showMessageDialog(null, "No name entered."); done_name = false; } else { Temp_Name = Input; done_name = true; } } Temp = new EmployeeKeyMinimalHashCodeMethod(val,Temp_Name); done = true; } return Temp; } public static long getPhone() { boolean done = false; long temp_phone = 0; String Input = "";

Page 358: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 53 of 61

while(!done) { Input = JOptionPane.showInputDialog("Please enter the " + "Employee phone number as xxxyyyy . "); Input = Input.trim(); if(Input.equals("") || Input == null) { JOptionPane.showMessageDialog(null, "No phone # entered."); done = false; } else { try { temp_phone = Long.parseLong(Input); } catch(NumberFormatException ex) { JOptionPane.showMessageDialog(null, "Illegal phone number entered"); done =false; } if(temp_phone <0) { JOptionPane.showMessageDialog(null, "Negative phone number entered"); done = false; } else { done = true; } } } return temp_phone; } public EmployeeKeyMinimalHashCodeMethod getKey() { return this.Data; } }

import java.util.*; import javax.swing.*; public class EmployeeTest2 { public static void main(String [] args) { //Creates a hash table of default size Hashtable Employee2_List = new Hashtable(); Employee2_List.put(new EmployeeKeyMinimalHashCodeMethod(31300,"Adam"), new Employee4(new EmployeeKeyMinimalHashCodeMethod(31300,"Adam"), 1112222));

Page 359: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 54 of 61

Employee2_List.put(new EmployeeKeyMinimalHashCodeMethod(49001,"Bertha"), new Employee4(new EmployeeKeyMinimalHashCodeMethod(49001,"Bertha"), 2223333)); Employee2_List.put(new EmployeeKeyMinimalHashCodeMethod(52202,"Cynthia"), new Employee4(new EmployeeKeyMinimalHashCodeMethod(52202,"Cynthia"), 3334444)); //Print the hashtable. Prints both the Key and values System.out.println("Now printing the entire Hashtable you built."); System.out.println(Employee2_List); //Print only the values, not the keys System.out.println("Now printing only the values in the Hashtable."); System.out.println(Employee2_List.values()); //Searching the hashtable System.out.println("Now printing the size of Hashtable you built."); System.out.println("Size of list = " + Employee2_List.size()); Employee4.findEmployee(Employee2_List); System.exit(0); } }

public final class EmployeeKey{ /** * Identification number of each employee. It is expected to be * unique for each employee. */ private int id_num; /** * Name of the each employee. Two employees with the same name could * be found. */ private String Name; /** * No argument constructor. */ public EmployeeKey() { this(0); } /** * One argument constructor. * @param init_id is the value of the employee ID. */ public EmployeeKey(int init_id) { this(init_id,null); } /** * Two argument constructor.

Page 360: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 55 of 61

* @param init_id is the value of the employee ID. * @param Init_Name is the Employee name. */ public EmployeeKey(int init_id, String Init_Name) { id_num = init_id; Name = Init_Name; } /** * Overrides the hashCode method in class java.lang.Object. * @return the value of the hash code for the object of class * <code>EmployeeKey</code>. */ public int hashCode() { return 7*Name.hashCode()+13*new Integer(id_num).hashCode(); } /** * Overrides the equals method in the java.lang.Object class * @param obj is the <code>Object</code> to be tested for equality. * @return true if obj is identical to the caller object, else * returns false. */ public boolean equals(Object obj) { if(this == obj) return true; else if(!(obj instanceof EmployeeKey)) return false; else { EmployeeKey Temp = (EmployeeKey)(obj); return this.id_num == Temp.id_num && this.Name.equals(Temp.Name); } } /** * Overrides the toString method in java.lang.Object class. * @return the <code>String</code> representation of class <code> * EmployeeKey</code>. */ public String toString() { return "ID number = "+ id_num + " Name = " + Name+ " "; } }

Page 361: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 56 of 61

import java.util.*; import javax.swing.*; public class Employee5 { /** * Data is the Employee Key, which includes the employee name * and ID number. */ private EmployeeKey Data; /** * The phone is the 7 digit employee phone number entered as xxxyyyy. */ private long phone; /** * Argument-less constructor sets id_num to zero and Name to blank and * phone number to zero. */ public Employee5() { this(0); } /** * The one argument constructor sets the id_num equal to the constructor * argument and the Name to blank and phone number to zero. * @param init_id is the value of the field id_num. */ public Employee5(int init_id) { this(init_id,null); } /** * The two argument constructor sets the id_num equal to the first argument * and Name equal to the second argument. The phone number is set * to zero. * @param init_id is the value of the field id_num. * @param Init_Name is the value of field Name. */ public Employee5(int init_id, String Init_Name) { this(init_id,Init_Name,0); } /** * The three arguemnt constructor initializes all three, the ID * number, name and phone number. * @param init_id is the value of the field id_num. * @param Init_Name is the value of field Name. * @param init_phone is the value of the field phone. */ public Employee5(int init_id, String Init_Name, long init_phone) {

Page 362: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 57 of 61

Data = new EmployeeKey(init_id,Init_Name); phone = init_phone; } /** * The constructor with arguments as EmployeeKey and long data types * sets the ID number and name by calling the EmployeeKey constructor. * @param Init_Data sets the fields Name and id_num of Object Data * @param init_phone is the employee phone number. */ public Employee5(EmployeeKey Init_Data, long init_phone) { Data = Init_Data; phone = init_phone; } /** * The static method getEmployee5 takes the user input for the data for * one employee and returns the reference to constructed Employee5 object. * @return the constructed <code>Employee5</code> from user data. */ public static Employee5 getEmployee5() { Employee5 Temp = new Employee5(); EmployeeKey Key = Employee5.getEmployeeKey(); long temp_phone = Employee5.getPhone(); Temp = new Employee5(Key,temp_phone); return Temp; } /** * Returns a string representation of the Employee. The method returns * the id_num, Name and phone number concatenated in one string identifying each. * @return a string representation of the object. */ public String toString() { return Data.toString()+ " Phone Number = " + this.phone+ " \n"; } /** * Takes the Employee Hashtable as an argument and finds and * displays the results of search queries on the hash table. * @param Employee5_List is the hash table of objects of type * <code>Employee5</code>. */ public static void findEmployee(Hashtable Employee5_List) { boolean done = false; while(!done) { //get the Employee

Page 363: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 58 of 61

JOptionPane.showMessageDialog(null, "Enter the data for “ + “ employee search"); EmployeeKey Key = getEmployeeKey(); Employee5 Obj = (Employee5)(Employee5_List.get(Key)); if( Obj != null) JOptionPane.showMessageDialog(null, " The Employee found is " + Obj); else JOptionPane.showMessageDialog(null,"No Employee with key " + Key + " found "); String Input = JOptionPane.showInputDialog("More Employees to search? " + "Enter [Y]es or [N]o . "); Input.trim(); if(Input.equals("Y") || Input.equals("y")) done = false; else done = true; } } /** * Helper method to get the employee key and return a reference to it. * @return the object of type <code>EmployeeKey</code>. */ public static EmployeeKey getEmployeeKey() { EmployeeKey Temp = new EmployeeKey(); boolean done = false; String Temp_Name = ""; String Input = ""; int val=0; while(!done) { Input= JOptionPane.showInputDialog("Please enter the " + "Employee 5 digit ID number . "); Input = Input.trim(); try { val= Integer.parseInt(Input); } catch(NumberFormatException ex) { JOptionPane.showMessageDialog(null, "Illegal ID number entered"); continue; } if(val<0) { JOptionPane.showMessageDialog(null, "Negative ID number entered"); continue; } boolean done_name = false; while(!done_name) {

Page 364: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 59 of 61

Input = JOptionPane.showInputDialog("Please enter the " + "Employee Name . "); Input = Input.trim(); if(Input.equals("") || Input == null) { JOptionPane.showMessageDialog(null, "No name entered."); done_name = false; } else { Temp_Name = Input; done_name = true; } } Temp = new EmployeeKey(val,Temp_Name); done = true; } return Temp; } /** * Helper method to get the employee phone number. * @return the employee phone number from user input. */ public static long getPhone() { boolean done = false; long temp_phone = 0; String Input = ""; while(!done) { Input = JOptionPane.showInputDialog("Please enter the " + "Employee phone number as xxxyyyy . "); Input = Input.trim(); if(Input.equals("") || Input == null) { JOptionPane.showMessageDialog(null, "No phone # entered."); done = false; } else { try { temp_phone = Long.parseLong(Input); } catch(NumberFormatException ex) { JOptionPane.showMessageDialog(null, "Illegal phone number entered"); done =false; } if(temp_phone <0) { JOptionPane.showMessageDialog(null, "Negative phone number entered"); done = false; }

Page 365: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 60 of 61

else { done = true; } } } return temp_phone; } /** * Returns the Data part of the <code>Employee5</code> object, which * is of type <code>EmployeeKey</code>. * @return the <code>EmployeeKey</code> for the object * <code>Employee5</code> object. */ public EmployeeKey getKey() { return this.Data; } /** * Takes the hash table for the <code>Employee5</code> as * argument, and deletes the employee from the table. * @param List is the <code>Hashtable</code> of objects of type * <code>Employee5</code>. */ public static void deleteEmployee5(Hashtable List) { boolean done = false; String Input = ""; while(!done) { EmployeeKey To_Delete = Employee5.getEmployeeKey(); Object Removed = List.remove(To_Delete); if(Removed !=null) JOptionPane.showMessageDialog(null, "The Employee " + Removed + " is deleted from the hash table."); else JOptionPane.showMessageDialog(null, "The Employee with key" + To_Delete +" is not in the hash table."); boolean inner = false; while(!inner) { Input = JOptionPane.showInputDialog("More employees to delete " +" [Y]es ? [N]o ?"); Input = Input.trim(); if(Input.equals("Y") || Input.equals("y")) { inner = true; done = false; } else if(Input.equals("N") || Input.equals("n")) { inner = true; done = true; } else

Page 366: E_Book_JavaDataStructure_SatishSinghal

CS 20 B : Topic 11 Hashing Page 61 of 61

{ JOptionPane.showMessageDialog(null, "Illegal choice"); inner = false; } } } } /** * Inserts the employees in the hash table. * @param List is the <code>Hashtable</code> of objects of type * <code>Employee5</code>. */ public static void insertEmployee5(Hashtable List) { //Build the Employee5 list using a loop boolean done = false; while(!done) { //get the Employee Employee5 Temp = Employee5.getEmployee5(); //Make sure that Employee Temp's Key is not already in the hash table if(List.get(Temp.getKey()) == null ) { List.put(Temp.getKey(),Temp); JOptionPane.showMessageDialog(null, "The employee inserted into the " +" hash table\n"); } else { JOptionPane.showMessageDialog(null, "The Key for the employee just " +" entered was already in the hash table. The employee is not” + “ entered.\n"); continue; } String Input = JOptionPane.showInputDialog("More Employees? " + "Enter [Y]es or [N]o . "); Input.trim(); if(Input.equals("Y") || Input.equals("y")) done = false; else done = true; } } }

References: 1. Hash Table animations: Hashing Animation Tool Animation of Hash Table using a Text file

Page 367: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 1 of 87

CS 20 B: Java Data Structures

Topic 12: Analysis of Algorithms Author: Satish Singhal Ph. D.

Version 1.0 The analysis of algorithm is a field of computer science, which endeavors to understand the complexity of algorithms, and analyze the best, average, and worst case scenarios for algorithms. Donald Knuth founded this field; whose book “Art of Computer Programming1” has become the bible for the researchers and students in this field of computer science. An algorithm is a basic idea behind a computer program, which does not change, with the computer language or hardware. Good algorithms are correct and efficient. Lack of correctness in an algorithm at some point would become a source of bugs in the program written based on that algorithm. Lack of efficiency will make an algorithm too time consuming and expensive to run. However, more to the point, one must look at the price to performance ratio for an algorithm and software containing it. Over the life of the software product, the product that has the lowest cost of ownership with performance that serves the product purpose would win out. Super fast but super expensive algorithms do not always lead to the best software products from the point of view of price to performance ratio, which must be minimized for every engineering product. Therefore it is very useful for a software engineer to analyze the algorithms for their correctness, efficiency and cost and then make the right product decision as to which algorithm must be used in the software product. In this chapter, however, we would only focus on the speeds of known algorithms, hoping that other two issues may be studied at a future date. Worst case, average case, and best case scenario for algorithms: All algorithms may be analyzed for their average, worst case, and best-case efficiency. The worst-case efficiency analysis is also called an asymptotic analysis, as this would pretty much be the case when the software based on algorithms would need to deal with large amount of user data. We illustrate the three efficiency cases by using the problem of finding a key in a sorted array of size N. Imagine we have an array of N integer, which is sorted numerical in the ascending order, such that the smallest value in the array is in the cell with index zero, and the largest value is in the last cell with index N-1. In such an array, we first use the linear search algorithm (ALG1) given below.

1 Art of computer programming volumes 1,2, & 3. Addison Wesley Publishers. (www.awl.com).

Page 368: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 2 of 87

For the algorithm ALG1, three efficiency related scenarios are given in Table 12.1 below. Case Array index at which the

Key is located Time to search is proportional to:

Best case 0 Number close to zero. Average case N/2 N/2 Worst case N-1 Approximately N

Table 12.1 It is possible that by some chance the key to be searched is located in the first array cell. In that case, the search is almost instantaneous and the search time is proportional to a very small number. In worst case, the key to be searched is last value in the array, in which case the search time is nearly proportional to the array size. However, on an average for large number of searches, the search time would be N/2, the average search time. We could say that a mathematical function, which could represent the search time using the ALG1 algorithm for an array, may be given by an expression such as: F (N) = A1*N + A2 …………………………… Expr12.1 Table 12.2 gives the values of A1, and A32for the three algorithmic efficiency cases.

Case A1 A2

Best case 0 For large N, negligible in

comparison to N.

Average case

1/2

Worst case 1 Table 12.2 For vary large value of N, however, the difference between N and N/2 would be rather small. Therefore, the worst case represents the asymptotic value of the function F (N). This is why the worst-case analysis of algorithms is also called an asymptotic analysis. When an algorithm, whose execution time is given by expression such as 12.1, which asymptotically reduces to F (N) = A1*N ……………………….. Expr 12.2 then this algorithm is called an O ( N ) algorithm, because its execution time, in worst case, would be proportional to the N, or the size of the data being processed.

ALG1. Set a Boolean variable to false, Sequentially scan the array from index zero to N-1; If value at the index being scanned is equal to the value being searched Then set a Boolean variable found to true, Else keep scanning, Return the Boolean variable.

Page 369: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 3 of 87

This kind of algorithmic analysis is also called a Big O analysis. Figure 12.1 gives another example of Big O analysis and its definition.

FIG. 12.1 Therefore in terms of Big O analysis of algorithms, we assume that the execution time of an algorithm may be represented by a polynomial function such as: F(N) = a1*NP + a2*NP-1 + a3*NP-2 + ……………… + an ……. Expr 12.3 This function for large values of N will have most contribution from its first term, and algorithm whose execution time follows this function would be called an algorithm with Big O of NP or simply O ( NP ) algorithm. Comparison Of Algorithms: To select the best algorithm to be used in designing a software product, it is imperative that we are able to compare the algorithms in terms of their Big O values. In addition, if we can actually measure the time taken by one algorithm vs. another then we can contrast our experimental findings with the theoretical analysis we may have done. In writing the algorithm ALG1, we used the brute force method of linear search of an array, even though we were told that the array has been sorted numerically in the ascending order. Can we make use of the sorted state of the array to speed up our search algorithm and make it faster than O ( N )? Indeed, we can and such an algorithm is called the binary search. The idea of binary search is similar to the process of finding the meaning of a word in a thick dictionary. To look up the meaning of a word, we open the dictionary right in the middle. Either word is found on the middle pages or it will be in the first half or in the second half. If it is in the first half, then we break the first half into halves and then look again. We keep repeating this process, until we find the page where the word is located. In binary search of a sorted array, we look at the middle value for the key we are searching. If key matches the middle value, the search is over. Otherwise, it must be either in the first half or in the second half. Each time we can eliminate half of the left over array to be searched. The process is repeated until we have found the value

The term N4 will increase most rapidly compared to other terms.

Page 370: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 4 of 87

or confirmed that the value is not in the array. The algorithm for the binary search ALG2 is given below. The function that describes the execution time for ALG2, or binary search is approximately given as: F(N) = log2 (N) ………………………………… Expr 12.4 Therefore the binary search may be called an O ( log2N ) algorithm. Even a simple comparison may show that an O ( log2N ) algorithm would be much faster than an

ALG2 Set a Boolean variable found to false, Assume key to be found = Key Set lowest_index = 0, The lowest index in the array where key can be. Set highest_index = length –1, where length is the array length, and highest_index Be the largest array index where the key can be. Define middle_index to be the middle cell of the array portion where we are searching for the key to be present. middle_index for any array portion would be defined as: middle_index = (lowest_index +highest_index)/2 As we scan the array and we search a smaller and smaller portion of the array, the difference between the lowest_index and highest_index would decrease. But as we scan through a loop, we must maintain that highest_index is either higher than the lowest index or equal to the lowest_index. Therefore, While highest_index is greater than or equal to lowest_index middle_index = (lowest_index +highest_index)/2 If Array[middle_index] is equal to the key then Found = true Break out of loop Else if Array[middle_index] is larger than Key //key is in first half of array highest_index = middle_index -1

Else if Array[middle_index] is smaller than Key //Key is in second half of array lowest_index = middle_index +1

return found

Page 371: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 5 of 87

O ( N ) algorithm, as for the former, the execution time drops by several order of magnitude. We can demonstrate the comparison between the algorithms ALG1 (linear search), and ALG2 (binary search) theoretically first and then confirm our results experimentally. Assume that time to search an array of N sorted elements is tlinear for linear search and tbinary for binary search. Then the expressions for the two search times are given as follows: tlinear = C * N …………………………………………. Expr 12.5 tbinary = C * log2 N ……………………………. Expr 12.6 Constant C is the time it takes for the computer to carry out one iteration of the search loop for each case. This time may not be same for both algorithms, but for the sake of simplicity, we assume it the same. Assume that we search an array of 106 elements, and we provide a value to search, which is larger than the largest element in the array. In that case, the search algorithm will be forced to search the entire array for both algorithms. Then the values of tlinear and tbinary are given as follows: tlinear = C*106 ……………………. Expr 12.7 tbinary = C*log2 (106) = C * 19.93 ………………. Expr 12.82 Using expressions 12.7 and 12.8, one can see that the linear search time would be about 50175 larger than the binary search time for an array of million elements. Actual time measurements show that the binary search is even faster than the theoretically calculated numbers. Experimental Measurements We write a class called BinarySearch, which has two methods in it, one called binSearch ( ), and other linearSearch ( ). Both methods take an array and a key to be searched as arguments and return a boolean parameter indicating whether key was found or not. Notably, both algorithms ALG1 for linear search and ALG2 for binary search have a loop, which consumes most of the time in the search process. We use a java class System, along with its static method currentTimeMillis ( ) to get the system time before the loops are executed and then just after it. The difference between these times is the time spent in executing the loops. The program is set up

2 You can find the value of log2 (106) as follows: Assume X = log2 (106). Then it is also true that 106 = 2x . Taking the logarithm to the base 10 of both sides, we get 6 = X * log10 ( 2) = X*0.30103. This gives us: X = 6/0.30103 = 19.93

Page 372: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 6 of 87

in the way that user can specify the size of the array to be created and then the value to searched in it. The summary of class BinarySearch is shown in Table 12.3.

Constructor Summary

BinarySearch()

Method Summary

static boolean binSearch(int[] Array, int key) Takes the array and a key to be searched by binary search process.

static int getArrayLength()

Prompts the user to enter the array length.

static java.lang.String getInput(java.lang.String Str)

Displays a JOptionpane to get the user input and returns the user input String.

static int getkey()

Prompts the user to enter the integer key to be searched in the array.

static boolean linearSearch(int[] Array, int key) Takes the array and a key to be searched by linear search process.

static void main(java.lang.String[] args)

static void putToSleep()

The method can be used to put to sleep the execution of the program by 1 millisecond.

static void showOutput(java.lang.String Str) Displays a JOptionpane to display a String

Table 12.3 Listing 12.1 gives the code for the class BinarySearch.

Page 373: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 7 of 87

import javax.swing.*; import java.util.*; public class BinarySearch { /** * Takes the array and a key to be searched by linear search process. * @param Array is the int array to be searched linearly * @param key is the value to be searched in the array * @return returns true if key is found in the array or false if key is * not in the array. */ public static boolean linearSearch(int [ ] Array, int key ) { boolean found = false; long start_time = System.currentTimeMillis();; for(int index = 0; index<Array.length; index++) { if(Array[index] == key) { found = true; break; } } long finish_time = System.currentTimeMillis(); BinarySearch.showOutput("The linear search time = " + (finish_time - start_time)*1000 +" Microseconds"); return found; } /** * The method can be used to put to sleep the execution of the program * by 1 millisecond. */ public static void putToSleep() { try { Thread.currentThread().sleep(1); } catch(InterruptedException e) { //blank. No code needed. }//end } /** * Takes the array and a key to be searched by binary search process. * @param Array is the int array to be searched using binary search * @param key is the value to be searched in the array * @return returns true if key is found in the array or false if key is * not in the array. */

Page 374: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 8 of 87

public static boolean binSearch(int [ ] Array, int key ) { int lowest_index = 0; int highest_index = Array.length -1; boolean found = false; int counter = 0; long start_time = System.currentTimeMillis(); System.out.println(start_time*10000 + "Microsecond"); while(highest_index >= lowest_index) { int middle_index = (lowest_index + highest_index)/2; if(Array[middle_index] == key) { found = true; break; } else if(Array[middle_index]>key) highest_index = middle_index-1; else if(Array[middle_index]<key) lowest_index = middle_index + 1; counter++; } long finish_time = System.currentTimeMillis(); System.out.println("Number of loop iteration in binary search = " + counter); System.out.println(finish_time*10000 + "Microsecond"); BinarySearch.showOutput("The binary search time = " + (finish_time - start_time)*1000 +" Microseconds"); return found; } public static void main(String [ ] args) { boolean done = false; String Input = ""; while(!done) { int length = BinarySearch.getArrayLength(); int [ ] Array = new int [length]; for(int index = 0; index<Array.length; index++) Array[index] = index; int key = BinarySearch.getkey(); if(binSearch(Array, key) == true) BinarySearch.showOutput("The element " + key+" found in the array."); Else BinarySearch.showOutput("The element" + key+" not found in” +” the array."); BinarySearch.linearSearch(Array,key); boolean done_response = false; while(!done_response) { try { Input = BinarySearch.getInput ("More data? [Y]es or [N]o?"); }

Page 375: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 9 of 87

catch(NullPointerException ex) { BinarySearch.showOutput("No response given."); done_response = false; } if(Input.equals("Y") || Input.equals("y")) { done_response = true; done = false; } else if(Input.equals("N") || Input.equals("n")) { done_response = true; done = true; } else { BinarySearch.showOutput("Invalid response."); done_response = false; continue; } } } System.exit(0); } /** * Prompts the user to enter the integer key to be searched in the array. * @return the integer to be searched in the array. */ public static int getkey() { String Input = ""; int key = 0; boolean done =false; while(!done) { try { Input = BinarySearch.getInput("Please enter the int to be " +" searched."); } catch(NullPointerException ex) { BinarySearch.showOutput("No value entered."); done = false; continue; } try { key = Integer.parseInt(Input); }

Page 376: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 10 of 87

catch(NumberFormatException ex) { BinarySearch.showOutput("Illegal value entered."); done = false; continue; } done = true; } return key; } /** * Displays a <code>JOptionpane</code> to display a <code>String</code> * @param Str is the <code>String</code> displayed to the user. */ public static void showOutput(String Str) { JOptionPane.showMessageDialog(null, Str); } /** * Displays a <code>JOptionpane</code> to get the user input and returns the * user input <code>String</code>. * @param Str is the prompt string for the user. * @return the user input string. */ public static String getInput(String Str) { return (JOptionPane.showInputDialog(Str)).trim(); } /** * Prompts the user to enter the array length. * @return the length of the array as an integer. */ public static int getArrayLength() { boolean done = false; int length = 0; while(!done) { String Input = ""; try { Input = BinarySearch.getInput("Please enter the length of the" +" array to be created."); } catch(NullPointerException ex) { BinarySearch.showOutput("No value entered."); done = false; continue; } length = 0; try {

Page 377: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 11 of 87

length = Integer.parseInt(Input); } catch(NumberFormatException ex) { BinarySearch.showOutput("Illegal value entered."); done = false; continue; } if(length <= 0 ) { BinarySearch.showOutput("Illegal value entered."); done = false; } else done = true; } return length; } }

//Listing 12.1

When the program in Listing 12.1 is run it asks for the length of the array to be created (Figure 12.2).

FIG. 12.2 On our machine3, where experiment was done one could enter the array size up to 15 Million. For array size larger than that, the java virtual machine gave an out of memory error as java.lang.OutOfMemoryError. The original array contains values from zero to N-1, where N is the length of the array entered in the dialog box in Figure 12.2. The system then prompts the user to enter an integer to be searched. In all cases, we gave a value of –1, so that the program is forced to search the entire array, as the value is not in the array (Figure 12.3).

3 A Windows 98SE machine with 500 MHZ Pentium III processor, with 440MB RAM.

Page 378: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 12 of 87

FIG. 12.3 We found that even for the maximum array length experiment the binary search time is nearly zero. A dialogue box similar to the Figure 12.4 gives the time taken to perform linear search.

FIG. 12.4 In order to get a reasonable value of the search time for each array size, four readings were taken for each array size. The data are shown in the Table 12.4. Array Size Measurement1 Measurement2 Measurement3 Measurement4 Average

1000000 50000 50000 50000 60000 525002000000 60000 60000 50000 50000 550003000000 110000 50000 110000 110000 950004000000 110000 110000 110000 170000 1250005000000 170000 160000 160000 160000 1625006000000 220000 220000 220000 160000 2050007000000 220000 220000 220000 220000 2200008000000 220000 220000 220000 220000 2200009000000 280000 280000 320000 270000 287500

10000000 330000 330000 330000 280000 31750011000000 320000 390000 330000 330000 342500

Table 12.44

4 All measurements are not shown in the table 12.4.

Page 379: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 13 of 87

The plot of average search time in microseconds as a function of array size is shown in a plot in Figure 12.5.

Linear Search Time = 0.0302* Array Size + 7954.5

0.00E+00

5.00E+04

1.00E+05

1.50E+05

2.00E+05

2.50E+05

3.00E+05

3.50E+05

4.00E+05

0.00E+00 2.00E+06 4.00E+06 6.00E+06 8.00E+06 1.00E+07 1.20E+07

Search Time in microseconds

Array Size

Average Search Time in Microseconds For Linear Seraching

AverageLinear (Average)

FIG. 12.5 On an average, a good linear search behavior is indicated by the graph in Figure 12.5. It may be instructive to note as to why the binary search time was close to zero even for the array size of 11 million. The number of iteration performed by the loop in method binSearch, even for the array size of 11 million were just 23, as compared to the 11million iterations for the loop in linear search! If we use the linear regression equation in Figure 12.5, then 23 loop iterations amounts to barely 8000 micro-seconds of computer time – which is too small to be measured by the technique we used in this experiment.

Page 380: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 14 of 87

Comparisons of Rates of growth: Other Big O algorithms In discussion of linear and binary search, we realized that different algorithms might process data at substantially different rates. When the amounts of data to be processed are small, then differences in the rates do not affect the processing time very much. For example for an array size less than 50,000, one may not even be able to detect a difference between the times taken by binary search and linear search. The total processing time, however, grows significantly, as the number of data to be processed increase. We would like to compare these rates of growth in processing time for algorithms, which may follow other Big O relationships. Table 12.6 provides these data for us.

Table 12.6 According to Table 12.6, the slowest growing algorithm would be the one, which flows the Big O of log2N, and the fastest growing one would be the 2N. Others, such as O ( N ), O ( N* log2N ), O ( N2 ), etc. would have growth rates between the log2N and 2N. One may not be able to appreciate these data very much unless we show the impact they may have on actual data processing times. Assume that a computer is capable of executing 1 billion (109) instructions per second. Then the Table 12.7 shows us as to how long it would take to complete the processing of data of size N, for algorithms following different Big O functions.

Page 381: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 15 of 87

Table 12.7

16.7 m 19.92 ms 0.020 µs 0.01s 1,000,000

1.16 days

0.23 s 0.023 µs 0.10s 10 Million

1 ms 9.96 µs 0.010 µs 1.00 µs

1000

100 ms 130 µs 0.013 µs 10 µs 10,000

10 s 1.67 ms 0.017 µs 0.10 µs

100,000

4 x 1013 Years

10 µs 0.66 µs 0.007 µs 0.10 µs

100

1.00s

0.05 µs

0.04 µs

0.03 µs

0.02 µs

0.01 µs

n

115.7 days

2.66 s 0.027 µs 100 Million

13 days 2.5 µs 0.28 µs 0.006 µs 50

18.3 min 1.6 µs 0.21 µs 0.005 µs 40

1 s 0.9 µs 0.15 µs 0.005 µs 30

1 ms 0.4 µs 0.09 µs 0.004 µs 20

1micro-second

0.1µs 0.03 µs 0.003 µs 10

2n n2 n log2 n log2 n n

Page 382: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 16 of 87

Note the value of time spent in processing data of size 100. The fastest algorithm (log2N) can process data of size N = 100 only in 0.07 microseconds. On the other hand, a 2N algorithm would process the same size data in 40,000 billion years or in 2000 times the age of the universe5. The data for some of the functions of Table 12.6 are shown in Figure 12.6 below.

Theoretical Big O Plots

0

2

4

6

8

10

12

14

16

0 2000 4000 6000 8000 10000

Input Size (N)

Loga

rithm

of R

unni

ng T

ime

Log[O(N)]Log[O(2N)]Log[O(N*N)]Log[O(N*N*N)]Log[O(N*N*N*N)]

FIG. 12.6 Analysis of Sorting Algorithms People from other fields always say that Computer Scientists are always talking about sorting. It just so happens that computers spend tremendous amount of time sorting data. One study done on mainframe computers showed that those computers spent 26% of their time in just sorting data. Certainly, it would appear that sorting is a very important activity in the field of computer data processing. The sorting algorithms may be divided into two categories:

1. Comparison based sorting 2. Radix Sorting

The steps included in comparison based sorting are: • Compare two members of the list to see whether they are equal or not,

5 The age of the universe is calculated to be about 20 billion years.

Page 383: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 17 of 87

• If they are not equal then move the lesser member towards the lower index of the list – if list is to be sorted in ascending order. Do the opposite if the list is to be sorted in descending order.

The radix sorting does not compare the values in the list with each other. Rather it takes the list member and designs keys for them. For example, keys for integers may be 0 to 9, as each digit in an integer would have value from zero to nine. Similarly, the key for a string where case of the alphabet is not important could have alphabets ‘a’ to ‘z’ as keys. These possible keys are called radix. The sorting depends upon creating buckets equal in number to the radix, and placing the members of list in these buckets with a pre-defined scheme repeatedly, until the list is sorted. We discuss each type of sorting separately. Comparison based sorting If we ignore the memory used in a sorting algorithm, then the efficiency of a sorting algorithm is based on the number of comparisons and number of data movements it would need to do to accomplish the sorting. Depending upon the original state of the list, higher are the numbers of comparisons, and list member movements, less efficient is the algorithm. Table 12.5 below gives a list of some of the comparison based sorting algorithms being used. Number Name 1. Selection Sort 2. Bubble Sort 3. Short Bubble Sort 4. Insertion Sort 5. Merge Sort 6. Quick Sort 7. Heap Sort Table 12.5 We show the logic, process and pseudo-code for each type of sorting algorithm first. Then in the end, we design a grand program, where all the sorting algorithms are coded and compared with each other for their efficiency. Selection Sort A java applet at the link below shows the animation of the selection sort and other sorting algorithms. Sorting Algorithm Applet In selection sort, we start scanning an array from the element at index = 0, and we look for the largest element in the array. You would recall from previous programming courses that the largest element in an array might be easily found by a method such as given in Listing 12.2, for finding the largest value in an integer array.

Page 384: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 18 of 87

public static int largest(int [ ] Array) { int largest= Array[0]; for(int index = 0; index<Array.length; index++) { if(largest < Array[index]) largest = Array[index]; } return largest; } //Listing 12.2 Once we find the largest element, we place it at the location N-1 in the array. In first such an attempt, the largest element in the array has been placed at the location, where it would be in the sorted array. This reduces the size of the array to be sorted and scanned by one. Next time we repeat the same process for the remaining array from index zero to N-2. When we find the largest element this time, we place it in the location with index N-2. We carry on this process, until the array is fully sorted. The above analysis shows that in selection sort the number of times an array to be sorted would be scanned is N-1. However, the size of the array to be scanned, decreases by one each time. If we sum up the sizes of the array to be scanned each time, the total number of array elements to be scanned in all scans will be given by the arithmetical series, N + (N-1) + (N-2) + (N-3)+ .................................................................... + 1. The basic algebra tells us that the sum of the above series would be N (N + 1) /2. The asymptotic form of the function, F(N) = N(N+1)/2 Would be F(N) = N2

Therefore one would conclude that selection sort a is an O ( N2 ) algorithm or it has a Big O of N2. The Figures 12.7A to 12.7I show the mechanism of selection sort for an array of integers.

Page 385: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 19 of 87

FIG. 12.7A Figure 12.7A shows an array of five elements with the integers 36, 24, 10, 6, and 12 in that order. In first pass we look for the largest element and place it in the array location (5-1 = 4). The largest element is 36 and it is swapped with the 12 which is in location with index = 4 (Figure 12.7B and 12.7C).

FIG. 12.7B

Page 386: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 20 of 87

FIG. 12.7C At the end of pass one the highest value bubbles to the last index of the array, therefore the last element is now sorted. In the pass two (Figure 12.7D) we scan the array from index zero to index three because the index four is already sorted. In this part of the array we find that 24 is the largest value, which must be swapped with the value of 6 in the array cell with index 3 (Figure 12.7D).

FIG. 12.7D

Page 387: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 21 of 87

Upon swapping the values 24 and 6, the last two element of the array are sorted (Figure 12.7E).

FIG. 12.7E In third pass we scan the array for the largest value from index 0 to index 2. The largest value in this part is 12, which is to be swapped with the value 10 – the last cell in the unsorted part of the array (Figure 12.7F).

FIG. 12.7F

Page 388: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 22 of 87

Once the 12 and 10 are swapped, the sorted part increases by one more cell (Figure 12.7G).

FIG. 12.7G Thus at the end of third pass the last three elements of the array are sorted. Now in the fourth and last pass we scan the array from index 0 to index (5-4 = 1). This time the largest value found is 10 (Figure 12.7H).

FIG. 12.7H

Page 389: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 23 of 87

When 10 and 6 are swapped, then the array is fully sorted (Figure 12.7I).

FIG. 12.7I From the above discussion of the selection sort, the following facts are clear:

• An array of size N will require N-1 passes through it. • The portion of the unsorted array decreases by one with each

pass. • Each pass scans the array from index 0 to index

(N – pass Number). • With each pass, the largest value is placed in the array cell with

index = (N- pass number). This is done by swapping the value in location (N- pass number) with the location, where the largest value is found.

Based on these facts the algorithm for selection sort may be written as follows:

Page 390: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 24 of 87

We code the selection sort algorithm as follows using the class SelectionSort (Listing 12.3). We however add a method swap, which would pretty much do the last part of the ALG4 algorithm.

ALG3 For pass_number zero to pass_number < size of array, pass_number++ Find the largest value in the array and the index where the Largest value is found.

Swap the values between the array cells where largest value was found and with the array (cell size –pass number).

The ALG3 would expand to ALG4 as follows: ALG4 Define Object Largest Define integer pass_num as pass number and set it to zero. Define integer index_largest and set it to zero. For pass_number zero to pass_number < size of array, pass_number++ index_largest = 0; Largest = Array[0]; For index2 = 0; index2<(size-pass_number); index2++ If Largest compareTo Array[index] < 0 then Largest = Array[index2] index_largest = index2 Array[index_largest] = Array[size - pass_number –1]; Array[size – pass_number-1] = Largest;

Page 391: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 25 of 87

The array of Figure 12.7A is passed to the method selectionSort in Listing 12.3. The

results are shown in the Figure 12.8. The array, which was passed as 36, 24, 10, 6, and 12, is now sorted. The recursive version of selection sort is shown in appendix A12 with source code for class SelectionSortRecursive shown in Listing A12.1.

FIG. 12.8

public class SelectionSort { public static void selectionSort(Object [ ] Array, int size) { Object Largest = new Object(); int index_largest = 0; for(int pass_number = 0; pass_number<size;pass_number++) { Largest = Array[0]; index_largest = 0; for(int index = 0; index<size-pass_number; index++) if((((Comparable)(Largest)).compareTo(Array[index]))<0) { Largest = Array[index]; index_largest = index; } swap(index_largest, size-pass_number-1, Array); } } public static void swap(int index_largest, int swap_from, Object [ ] Array) { Object Temp = Array[index_largest]; Array[index_largest]= Array[swap_from]; Array[swap_from]= Temp; } public static void main(String [] args) { Object [ ] Array = new Integer[]{ new Integer(36),new Integer(24), new Integer(10), new Integer(6),new Integer(12)}; selectionSort(Array,Array.length); for(int index = 0; index<Array.length;index++) System.out.println(Array[index]); } }

Page 392: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 26 of 87

Bubble Sort We use the sorting algorithm applet once more to visualize the mechanism of bubble sort. Sorting Algorithm Applet In bubble sort is similar to selection sort in the sense that with each pass through the array, the largest value would be bubbled to the end of the array. However, the mechanism of bubbling is a bit different. Here, starting with the index zero, we compare the two adjacent values to see which one is larger. If the value at the lower index is higher, then we swap the lower and higher index values. If the two adjacent values being compared are same or the one at lower index is lower, then no swap is done. The net result is that after first pass, the largest value will bubble to the last index, and after second pass, the second largest value will bubble to the index one before last and so on. The bubble sort algorithm is also an O ( N2 ) algorithm, but it performs slower than selection sort ( which is also an O ( N2 ) algorithm), because bubble sort has more swapping than selection sort, which has only one swap per scan of the array. The algorithm ALG5 for bubble sort is given below. In ALG5, the outer loop simply controls the pass number for the inner loop. Inner loop simply must scan the array from index zero to one less than the size minus pass number. If an element at location index is found larger than the one at the location index+1, then the method swap is called to exchange them. Otherwise, inner loop does not do anything. We show the code of methods bubbleSort, swap and main to test them in the class BubbleSort in the Listing 12.4 below. public class BubbleSort { public static void bubbleSort(Object [ ] Array, int size) { for(int pass_number=0; pass_number<size; pass_number++) { for(int index =0; index<size -pass_number-1; index++) { if(((Comparable)(Array[index])).compareTo(Array[index+1])>0) swap(index, index+1, Array); } } }

ALG5 //set the first loop to control the pass number through the unsorted array for pass_number zero to pass_number < array_size, pass_number++ for index = 0; index< array_size – pass_number –1, index++ if element at array location index is larger than the one at index+1 then call function swap as swap (index, index+1, array) to swap the elements at index and index+1.

Page 393: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 27 of 87

public static void swap(int index_largest, int swap_from, Object [ ] Array) { Object Temp = Array[index_largest]; Array[index_largest]= Array[swap_from]; Array[swap_from]= Temp; } public static void main(String [] args) { Object [ ] Array = new Integer[]{ new Integer(36),new Integer(24), new Integer(10), new Integer(6),new Integer(12)}; bubbleSort(Array,Array.length); for(int index = 0; index<Array.length;index++) System.out.println(Array[index]); Object [ ] Array2 = new String[]{"Zack", Mary","Zelda","Aron","Satish", "Ramon", "Bush","Carolyne","Heather"}; bubbleSort(Array2,Array2.length); for(int index = 0; index<Array2.length;index++) System.out.println(Array2[index]); } }

//Listing 12.4

The results of Listing 12.4 for sorting an Integer and a String array are given in Figure 12.9.

FIG. 12.9 Although the mechanism of bubble sort becomes amply clear by the java applet we saw early on, we also present the following PowerPoint presentation to emphasize it.

BubbleSort.ppt

A typical array, similar to the one we presented for selection sort is sorted by the bubble sort (Figure 12.10A).

Page 394: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 28 of 87

FIG. 12.10A As discussed before, the mechanism of bubble sort is that we start at the beginning of the array, and swap values in the index zero and one if the value in the index zero is larger (Figure 12.10B). This advances the larger value towards the end of the array.

Page 395: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 29 of 87

FIG. 12.10B In three, more swaps the value 36 bubbles to its “correct” location – the end of the array (Figure 12.10C). A portion of the array is now sorted.

FIG. 12.10C

Page 396: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 30 of 87

One starts scanning again from the array index zero, but this time one excludes the sorted portion from the scan. This would bubble the next largest value to the index one before last (Figure 12.10D).

FIG. 12.10D Notices, that though, the value 12 is now sorted, but the algorithm cannot claim it part of the sorted array yet, since in the third scan, the array from index zero to index two would be used. Incidentally, this array is in sorted state after three passes, but the actual process would take four scans, though last scan would not make any changes. The sorted array after four scans is shown in Figure 12.10E.

Page 397: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 31 of 87

FIG. 12.10E Insertion Sort Once again, we revert to sorting algorithm java applet to understand the mechanism of insertion sort. Sorting Algorithm Applet In insertion sort, starting from the beginning of the array, we look at the value at the index zero, and if this value is smaller than the value in index one, then we consider the first two elements already sorted. However, if the value in index zero is larger than the value in the index one, then we swap them. Then we look at the value in the index two and find its best place in the “sorted” sub-array, and insert it there. We keep taking the first value from the unsorted sub-array, find its best location in the sorted sub-array, and insert it there. All the values after the best location would be moved up by one array index, thereby increasing the size of sorted array by one. The PowerPoint presentation below shows the process in more detail further.

InsertionSort.ppt

We start out with the array shown in Figure 12.11A.

Page 398: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 32 of 87

FIG. 12.11A We treat the array like a hand of cards in the card game and assume that first element forms the sorted sub-array (Figure 12.11B and 12.11C).

FIG. 12.11B

Page 399: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 33 of 87

FIG. 12.11C Now we look at the sorted sub-array and take out its first element 24 (Figure 12.11D).

FIG. 12.11D

Page 400: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 34 of 87

We find the best location for 24 in the sorted sub-array and insert it there. In order to do that first we move 36 to the place vacated by 24 (Figure 12.11E).

FIG. 12.11E Then we move 24 to the location vacated by 36 and that results in growth of sorted sub-array by one more element (Figure 12.11F).

FIG. 12.11F

Page 401: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 35 of 87

Using the mechanism shown in Figures 12.11C to 12.11F, the element 10 will next be sorted and be added to the sorted sub-array (Figure 12.11G).

FIG. 12.11G Using the same mechanism, entire array is sorted as the elements 6 and 12 are also inserted in their proper location in the sorted sub-array (Figure 12.11H).

Page 402: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 36 of 87

FIG. 12.11H Analysis of Insertion Sort Algorithm It is clear from the description of insertion sort, that algorithm iterates twice: once over the overall array to pick the first item from the unsorted sub-array and then to find the location to insert the picked item. The first iteration is done over the overall array, which will be done N times, where N is the array size. The second iteration, which is done inside the first done is done as follows: First iteration is done one time Second iteration is done two times ………………………………….. Nth iteration is done N times. Therefore the function describing the time consumed in insertion sort may be given as: F(N) = 1 + 2 + 3 + 4 + ………………………………+(N-1) + N Once again, that sums up to: F(N) = N(N-1)/2 OR; The asymptotic form of above is simply F(N) = N2 – indicating that insertion sort is also an O ( N2 ) algorithm. Algorithm ALG6 gives the algorithm for the insertion sort and the Listing 12.5 gives the source code for it.

Page 403: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 37 of 87

public class InsertionSort{ public static void insertionSort(Object [ ] Array, int size) { int location = 0; for(int pass_number = 0; pass_number<size; pass_number++) { location = pass_number; Object Temp = Array[pass_number]; for(int index = pass_number; index>=0; index--) { if(((Comparable)(Temp)).compareTo(Array[index])<0) { Array[index+1] = Array[index]; location = index; } } Array[location] = Temp; } } public static void main(String [] args) { Object [ ] Array = new Integer[]{ new Integer(36),new Integer(24), new Integer(10), new Integer(6),new Integer(12)}; insertionSort(Array,Array.length); for(int index = 0; index<Array.length;index++) System.out.println(Array[index]);Object [ ] Array2 = new String[]{"Zack", "Mary","Zelda","Aron","Satish", "Ramon", "Bush","Carolyne","Heather"}; insertionSort(Array2,Array2.length); for(int index = 0; index<Array2.length;index++) System.out.println(Array2[index]); } }

//Listing 12.5 The results of Listing 12.5 are identical to Figure 12.9.

ALG6 For pass_numer zero to pass_number <array_size, pass_number++ Set Temp = array [pass_number] Set location = pass_number For index = pass_number, index>=0, index++ If Temp is smaller than array [index] then Move the item at location index to one index higher OR Array [index + 1] = Array [index] Save the current index OR location = index location is the place to insert the item Temp , thus Array [location] = Temp

Page 404: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 38 of 87

Recursive Sorting Algorithms: In recursive sorting algorithms, the list is not sorted at once; rather it is broken down in two almost equal parts. Then two portions are sorted and merged together. The process of breaking down a larger problem into smaller parts (in this case, the problem of sorting) is called “divide and conquer” approach. The rationale of divide and conquer sorting approach is shown by the Figure 12.12 below.

FIG. 12.12 If we have an array of size 100, then the using an O (N2) algorithm to sort entire array at once will take time proportional to 1002 = 10,000. However, if we divide the array into two parts, each being 50 elements, then the time to sort them reduces to a number proportional to, 502 + 502 = 2500 + 2500 = 5000. Of course, the sorted halves have to be merged. Merging two arrays is an algorithm of O (N). Therefore, overall time will be proportional to 5100. The time differential between divide and conquer sorts and straight O (N2) sorts become substantial as the array size increases, and if arrays to be sorted is divided further in smaller portions. Merge Sort If array is divided into portions as small as two elements each, and then merged to form the sorted array, then instead of O (N2), it becomes an O (Nlog2N) algorithm. We must be clear about the strategy for merging, as parts that are sorted separately

Page 405: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 39 of 87

will have to be merged with natural order preserved. Figure 12.13 shows an example of merging strategy.

FIG. 12.13 Imagine that we have two sorted sub arrays, one containing 1, 3,7,11,12, and other 2,4,6. Now to merge them together and still get a final sorted list, we use the following strategy.

• The smallest element would be either in the first cell of first array or in the second array. In Figure 12.2, it is in the first cell of first array, so we move it into the first cell of the final array.

• The algorithm marks the copied element as copied, so it is not copied again. • Then we compare the elements 2 and 3, since 2 is smaller than 3, we move

that to the next slot in the final array, and element 2 is marked copied. • The process goes on and finally when one array is finished, the remaining

elements from the unfinished array are copied to the last slots in the final array.

The following PowerPoint presentation shows the merge process clearly. Error! Not a valid link.

Page 406: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 40 of 87

The Figure 12.14 shows that how the process of comparing the two sub arrays and copying them to the final array progresses.

FIG. 12.14A Comparing the first cells of two sub arrays, the smallest value is found in the first cell of the first sub array (Figure 12.14A). This value is copied to the first cell of final array, and the copied cell is marked as read (Figure 12.14B).

FIG. 12.14B

Page 407: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 41 of 87

Then the next cell from the left sub array is compared with the first cell from the right sub array. Since 2 is smaller than 3, it is copied to the next unfilled cell of the final array and copied element is marked as copied (Figure 12.14C and Figure 12.14D).

FIG. 12.14C

FIG. 12.14D

Page 408: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 42 of 87

This process of comparing values and copying them to final array continues until all elements from one of the sub array are exhausted (marked copied) (Figure 12.14E).

FIG. 12.14E The remaining elements from the array with un-copied elements are now copied to the final array (Figure 12.14F).

FIG. 12.14F The recursive algorithm for merge sort is deceptively simple (ALG7).

Page 409: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 43 of 87

One would note that method mergeSort above would be another example of double recursion. The prototype of method mergeSort will need the array to be sorted as well as the indices of two ends of the array, which will form the sorted portion. The prototype is given below. public static void mergeSort (Comparable [ ] values, int first, int last); values is the array of objects, which implement Comparable interface. First is the lower index of the array, where sorting must begin, and last is the index, higher than first, where sorted array ends. This gives the size of the array to be sorted as (last – first +1). Since this a recursive algorithm (ALG7), we must know what the sentinel or base case is. Obviously, when array is split so that a size of one element is reached, then there is nothing to sort. Therefore the base case is: If size of array is less than two, then do nothing And general case is: Execute the ALG7 Size of the array will be equal to two or greater than two as long as index first is lower than the index last. Therefore, in each recursive call to the method mergeSort, subject to the condition, that first is lower than last; we need to do the followings:

1. Define a middle index where array is to be cut in half. This index is simply (first + last)/2.

2. mergeSort from index first to index middle found in step 1. 3. mergeSort from index middle + 1, to index last. 4. merge two arrays whose bounds are first to middle, and middle+1 to last.

This pseudo-code is coded as follows; (Listing 12.6). public static void mergeSort( Comparable [ ] values, int first, int last) { if(first < last) { int middle = ( first + last )/2;

ALG7 : Merge sort (recursive) mergeSort ( ) {

Cut array is half mergeSort the left half mergeSort the right half Merge the two-sorted halves into one sorted array

}

Page 410: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 44 of 87

mergeSort ( values, first, middle); mergeSort (values, middle+1, last) merge (values,first, middle, middle+1, last); } } //Listing 12.6 Based on the technology described in Figure 12.14, we still need to write the method merge, which actually does most of the work. Method merge uses a temporary array where merging is done. In each recursive-call the temporary array gets bigger as the merging of sorted halves continues. As evidenced from Figure 12.14, the method merge has to copy elements progressively from left sub array or right sub array depending upon how the values in the left sub-array compare with the right sub-array. In Figure 12.14, we may have given the impression that it maintains two separate arrays, but that is actually not the case. It just uses the same array with partitions defined by the four indices shown by Figure 12.15.

FIG. 12.15 Based on Figure 12.15, we write the prototype of method merge as follows: public static void merge (Comparable [ ] values, int leftFirst, int leftlast, int rightFirst, int rightLast); Figure 12.16 repeats the process shown in Figure 12.14, as to how the sorted sub arrays are first copied to a temporary array, and then to the final array called values.

Page 411: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 45 of 87

FIG. 12.16 The algorithm for method merge is given as follows:(ALG8).

Page 412: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 46 of 87

The termination condition of the first while loop is that loop must exit as soon as elements are finished copying from left sub-array or from right sub-array. That will happen as soon as the leftFirst becomes larger than the leftLast or rightFirst becomes larger than rightLast. This termination condition may be shown as follows: Termination Condition (leftFirst > leftLast) || (rightFirst > rightLast) The loop pretest condition is simply negation of loop termination condition. Therefore; Pretest Condition ! (leftFirst > leftLast) || (rightFirst > rightLast) Using DeMorgan’s rules; Pretest Condition (leftFirst <= leftLast) && (rightFirst <= rightLast) Therefore the first while loop will be written as follows: while(leftFirst <= leftLast) && (rightFirst <= rightLast) { //code } Last three copy loops are simple. The first loop copying remaining elements from left sub-array must terminate as soon as leftFirst becomes larger than leftLast, therefore the loop pretest condition is simply leftFirst<= leftLast. The second loop

ALG8; merge( Comparable [ ] values, int leftFirst, int leftLast, int rightFirst, int rightLast) use a local array tempArray of same size and type as the values Set the index to leftFirst Set saveFirst equal to the leftFirst //this tells, where to begin copy back in values array While more items in left half and more items in right half If values [leftFirst] <values [rightFirst] Set tempArray [index] = values [leftFirst] Increment leftFirst Else Set tempArray [index] = values [rightFirst] Increment rightFirst Increment index (only one of the following two will execute in one call cycle) Copy remaining elements from left sub-array to the tempArray Copy remaining elements from right sub-array to the tempArray Copy the sorted elements from tempArray back to the values array.

Page 413: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 47 of 87

to copy remaining elements from right sub-array must terminate as soon as the rightFirst becomes larger than rightLast. Thus pretest condition is rightFirst<=rightLast. Last loop copies the values from tempArray to values array from the saved index saveFirst to rightLast. Taking all these details in consideration, we write the code for method merge and mergeSort as follows: (Listing 12.7), along with the main method to test it. //********************************************************************** public class MergeSort { public static void mergeSort( Comparable [ ] values, int first, int last) { if(first < last) { int middle = ( first + last )/2; mergeSort ( values, first, middle); mergeSort (values, middle+1, last); merge (values,first, middle, middle+1, last); } }

public static void merge(Comparable [ ] values, int leftFirst, int leftLast,int rightFirst, int rightLast)

{ Object [ ] tempArray = new Object [values.length]; int saveFirst = leftFirst; int index = leftFirst; while(leftFirst<=leftLast && rightFirst<=rightLast) { if(values [leftFirst].compareTo(values [rightFirst])<0) { tempArray [index] = values [leftFirst]; leftFirst++; } else { tempArray [index] = values [rightFirst]; rightFirst++; } index++; } while(leftFirst<=leftLast) { tempArray [index] = values [leftFirst]; leftFirst++; index++; } while(rightFirst<=rightLast) { tempArray [index] = values [rightFirst]; rightFirst++;

Page 414: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 48 of 87

index++; } for(index = saveFirst; index<=rightLast; index++) values[index] = (Comparable)tempArray[index]; } public static void main(String [] args) { Comparable [ ] Array = new Integer[]{ new Integer(36),new Integer(24), new Integer(10), new Integer(6),new Integer(12)}; mergeSort (Array,0, Array.length-1); for (int index = 0; index<Array.length;index++) System.out.println (Array [index]); Comparable [ ] Array2 = new String[]{"Zack", "Mary", "Zelda" ,"Aron" ,"Satish", "Ramon", "Bush", "Carolyne" ,"Heather"}; MergeSort (Array2,0, Array2.length-1); for (int index = 0; index<Array2.length;index++) System.out.println (Array2 [index]); } }//end of class MergeSort

//Listing 12.7 Output of Listing 12.7 is identical to Figure 12.9. Further Analysis of mergeSort It is important to understand that recursion in method mergeSort (Listing 12.7 or Listing 12.6) will stop, when the array size reaches one. This because at that time assertion condition first<last will be false, and recursive calls stop. The recursive division of an array of 16 elements will undergo four levels of recursion (Figure 12.17).

Page 415: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 49 of 87

FIG. 12.17 Also one notices that mergeSort is a post-order type algorithm. Therefore, at each level the recursive, calls stack up and merging at each level is done as one backs out of each level. Merging will recreate the sub-arrays back as sorted entities at each level during backing out from recursion (Figure 12.18).

Page 416: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 50 of 87

FIG. 12.18 Backing out from fourth level, from left to right, 30 and 16 are rewritten in order 16 and 30, 2, and 11 are written in same order, 9 and 15 in the same order and so on. Figure 12.18 applies the principles of merging illustrated in Figures 12.14A to 12.14E to merge into a sorted sub array in the process of backing out from each level. Finally, in backing out from first level of recursion, the sorted array is produced. For the sake of visualization, we have shown the arrays at each level of recursion and backing out as if there are separate arrays operating at each level. The truth is that array always remains the same, but the algorithm manipulates its portions as if they are individual arrays. Though merge sort is an O (Nlog2N) algorithm, its efficiency is reduced because it uses a temporary array, thus doubling the use of program memory for the array. Next we discuss quick sort algorithm, which is also an O (Nlog2N) algorithm, but it only uses the single array.

Quick Sort Algorithm The quick sort algorithm was developed by Tony Hoare and is considered one of the fastest sorting algorithms today. Similar to merge sort quick sort also uses a “divide and conquer” technique. The logic of quick sort may be understood as follows: Let us assume that a professor has a pile of final examination papers to grade, with each paper having student’s last name on the top. The fastest way to sort them

Page 417: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 51 of 87

alphabetically would be to make two piles – one having last names starting from A to L and other with last names starting with M to Z. Then the middle alphabet between A and L is F, so break up the first pile in two piles again – one with names from A to F and other from G to L. When this process is continued, then finally, there will be 26 or lower number of piles, each one sorted at least with respect to the first letter of the last name. The process is schematically shown in Figure 12. 19.

FIG. 12.19 Using quick sort an array may be sorted in its entirety, but to make the method more versatile, it is coded so that the method will accept a lower index and a higher index and sort the array bounded by two indices. Therefore, the signature of method quickSort will be as follows: public static void quickSort (Comparable [ ] Data, int low_index, int high_index); The algorithm indicates that the process of splitting arrays must go on until there are only two elements left in the array. This is simply so, because in an array of size one, there is nothing to sort. Therefore the base case in the recursive call to the method quickSort is enforced by making the recursive calls only if array size is two elements or larger. That would require that the method executes only when6 (high_index – low_index) > 0. Since the array also needs to be partitioned each time, before quickSort can be called recursively on two partitions, a method returning the index, where array is partitioned is needed as well. Following these guidelines, the structure of the method quickSort will look like as follows (Figure 12.20):

6 For an array of size two, the high_index would be one and low_index would be zero.

Page 418: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 52 of 87

public static void quickSort (Comparable [ ] Data, int low_index, int high_index) { if((high_index - low_index) > 0) { //Call the method to return the index where the array would be // partitioned. //Recursive call to quickSort to sort first part //Recursive call to quickSort to sort the second part } } FIG. 12.20 Arguments to the method that does partition would be same as that of quickSort. The actual code for quickSort is deceptively simple and is given in the Listing 12.8 below. public static void quickSort (Comparable [ ] Data, int low_index, int high_index) { if((high_index - low_index) > 0) { int partition_index = partition (Data, low_index, high_index); quickSort (Data, low_index, partition_index-1); quickSort (Data, partition_index+1, high_index); } } //Listing 12.8 Tony Hoare’s genius in implementing quickSort lied in concept of the method partition and its implementation, which does bulk of the work in quick sort. Algorithm For Method partition: Most important method in quick sort is the method partition. It returns a value partition_index such that all the elements to the left of partition_index are lower than the element at partition_index, and all the elements at right are higher or equal to it. This concept is called having an invariant. Quick Sort Invariant Complex algorithms can be coded easier if one can design them around invariants, which are facts we know to be true at some point in execution cycle. The partition method depends upon choosing a value from the array, which is called a “pivot”. If partition method gets the array, low_index, and high_index as arguments (Listing 12.8), the method must:

• Choose a pivot value from the array elements.

Page 419: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 53 of 87

• Arrange array into two sub-arrays, such that left sub-array S1 has all the items less than the pivot value, and right sub-array S2 has all the items more than or equal in value to the pivot.

Thus throughout the partitioning process the following invariant holds true: The items in the region S1 are all less than the pivot, and items in region s2 are all greater than or equal to the pivot. There is considerable debate upon the best choice of a pivot. Here we will use a location or value for the pivot, which will make our task for explaining algorithm for the method partition easier. Actual coding of method partition will be done differently than the algorithm used for explaining it’s functioning. This is done to avoid all the pit falls a wrong choice of pivot may cause in certain conditions. Assume that we choose the first element in the array as a pivot. Then array will can be seen as having three natural divisions: S1 (where all elements are lower than pivot), S2 (where all elements are higher than or equal to the pivot), and unknown, where no such certainty exists (Figure 12.21).

FIG. 12.21 Take the concrete example of the array shown in Figure 12.17 (reproduced below Figure 12.22).

Page 420: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 54 of 87

FIG. 12.22 The algorithm, in one cycle of execution must place the elements from unknown into the regions S1 and S2, and pivot in the middle of S1 and S2, so that finally the situation, similar to the Figure 12.23 results.

FIG. 12.23 When the partitions are as small as two array elements, then each such segment will be in the sorted state. In quick sort, no explicit merging is needed, because swapping array elements with each other by the method partition does all the segmentation, and sorting of each segment. Getting back to the invariant again: “The items in the region S1 are all less than the pivot, and items in region s2 are all greater than or equal to the pivot”, we can actually enforce the invariant by each subsequent call to partition, by a simple technique. We initialize (Figure 12.21) the index lastS1 = first and firstUnknown = first + 1 By doing so, we are forcing the whole array, except the pivot to be unknown and this situation is shown in Figure 12.24.

Pivot S1 S2 Unknown

Page 421: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 55 of 87

FIG. 12.24 Notice that doing so conveniently preserves the invariant at the beginning of the method call, as the width of both S1 and S2 is made zero. However, with each call to the method partition, an element from unknown is taken and is placed in region S1 or S2. This is done by swapping the elements from unknown with the element in region S1 or S2 (Figure 12.25).

FIG. 12.25 This reduces the length of unknown by one cell with each call to the function partition. Figure 12.25 shows the possibility that element at firstUnknown is less than pivot p. In this case, the element at firstUnknown is swapped with the element at lastS1+1, thus reducing the unknown by one. The other possibility is shown by Figure 12.26, where the element at firstUnknwon is larger than or equal to P, in which case the firstUnknown is simply incremented by one.

Page 422: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 56 of 87

FIG. 12.26 Figure 12.27 shows the process more clearly.

Page 423: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 57 of 87

FIG. 12.27 At the beginning of the call to partition, the pivot is chosen to be 27, and firstUnknown is set to one to preserve the invariant. Then the reduction in size of unknown, must take place. 38 is larger than pivot (27), so it belongs to S2, so we move firstUnknown by one. S2 is thus created. But the element after that, 12, belongs to S1, so to create S1, we swap 38 and 12. Both S1 and S2 are now created. Next element, 39 belongs to S2, so firstUnknown is simply incremented. Next element 27 also belongs to S2, so we increment firstUnknown, one more time. But the last element 16, belongs to S1, so we swap 38 and 16, and increase the S1 by one. The final task is to put the pivot (27) between the sub-arrays S1 and S2. Swapping 16 and 27 does this. Notice that how the “invariant” was preserved all through the

Page 424: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 58 of 87

entire execution. The pseudo-code for the method partition can now be written easily as follows (ALG7). The Listing 12.9 shows the code for method partition. We show the code for choosePivot later. //************************************************************* private static int partition(Comparable[ ] theArray, int first, int last) { choosePivot (theArray, first, last); Comparable pivot = theArray [first]; // reference pivot // Initially, everything but pivot is in unknown int lastS1 = first; // index of last item in S1 // move one item at a time until unknown region is empty for (int firstUnknown = first + 1; firstUnknown <= last; ++firstUnknown) {

if (theArray[firstUnknown].compareTo(pivot) < 0) {

// item from unknown belongs in S1 ++lastS1; swap(theArray, firstUnknown, lastS1); }

ALG7 int partition (theArray, first, last) Call method choosePivot, which chooses a pivot and swaps it with the first array element. (Since the pivot is in first array location, we know what its value is). *Initialize //set P equal to the pivot value P = theArray [first] //Set S1 and S2 to be empty. lastS1 = first firstUnknown = first + 1 //Creation and expansion of regions S1 and S2 while firstUnknown is less than or equal to last (See Figure 12.27) if (theArray[firstUnknown] < P) Move theArray[firstUnknown] into region S1 Else Move theArray[firstUnknown] into region S2 //end of while //place the pivot between the S1 and S2 in its proper location Swap theArray[first] with the theArray[lastS1] Return lastS1 as the location of the pivot.

Page 425: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 59 of 87

// else item from unknown belongs in S2, which is done by //incrementing firstUnknown

} //end of for loop // place pivot in proper position and mark its location swap(theArray, first, lastS1); return lastS1; } private void Swap(Comparable [ ] theArray, int firstUnknown, int lastS1) { Comparable tempItem = theArray[firstUnknown]; theArray[firstUnknown] = theArray[lastS1]; theArray[lastS1] = tempItem; } private void choosePivot(Comparable [ ] theArray, int first, int last) { int val = (first + last)/2; swap ( theArray, first, val); } //Listing 12.9 Notice that Listing 12.9 gives only one version of method choosePivot. Many other versions are possible. Listing 12.10 gives the complete class QuickSort, which has all the methods and main to test quickSort developed above. //************************************************************ public class QuickSort { public static void quickSort (Comparable [ ] Data, int low_index, int high_index) { int partition_index; if((high_index - low_index) > 0) { partition_index = partition (Data, low_index, high_index); quickSort (Data, low_index, partition_index-1); quickSort (Data, partition_index+1, high_index); } } private static int partition(Comparable[ ] theArray, int first, int last) { choosePivot (theArray, first, last); Comparable pivot = theArray [first]; // reference pivot // Initially, everything but pivot is in unknown int lastS1 = first; // index of last item in S1 // move one item at a time until unknown region is empty for (int firstUnknown = first + 1; firstUnknown <= last; ++firstUnknown) { if (theArray[firstUnknown].compareTo(pivot) < 0) { // item from unknown belongs in S1

Page 426: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 60 of 87

++lastS1; swap(theArray, firstUnknown, lastS1); } // else item from unknown belongs in S2, which is done by //incrementing firstUnknown } //end of for loop // place pivot in proper position and mark its location swap(theArray, first, lastS1); return lastS1; } private static void swap(Comparable [ ] theArray, int firstUnknown, int lastS1) { Comparable tempItem = theArray[firstUnknown]; theArray[firstUnknown] = theArray[lastS1]; theArray[lastS1] = tempItem; } private static void choosePivot(Comparable [ ] theArray, int first, int last) { int val = (first + last)/2; swap ( theArray, first, val); } public static void main(String [] args) { Comparable [ ] Array = new Integer[]{ new Integer(36),new Integer(24), new Integer(10), new Integer(6),new Integer(12)}; System.out.println("Printing unsorted integer array."); for(int index = 0; index<Array.length;index++) System.out.println(Array[index]); quickSort(Array,0,Array.length-1); System.out.println("Printing sorted integer array."); for(int index = 0; index<Array.length;index++) System.out.println(Array[index]); System.out.println(); Comparable [ ] Array2 = new String[]{"Zack", "Mary","Zelda","Aron","Satish", "Ramon", "Bush","Carolyne","Heather"}; System.out.println("Printing unsorted string array."); for(int index = 0; index<Array2.length;index++) System.out.println(Array2[index]); quickSort(Array2,0,Array2.length-1); System.out.println("Printing sorted string array."); for(int index = 0; index<Array2.length;index++) System.out.println(Array2[index]); System.out.println(); Comparable [ ] Array5 = new Float[]{new Float(-2.1),new Float(2.3), new Float(1.0), new Float(68.3),new Float(12.9)}; System.out.println("Printing unsorted float array."); for(int index = 0; index<Array5.length;index++) System.out.println(Array5[index]); System.out.println("Printing sorted float array.");

Page 427: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 61 of 87

quickSort(Array5,0,Array5.length-1); for(int index = 0; index<Array5.length;index++) System.out.println(Array5[index]); System.out.println(); Comparable [ ] Array4 = new Character[]{new Character('Z'),new Character('D'), new Character('A'), new Character('B'), new Character('O')}; System.out.println("Printing unsorted character array."); for(int index = 0; index<Array4.length;index++) System.out.println(Array4[index]); quickSort(Array4,0,Array4.length-1); System.out.println("Printing sorted character array."); for(int index = 0; index<Array4.length;index++) System.out.println(Array4[index]); } }

//Listing 12.10 The results of Listing 12.10 are given in the Figure 12.28 below. //*********************************************************** Printing unsorted integer array. 36 24 10 6 12 Printing sorted integer array. 6 10 12 24 36 Printing unsorted string array. Zack Mary Zelda Aron Satish Ramon Bush Carolyne Heather Printing sorted string array. Aron Bush Carolyne Heather Mary Ramon Satish Zack Zelda

Page 428: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 62 of 87

Printing unsorted float array. -2.1 2.3 1.0 68.3 12.9 Printing sorted float array. -2.1 1.0 2.3 12.9 68.3 Printing unsorted character array. Z D A B O Printing sorted character array. A B D O Z

//********************************************** //FIG. 12.28 The quick sort works the best if the choice of pivot is such that it is a randomly chosen element from the array being processed by the method partition. When choice of pivot does not conform to this specification and when array is nearly sorted, or array has all the same numbers, the performance of quick sort may degrade to an O (N2) algorithm, like selection sort. In addition, the quick sort is not very efficient for sorting small arrays. The heap sort removes the degrading performance problem of quick sort. Heap Sort We need to discuss the concept of a heap before discussing heap sorting. Concept of a heap A heap may be a linear or nonlinear data structure, where each element contains a key, such that key in the element in position k is equal or larger than the key in the position 2k+1, and in 2k+2 (if exists). One must remember, however, that in java the array index starts at zero. Therefore, the element with index or subscript k is in (k+1 )th element in the list. An example of a heap data structure stored in an array is shown in Figure 12.29.

Page 429: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 63 of 87

FIG. 12.29 Notice that the array in Figure 12.29 satisfies the definition of a heap. The element 85, in position zero is the largest in whole list. The element in index one, 70, is larger than the elements at indices 2*1 + 1 = 3, and at index 4 and so on. The nonlinear form of a heap will look like a binary tree. There are however some restrictions on heap, those are not there on a general binary tree. The heap binary tree must satisfy the following two properties:

• Order property: For every node in the tree the value of the key must be equal or higher than the value of keys in its children. This property is just a restatement of the heap definition we gave earlier.

• Shape property: A heap must be a complete binary tree. A complete binary tree is either full of full through the next-to-last level, with leaves on the last level as far to the left as possible. A full binary tree is the one where all leafs are at the same level, and all non-leaf nodes have two children. Figure 12.30 shows the examples of full and complete binary tree. Note that a full binary tree is also a complete binary tree, but the reverse may not always be true.

The definition of heap given above is also called maximum heap. There is a minimum heap also, where each node is smaller or equal to its children node. FIG. 12. 30 Triangle is full binary tree and the cut off triangle is complete binary tree We can put elements in a heap by maintaining the order and the shape property. More than one possible heaps can be generated for the same collection of elements. Therefore, heap is not as unique as binary search tree, where for elements added in

Page 430: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 64 of 87

a sequence, only a unique structure will result. Note that every sub tree of a heap is also a heap. Heaping algorithm Interestingly enough though we visualize a heap, using binary tree, the actual heap implementation is done using a linear data structure such as an array, vector or linked list. This is possible, because each heap is a complete binary tree, so it is always balanced, thus we would be able to decipher the implicit links between the array cells and spaces between filled cells will not be empty. Following rules are used to place the elements in an array, which will simulate a “maximum heap”.7

• Assume the index is the index of the array used as heap. • If an element is not the root, than its parent is at position

(index –1)/2. • If an element has a left child, that child is in position (index*2)+1 • If an element has a right child, that child is in position

(index*2)+2 • Root is placed in the index zero.

Understand that either in the array, or in the binary tree representation, more than one heap configurations are possible. Reversing above rules gives us the strategy for converting a heap array into binary tree representation. These rules are as follows:

• Put the first element of the array in root position. • For every root of the tree or sub tree, the left child will be in the

position (index *2) + 1 and right child will be in the position (index*2) + 2. If those positions or one of those positions is empty, either then the element is a leaf node or it has one child only.

Using these rules we show as to how the heap array in the Figure 12.29 can be represented as a binary tree. The highest index in the array is 12. using the formula 12 = index*2 + 2, gives us index = 5. This means that 58 is the right child of element in index 5. The table below shows the calculations as to the element in which index has which other element as its left or right child. This will remain standard for any conversion from the array to binary heap tree. The elements in index numbers one and two are the left and right children of the root. Position of the element = index

Position of left child = (index*2 ) + 1

Position of right child =(index*2) +2

1 3 4 2 5 6 3 7 8 4 9 10 5 11 12 6 13 14 7 15 16 7 Since in this work, we only use maximum heap, from now on, unless specified, the heap will mean a “maximum heap”.

Page 431: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 65 of 87

8 17 18 9 19 20 10 21 22 Table 12.8 In an array of length L all elements from index L/2 to index L-1 are leaf nodes and are a heap. Using the results from above table, we now convert the binary tree representation of the heaped array given in Figure 12.29. *5 is the root node. 70 is the left child of root, and 80 is the right child. From then on, we can just go with the difference of 22two and three to find the left and right child respectively as we use the table 12.8. The resulting binary tree heap is given in Figure 12.31.

FIG. 12.31 Logic of heap sort Since, heap has the property of the largest element always being on the top, it can be used to sort a list using following steps:

1. Take the element from the root of the heap and put it in the last slot of the sorted array.

2. Reheap the remaining elements, which put the next largest element at the root.

3. Repeat steps one and two, until, there are no more elements left in the heap. After this, the array will be in the sorted form.

The first part of the algorithm is similar to selection sort. However, what makes the algorithm faster is finding the next largest element. The shape property of the heap keeps the binary tree at a minimum height at all times. Therefore, finding the next largest element never requires more than log2N comparisons, as opposed to O (N) comparison time in selection sort.

Page 432: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 66 of 87

Process of Heap Sort In its normal state, an array to be sorted may not be a heap. Therefore, the first task to use heap sort for array sorting is to convert the array into a heap. Understand that every array already has at least one property of a heap. That is the shape property. Figure 12.32 shows an array and a heap made from that array using rules in Table 12.8.

FIG. 12.32 The heap shown in Figure 12.32 has the shape property but not the order property. Also all the leaf nodes are already heaps. The node containing value 2 is not a heap, but it can be converted to a heap easily. One way to do that will be to remove the element 2, which converts the tree with node 2 as a tree with empty root. Then we can add another item from the tree, to preserve the order property. A method can be developed to do this. Let us call this method as reheapDown, as it adds an item to the empty root. Starting from the lowest non-leaf nodes, we can reheapDown, nodes at each level, up to the root, such that the whole tree will eventually turn into a heap. Once the reheapDown has been called on the root node, the whole tree must be a heap. After that, the whole tree will satisfy the order as well as the shape property of the heap. Process of re-heaping the entire tree may look like the Figure 12.33 below.

Page 433: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 67 of 87

FIG. 12.33 We start with the lowest, left most, non-leaf node of tree. In this case, it is the node 2 (Figure 12.33 b). The way to convert this sub-tree into a heap is to swap nodes 2 and 19. This sub-tree converts to heap (Figure 12.33 C). Then we go to the non-leaf node on the rightmost part of the tree, and see that sub-tree with node 36 is not a heap. Therefore, we swap 100 and 36, and that sub-tree now converts to a heap. Then we go to sub-tree with node 17, which is not a heap (Figure 12.33 d). We swap 17 and 19, and that sub-tree then converts into a heap (Figure 12.33e). At this point, whole tree except the root node is a heap. To convert the whole tree into a heap, we swap nodes 25 and 100. However, that disturbs the balance of sub-tree with root as 25. Therefore, we need to do another swap between 25 and 36 so that 36 will become parent of 25. Now the whole tree is a heap (Figure 12.33f). The array after such heaping will look like Figure 12.34.

Page 434: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 68 of 87

FIG. 12.34 In heap sort the element 100 will then be swapped with 7, and last element will form the sorted array. The algorithm will once again need to re-heap the array to find the next largest value. We write the method reheapDown, which will take an array and convert it to a heap. Method reheapDown The method reheapDown must take the array, a beginning index and an end index for the array portion to be heaped. The prototype of the method is as follows: public static void reheapDown (Comparable [ ] Vals, int root, int bottom); The algorithm of method reheapDown is given as follows (ALG8)

Page 435: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 69 of 87

The method swap used in ALG8 is standard swap method used in this chapter. The source code for the method reheapDown is given below in Listing 12.11. private static void reheapDown(Comparable [] Vals, int root, int bottom) { int maxChild = 0; int leftChild = root*2 +1; int rightChild = root*2+2; if(leftChild<=bottom) { if(leftChild==bottom) { maxChild = leftChild; } else { if (Vals[leftChild].compareTo(Vals[rightChild])<0 ||Vals[leftChild].compareTo(Vals[rightChild])==0 ) maxChild = rightChild; else maxChild = leftChild; } if(Vals[root].compareTo(Vals[maxChild])<0) { swap(Vals,maxChild,root); reheapDown(Vals,maxChild,bottom); } } } Listing 12.11 We show the following PowerPoint presentation to illustrate the mechanism of heap sort one more time. IllustratingHeapSort.ppt

ALG8 void reheapDown(Comparable [ ] Vals, int root, int bottom); IF root is not a leaf Find the child with larger value Set maxChild equal to the index of larger child If Vals [root] is smaller than the Vals [maxChild]

Swap the values at indices root and maxChild reheapDown (Vals, maxChild, bottom)

Page 436: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 70 of 87

Now since most of the work is done by the method reheapDown, the algorithm and pseudo-code for the method heapSort becomes easier. The prototype of the method heapSort will be as follows: public static void heapSort(Comparable [ ] Vals, int first, int last); We can now implement the steps outlined on page 66 to write the pseudo-code and algorithm ALG9 for method heapSort. The complete code for class HeapSort, demonstrating the action of method heapSort is given in Listing 12.12. import java.io.*; public class HeapSort { private static void reheapDown(Comparable [] Vals, int root, int bottom) { int maxChild = 0; int leftChild = root*2 +1; int rightChild = root*2+2; if(leftChild<=bottom) { if(leftChild==bottom) { maxChild = leftChild; } else { if (Vals[leftChild].compareTo(Vals[rightChild])<0 ||Vals[leftChild].compareTo(Vals[rightChild])==0 ) maxChild = rightChild; else maxChild = leftChild; }

ALG9 public static void heapSort(Comparable [ ] Vals, int first, int last) //Convert the incoming array to a heap first. Leaf nodes are already heaps. Therefore begin from the index last and move down in index. For index beginning at last, index >=0, index— reheapDown(Vals, index, last-first) //This converts the array to heap //Now do the sorting, Since the largest value is already on the top Set passcount equal to first while passcount is less than array length swap( Vals, first, length-passcount-1)//same logic as selection sort increment passcount reheapDown(Vals, first, length-passcount-1)

Page 437: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 71 of 87

if(Vals[root].compareTo(Vals[maxChild])<0) { swap(Vals,maxChild,root); reheapDown(Vals,maxChild,bottom); } } } private static void swap(Comparable [ ] theArray, int firstUnknown, int lastS1) { Comparable tempItem = theArray[firstUnknown]; theArray[firstUnknown] = theArray[lastS1]; theArray[lastS1] = tempItem; } public static void heapSort(Comparable [ ] Vals, int first, int last) { first = 0;//will not sort partial array last = Vals.length-1;//Will not sort partial array int length = last-first+1; for(int index = last; index>=first; index--) reheapDown(Vals,index,length-1); for(int passcount = first;passcount<length; ) { swap(Vals,first,length-passcount-1); passcount++; reheapDown(Vals,first,length-passcount-1); } } public static void main(String [] args) throws IOException { //uncomment the code below to sort an array or 200 random numbers /*Comparable [] Rand_Arr = new Comparable [200]; for(int index=0; index<Rand_Arr.length; index++) {

int temp = (int)(Math.random()*1000)/100 + (int)(Math.random()*1000)%100;

Rand_Arr[index]= new Integer(temp); } System.out.println("Printing unsorted random array."); printArray(Rand_Arr); heapSort(Rand_Arr,0,Rand_Arr.length); System.out.println("Printing Sorted random array."); printArray(Rand_Arr); System.in.read();*/ Comparable [ ] Array = new Integer[]{ new Integer(36),new Integer(24), new Integer(10), new Integer(6),new Integer(12), new Integer(-22), new Integer(200)}; System.out.println("Printing UNSORTED integer array.");

Page 438: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 72 of 87

printArray(Array); heapSort(Array,0,Array.length-1); System.out.println("Printing sorted integer array."); printArray(Array); Comparable [ ] Array2 = new String[]{"Zack", "Mary","Zelda","Aron","Satish", "Ramon", "Bush","Carolyne","Heather"}; System.out.println("Printing UNSORTED string array."); printArray(Array2); heapSort(Array2,0,Array2.length-1); System.out.println("Printing sorted string array."); printArray(Array2); Comparable [ ] Array5 = new Float[]{new Float(-2.1),new Float(2.3), new Float(1.0), new Float(68.3),new Float(12.9)}; System.out.println("Printing UNSORTED float array."); printArray(Array5); System.out.println("Printing sorted float array."); heapSort(Array5,0,Array5.length-1); printArray(Array5); Comparable [ ] Array4 = new Character[]{new Character('Z'),new Character('D'),new Character('A'), new Character('B'), new Character('O')}; System.out.println("Printing UNSORTED character array."); printArray(Array4); heapSort(Array4,0,Array4.length-1); System.out.println("Printing sorted character array."); printArray(Array4); } public static void printArray(Object []Arr) { for(int index =0; index<Arr.length; index++) { System.out.print(Arr[index]+ " "); if(index%7 == 0) System.out.println(); } System.out.println(); } }

//Listing 12.12 The results of Listing 12.12 are similar to Figure 12.28. One of the big advantages of heap sort algorithm is that it does not degenerate to O (N2) algorithm for certain data sets like quick sort can. It consistently performs as a O (Nlog2N) algorithm for all data sets, and in that sense is more reliable. However, we may like to have even a faster sorting algorithm. Radix sort performs faster than heap sort does, and we discuss it next.

Page 439: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 73 of 87

Radix Sorting Radix sorting is very different from comparison-based algorithms. The value to be sorted would have number of character positions in it. A key is designed for each position. For example for an integer value, the key could be digits 0, 1, 2,……… 9. For alphabets (ignoring case sensitivity), the key could be a,b,c,d,…….z. These possible values are called “radix”. The members of an array are to be sorted is put into a group of baskets, based on the radix of least significant character. Then they are recombined to perform partial sorting. Consider the original array in the Figure 12.35.

FIG. 12.35 Note that we must pad the numbers with leading zeros, so that all numbers have same number of digits. Then we must create buckets for each radix, which in this case are digits zero to nine. In each bucket, we would need queues, in which we would place array members. Figure 12.36 shows an empty bucket and queue system. [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] FIG. 12.36 We start scanning the array for the least significant (right most) digit of each number. The number is placed in a bucket matching the radix of the digit being scanned. For example, in the first pass through the array in Figure 12.35, we scan the right most digit of first array element 762. The radix of that digit is 2, so we place it in the first queue of bucket #2 (Figure 12.37).

Must pad the numbers with leading zero as needed, so that all have the same number of digits in them.

Page 440: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 74 of 87

[0] [1] [2] [3] [4] [5] [6] [7] [8] [9] 762 FIG. 12.37 Next number is 124, so it gets placed in the first queue of bucket number 4 (Figure 12.38). [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] 762 124 FIG. 12.38 We continue scanning until all the numbers have been placed in one or the other bucket. After first scan, the buckets and queues look like Figure 12.39. FIG. 12.39 Now we copy back the values to the array starting vertically down from leftmost bucket and moving rightward towards the higher number buckets. For example the sequence of copying back to the array will be 800, 100, 761, 001, 762…. 999. After copying, and after first pass, the array will look similar to Figure 12.40.

Page 441: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 75 of 87

FIG. 12.40 Notice that after first such pass the value with the largest right most digit is bubbled to the end of the array. We process the array again, but this time we scan the 2nd significant figure from the right. The buckets and array after second pass look similar to the Figure 12.41. FIG. 12.41

Page 442: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 76 of 87

In the last pass, we place array elements in the buckets using the first significant or left most figures. The buckets and array after third pass look like Figure 12.42. At this point, the array is sorted in ascending order. FIG. 12.42 Looking at the progress of radix sort, we can say that in each pass it sorts the digits in the position, which equals the pass number. In pass number zero, the digits in rightmost location are sorted and so on. Let us call the method to perform radix sort as radixSort. Based on the discussion above, the method will need two loops. First loop will iterate number of significant figures in the largest number. For example, if the largest number in the array is 10,000, then the first loop will need to iterate over five positions, one for each significant figure. The inner loop will then go look at each element of the array, and put them into buckets or queues based on the significant figure being sorted. The signatures of method radixSort are as follows:

Page 443: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 77 of 87

public static void radixSort ( Object [ ] values, int numValues, int numPositions, int radix) Notice that the array to be sorted (values in this case) does not have to be type Comparable, because radix sort never compares the objects to be sorted! For the sake of simplicity in our actual example we use an array of StringBuffer objects and we alter the signatures of the method radixSort accordingly. The algorithm and pseudo-code of method radixSort is given below: (ALG10). The source code for the method radixSort, replacing the object array with a StringBuffer array is shown below in Listing 12.13. public static void redixSort(StringBuffer []values, int numValues, int numPositions, int radix) { LinkedQueue [] queues = new LinkedQueue [radix]; for(int index=0; index<queues.length; index++) queues[index]= new LinkedQueue(); int whichQueue = 0; for(int position = numPositions-1; position>=0; position--) { for(int counter = 0; counter<numValues; counter++) { char val = values[counter].charAt(position); whichQueue = Integer.parseInt( new Character(val).toString()); queues[whichQueue].enqueue(values[counter]); } collectQueues(values,queues,radix); } }//Listing 12.12

ALG10 public static void radixSort ( Object [ ] values, int numValues, int numPositions, int radix) Create an array of FIFO queue with array length being equal to the integer radix. //numPositions is the number of significant digits in the largest number in the array For position from numPosition-1 to position zero and decrementing position by one For counter going from zero to less than numValues & incrementing by one

Set the value of whichQueue to the value of the integer at the position of values [counter]//determines as into which queue the array member will go in Enqueue the values [counter] member of the array in queue [whichQueue]

Call the method collectQueues (values, queues, radix)

Page 444: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 78 of 87

Notice that we use the linked version of the queue, so there is no limitation on the size of the queue. StringBuffer objects, containing integers are easy to sort. Therefore, we change the method signature accordingly to include an array of StringBuffer, instead of an object array. The purpose of the method collectQueues is to collect the queues populated in each loop iteration, iterating over the significant positions in the integers, and then fill the array back by dqueueing them as discussed earlier when we illustrated the mechanism of radix sort. The signatures of collectQueues method are as follows: private static void collectQueues(Object [ ] values, LinkedQueue [ ] queues, int radix); Again, we replace the object array with a StringBuffer array, for the sake of simplicity. The pseudo-code and algorithm for the method collectQueues is as follows (ALG11). Listing 12.13 shows the entire source code for the class RadixSort, which include the supporting methods and the main method. //*************************************************************** public class RadixSort { /**numPositions is the number of positions in the largest digit in the * array. */ public static void redixSort(StringBuffer []values, int numValues, int numPositions, int radix) { LinkedQueue [] queues = new LinkedQueue [radix]; for(int index=0; index<queues.length; index++) queues[index]= new LinkedQueue(); int whichQueue = 0; for(int position = numPositions-1; position>=0; position--)

ALG11 private static void collectQueues(Object [ ] values, LinkedQueue [ ] queues, int radix); Set index to zero For counter going from zero to less than radix & incrementing by one While queues [counter] is not empty dequeue an item from queues [counter] copy the dequeued item into values [index] increment the index by one. //Since all queues have only number of items equal to the array length, we //need not control the length of the array.

Page 445: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 79 of 87

{ for(int counter = 0; counter<numValues; counter++) { char val = values[counter].charAt(position); whichQueue = Integer.parseInt( new Character(val).toString()); queues[whichQueue].enqueue(values[counter]); } collectQueues(values,queues,radix); } } //******************************************************** private static void collectQueues(StringBuffer [] values, LinkedQueue [ ] queues, int radix) { Object item = new Object(); int index = 0; for(int counter=0; counter<radix; counter++) { while(!queues[counter].isEmpty()) { item = queues[counter].dequeue(); values[index++] = (StringBuffer)(item); } } } //************************************************************ private static int findLargestNum(StringBuffer []Rand_Arr) { int length = Rand_Arr[0].length(); for(int index = 0; index<Rand_Arr.length; index++) { if(length < Rand_Arr[index].length()) length = Rand_Arr[index].length(); } return length; } //*********************************************************** private static int padStrings(StringBuffer [] Rand_Arr) { int length = findLargestNum(Rand_Arr); for(int index=0; index<Rand_Arr.length; index++) { if(Rand_Arr[index].length()<length) { doPadding(Rand_Arr[index],length); } } return length; } //******************************************************* private static void doPadding(StringBuffer Str,int length) { //pad the string to length length by adding leading zero

Page 446: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 80 of 87

int num = length - Str.length() ;//needs that many pads for(int index = 0; index<num; index++) { Str = Str.insert(0,'0'); } } //*********************************************************** /*public static void stripLeadingZeros(StringBuffer [ ] vals, int length) { for(int counter=0; counter<vals.length; counter++) { for(int index = 0; index<length-1; index++) { char temp = vals[counter].charAt(index); if( temp =='0') { vals[counter].deleteCharAt(index); } else break; } } }*/ //************************************************************** public static void main(String []args) { //First only developing to sort integers. //Create integer array as a StringBuffer array StringBuffer [] Rand_Arr = new StringBuffer [40]; //for(int index=0; index<Rand_Arr.length;index++) // Rand_Arr[index] = new StringBuffer(3); for(int index=0; index<Rand_Arr.length; index++) {

int temp = (int)(Math.random()*1000)/100 + (int)(Math.random()*1000)%100;

Rand_Arr[index]= new StringBuffer(new Integer(temp).toString()); } System.out.println("Printing unsorted random array."); printArray(Rand_Arr); //Find the length of largest string //Now pad all the strings in the array to same length by adding //leading zeros int numPositions = padStrings(Rand_Arr); System.out.println("Printing after padding."); printArray(Rand_Arr); redixSort(Rand_Arr,Rand_Arr.length,numPositions,10); System.out.println("Printing array after radix sorting."); printArray(Rand_Arr); //convert back to integer array int [] int_arr = new int [Rand_Arr.length]; for(int index=0; index<Rand_Arr.length; index++) {

Page 447: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 81 of 87

int_arr[index] = Integer.parseInt(Rand_Arr[index].toString()); } System.out.println("Printing integer array after radix sorting."); printArray(int_arr); } public static void printArray(Object []Arr) { for(int index =0; index<Arr.length; index++) { System.out.print(Arr[index]+ " "); if(index%7 == 0) System.out.println(); } System.out.println(); } public static void printArray(int []Arr) { for(int index =0; index<Arr.length; index++) { System.out.print(Arr[index]+ " "); if(index%7 == 0) System.out.println(); } System.out.println(); } }

//Listing 12.13 Figure 12.43 shows the output of Listing 12.13, where we show unsorted and sorted StringBuffer forms of a random integer array, and finally the integer form of the same sorted array, where the leading zeros are stripped away again.

Page 448: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 82 of 87

FIG. 12.43 Our strategy in this program was as follows:

1. We created an array of randomly generated integer of size 40. 2. We copied them to an array of StringBuffer of size 40. 3. We call method padStrings, which takes the StringBuffer array as an

argument, and pads all the numbers having lesser significant digits than the largest number with leading zeros. For example in Figure 12.43, the integer 6 was padded to 006, because the largest number, 107 has three significant figures in it. Method padStrings also returns the number of significant figures in the largest number.

4. After that, we sort the array and copy it into an integer array. The copy process automatically deletes the leading zeros from StringBuffer objects, which were put in to facilitate sorting.

The amount of work done by radix sort depends upon the size of the array to be processed (N) and the number of positions P in each array member to be processed. Based on that we may say that it is an O (N*P) algorithm. However, for large array sizes, N will dominate. Therefore, it is an O (N) algorithm.

Page 449: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 83 of 87

Appendix A12 The javadoc for the recursive version of selection sort method is given below. The source code follows after the javadoc. Class SelectionSortRecursive java.lang.Object

SelectionSortRecursive

public class SelectionSortRecursive extends java.lang.Object

This class shows the recursive technique applied to selection sort method.

Method Summary static int indexOfLargest(java.lang.Object[] Array, int lower_bound,

int upper_bound) Finds the index of largest element in the array and returns it.

static void main(java.lang.String[] args)

Instaintiates an array of integers. static void selectionSort(java.lang.Object[] Array, int lower_bound,

int upper_bound) Sorts the entire array of objects or a portion of it passed to it.

static void swap(int index_largest, int swap_from, java.lang.Object[] Array) Swaps elements from one index to the other in the array.

Constructor Detail

SelectionSortRecursive public SelectionSortRecursive()

Creates a new instance of SelectionSortRecursive

Method Detail

main public static void main(java.lang.String[] args)

Instaintiates an array of integers. Calls recursive selection sort method. Prints the sorted array. Parameters: args - the command line arguments. No command line arguments passed in this case.

selectionSort

public static void selectionSort(java.lang.Object[] Array, int lower_bound, int upper_bound)

Page 450: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 84 of 87

Sorts the entire array of objects or a portion of it passed to it. Requires that lower_bound<=upper_bound. Parameters: Array - The array of objects to be sorted. lower_bound - the index of lower bound of the array portion to be sorted. upper_bound - index of upper bound of the array to be sorted.

indexOfLargest

public static int indexOfLargest(java.lang.Object[] Array, int lower_bound, int upper_bound)

Finds the index of largest element in the array and returns it. Parameters: Array - Array of objects lower_bound - lower bound of the array upper_bound - upper bound of the array Returns: the index of largest element with in the lower_bound and upper_bound

swap

public static void swap(int index_largest, int swap_from, java.lang.Object[] Array)

Swaps elements from one index to the other in the array. Parameters: index_largest - one of the index involved in swap swap_from - other index involved in swap

Page 451: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 85 of 87

Array - the array in which swap is to be performed. /* * SelectionSortRecursive.java * Created on April 8, 2006, 8:15 AM *Reference: Prof. Paul Hilfinger Notes on CS61 B at UC Berkeley */ /** * This class shows the recursive technique applied to selection sort method. * @author Satish Singhal */ public class SelectionSortRecursive { /** Creates a new instance of SelectionSortRecursive */ public SelectionSortRecursive() { } /** * Instaintiates an array of integers. Calls recursive selection sort method. * Prints the sorted array. * @param args the command line arguments. No command line arguments * passed in this case. */ public static void main(String[] args) { Object [ ] Array = new Integer[]{ new Integer(36),new Integer(24), new Integer(10), new Integer(6),new Integer(12)}; selectionSort(Array,0,Array.length-1); for(int index = 0; index<Array.length;index++) System.out.println(Array[index]); } /** * Sorts the entire array of objects or a portion of it passed to it. * Requires that lower_bound<=upper_bound. * @param Array The array of objects to be sorted. * @param lower_bound the index of lower bound of the array portion to be sorted. * @param upper_bound index of upper bound of the array to be sorted. */ public static void selectionSort(Object [ ] Array, int lower_bound, int upper_bound) {

Page 452: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 86 of 87

if(lower_bound < upper_bound) { int largest = indexOfLargest(Array, lower_bound, upper_bound); swap(largest,upper_bound,Array); selectionSort(Array,lower_bound,upper_bound-1); } } /** * Finds the index of largest element in the array and returns it. * @param Array Array of objects * @param lower_bound lower bound of the array * @param upper_bound upper bound of the array * @return the index of largest element with in the lower_bound * and upper_bound */ public static int indexOfLargest(Object[ ] Array, int lower_bound, int upper_bound) { if(lower_bound >= upper_bound) return upper_bound; else { int largest = indexOfLargest(Array, lower_bound+1, upper_bound); if(((Comparable)Array[lower_bound]).compareTo(Array[largest])>0) return lower_bound; else return largest; } }//end of method indexOfLargest /** * Swaps elements from one index to the other in the array. * @param index_largest one of the index involved in swap * @param swap_from other index involved in swap * @param Array the array in which swap is to be performed. */ public static void swap(int index_largest, int swap_from, Object [] Array) { Object Temp = Array[index_largest]; Array[index_largest]= Array[swap_from]; Array[swap_from]= Temp; } }//end of class //Listing A12.1

Page 453: E_Book_JavaDataStructure_SatishSinghal

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 87 of 87