Anda di halaman 1dari 453

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: Note the absence of
//*************************************************
package name! This
class MyClass means that class is in
{ an unknown package
and is not accessible
//---------------- outside it!
}

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.

Java Essentials – Topic 1 Page 1 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:


//************************************************* Includes the
package datastructures; package name of
which the class is
member.
public class MyClass2
{
//---------------- Class is declared
public.
}

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

Java Essentials – Topic 1 Page 2 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.
Instance field
//*************************************************
public class MyClass
{ Class field
int value;
static int standard_num;
Class method
static int findSquare(int val)
{
return val*val;
} Instance method

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.

Java Essentials – Topic 1 Page 3 of 33


TestMyClass is the client class
residing in the same package
as MyClass.
//*************************************************************
public class TestMyClass
{
public static void main(String[] args) Setting the value of an
{ instance field. Field
MyClass Temp1 = new MyClass( ); named “value” had no
modifier attached to it.
Temp1.value = -10;

MyClass.standard_num = 100; Setting the value


of a class field.
MyClass Temp2 = new MyClass( );
Temp2.value = -30;
Calling instance
System.out.println (Temp1.toString( )); method.
System.out.println (Temp2.toString( ));

int num = MyClass.findSquare (5); Calling class method.

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:

Java Essentials – Topic 1 Page 4 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.

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.

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.

Java Essentials – Topic 1 Page 5 of 33


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

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.

Java Essentials – Topic 1 Page 6 of 33


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

Clients that lie outside


the package also lie
outside the area of Need not lie inside the same package in
visibility! which “ a class” is!

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.

Java Essentials – Topic 1 Page 7 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!

Java Essentials – Topic 1 Page 8 of 33


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

Package – Private or N o M odifier


Package
Field w ith no boundary
m odifier

a class
A rea of
Client 1 visibility.
C lients and
Subclass 2 subclasses,
Client 2 both m ust
Subclass 1 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

Java Essentials – Topic 1 Page 9 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.

Java Essentials – Topic 1 Page 10 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

Java Essentials – Topic 1 Page 11 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

Java Essentials – Topic 1 Page 12 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).

Uses the keyword


public class Vehicle extends to establish
{ inheritance!
//Fields and Methods
}

public class Airplane extends Vehicle


{
//Fields and Methods
}

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.

Java Essentials – Topic 1 Page 13 of 33


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
public class Vehicle package.
{
protected double speed; Visible to class Jet, but invisible
to client code, like main
function, when doing black box
} testing and client lying outside
the package.
public class Airplane extends Vehicle
{
protected int numberOfwings;
}

public class Jet extends Airplane


{
protected double enginethrust;
}

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.

Java Essentials – Topic 1 Page 14 of 33


public class Vehicle
{
protected double speed;
Chains the constructor with no
argument to the constructor
public Vehicle( ) with speed as an argument!
{ Keyword this refers to calling
this (0.0); the constructor, in this case the
one with one argument.
}

public Vehicle (double init_speed)


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

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.

Java Essentials – Topic 1 Page 15 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

Java Essentials – Topic 1 Page 16 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

Java Essentials – Topic 1 Page 17 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

Java Essentials – Topic 1 Page 18 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
}

Java Essentials – Topic 1 Page 19 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

Java Essentials – Topic 1 Page 20 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
}

Java Essentials – Topic 1 Page 21 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.

With exception of java class Object; ALL


classes in java are derived classes!!!
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.

Listing 1.6

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.

Java Essentials – Topic 1 Page 22 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) Veh1 points to an
{ object of type Jet.
Vehicle Veh1 = new Jet(500,4, 200);
Jet class describe method
Veh1.describe( ); is called.
Veh1 = new Airplane(400, 2);

Veh1.describe( ); Veh1 now points to an


object of type Car.
Veh1 = new Car ( 60, 4, 20);

Veh1.describe( ); Car class describe method is


called.
}
}
Listing 1.7
The output of the Listing 1.7 is shown in the Figure 1.14.

Java Essentials – Topic 1 Page 23 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).

Java Essentials – Topic 1 Page 24 of 33


Sequence of Construction of Jet
class instance
Object part is
Time created first!
Object
Constructor call

Vehicle Constructor call


Vehicle part
is created
Airplane Constructor call next

Jet part is
Jet Constructor call 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.

Java Essentials – Topic 1 Page 25 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.

Java Essentials – Topic 1 Page 26 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) If Temp is type Jet,
{ then instanceof will
Object Temp = new Jet(500, 4, 200); return true otherwise it
will return false.

if(Temp instanceof Jet)


{ Now it is safe to cast Temp
Jet Jet1 = (Jet)(Temp); into Jet Type reference.
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.

Java Essentials – Topic 1 Page 27 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:

public final class FinalClass


{
//-----------------------------
}
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

Java Essentials – Topic 1 Page 28 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

Java Essentials – Topic 1 Page 29 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.

Java Essentials – Topic 1 Page 30 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

Java Essentials – Topic 1 Page 31 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( ).

Java Essentials – Topic 1 Page 32 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);
}
Notice that we use the class to
public static void main(String [ ] args) be extends or implements
{ directly and use of keyword
Window1 Temp = new Window1( ); extends or implements is not
needed.
Temp.addWindowListener (
Pair of braces new WindowAdapter( )
after the {
constructor call public void windowClosing (WindowEvent Evt)
defines the body {
of anonymous System.exit(0);
The
class }
unimplemented
}
and other methods
);
are now coded
}
inside the pair of
}
braces, which
define the
anonymous class.
The right parenthesis for the
Listing 1.11 method addWindowListener
seems out of place and awkward
but is syntactically correct!

Java Essentials – Topic 1 Page 33 of 33


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.

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.


FIG. 2.1

Example of an abstract class and its extension.


Let us consider the following inheritance hierarchy (Figure 2.2).

Abstract Classes and inheritance in Java Page 1 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.

Abstract Classes and inheritance in Java Page 2 of 16


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

Implemented
Methods

Abstract
Method

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 Classes and inheritance in Java Page 3 of 16


public class Point extends Shape
{ Fields are x and y
protected int x;
protected int y; coordinates for
the Point.
public Point()
{
this(0);
Default and
} explicit
constructors,
public Point(int a)
{
all chained
this(a,0); together.
}
public Point(int a, int b)
{
this.x = a;
this.y = b;
}
abstract method from
public String getName() the parent class Shape
{ implemented.
return "Point";
}

public void setPoint(int a, int b)


{
this.x = a;
this.y = b;
}

public void setX(int x1) Other


{ methods of
this.x = x1;
} class Point.
public void setY(int y1)
{
this.y = y1;
}

public int getX()


{
return this.x;
}

Abstract Classes and inheritance in Java Page 4 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; New field
edge is
added. It inherits
public Square() x and y.
{
this(0.0);
}

public Square(double edge1) Constructors


{
this(edge1,0,0);
}

public Square(double edge1, int a)


{
this(edge1, a, 0);
}

public Square(double m_edge, int a, int b)


{ Methods
super(a,b);
setEdge(m_edge);
}

public void setEdge(double m_edge)


{
this.edge = (m_edge >= 0 ? m_edge : 0 );
}

Abstract Classes and inheritance in Java Page 5 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()
{ Constructors
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);
}

Abstract Classes and inheritance in Java Page 6 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).

Abstract Classes and inheritance in Java Page 7 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:

Abstract Classes and inheritance in Java Page 8 of 16


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

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

public class Outer


{ This is
static class NetsedClass OK!!
{
Can declare static variables and constants.
}
}

public class Outer


This is
{
also
class InnerClass
OK!!
{
//Cannot declare static variables!!
//Can declare static constants!
}
}

public class Outer This is


{ also
class InnerClass extends SomeClass OK!!
{
//Cannot declare static variables!!
//Can declare static constants!
}
}
Abstract Classes and inheritance in Java Page 9 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.

Static inner
class/nested
class

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

Abstract Classes and inheritance in Java Page 10 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.

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

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:

Abstract Classes and inheritance in Java Page 11 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 ; A constructor to instantiate the object
Frame is now needed. Otherwise the inner
public CloseableWindow3( ) class WindowClosing will not know as to
{ which My_Window to dispose.
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); Before the dispose can be called
} on My_Window, we need an
} object of type CloseableWindow3
to establish a binding between the
public static void main(String [] args) outer class and inner class objects.
{
CloseableWindow3 Instance = new CloseableWindow3( );
Creation of My_Window
also needs an object of
Instance.My_Window.setBounds(10,10,600,600);
type ClosingWindow3.
Instance.My_Window.setBackground(Color.blue);
Instance.My_Window.setVisible(true);

CloseableWindow3.WindowClosing Object_WindowClosing =
Instance.new WindowClosing();

Instance.My_Window.addWindowListener(Object_WindowClosing);
}
} Listing 2.7

Abstract Classes and inheritance in Java Page 12 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();
} Method display has the
same code as the main
public static void display1( ) method, except the
{ window is smaller and
CloseableWindow4 Instance = new CloseableWindow4(); yellow in color and its
Instance.My_Window.setBounds(100,100,100,100); origin is located
Instance.My_Window.setBackground(Color.yellow); differently.
Instance.My_Window.setVisible(true);
CloseableWindow4.WindowClosing Object_WindowClosing =
Instance.new WindowClosing();
Instance.My_Window.addWindowListener(Object_WindowClosing);
}
} Listing 2.8

Abstract Classes and inheritance in Java Page 13 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

Abstract Classes and inheritance in Java Page 14 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.

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.

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.

Abstract Classes and inheritance in Java Page 15 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.

Abstract Classes and inheritance in Java Page 16 of 16


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:

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.*/
}

The class implementing a java interface uses keyword implements to implement it.
Syntax of implementing a java interface is given below.

Java Interfaces Page 1 of 18


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.*/
}

Interfaces can have inheritance relationships between themselves. An interface can


be “extended” from one or more interfaces. The syntax is as follows:

Public interface SomeInterface extends Interface1


{

}
public interface SomeInterface extends Interface1, Interface2
{
……
}
Note that keyword “extends” is used when an interface extends another
interface.

Java Interfaces Page 2 of 18


We show an example of writing a java interface and its implementation in Listing
3.1 below.
public interface Door
{ Only abstract
public void open( ); methods!!!
public void close( );
}

public class CarDoor implements Door


{ Provides
public void open( ) implementation for
{ all the methods
System.out.println ( "Enter the car" ); inherited from
} interface Door!
public void close( )
{
System.out.println ( "Look out, closing the door");
}
}

public class TestDoor


{
public static void main( String[ ] args ) A reference of type Door
{ can point to an object of
Door Instance1 = new CarDoor( ); type CarDoor because
Instance1.open( ); CarDoor is-a Door.
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.

Java Interfaces Page 3 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).

Java Interfaces Page 4 of 18


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

StudentVoter

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.

Java Interfaces Page 5 of 18


StudentVoter
Shown on
next page!

Java Interfaces Page 6 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.

Java Interfaces Page 7 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);
}

Java Interfaces Page 8 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.

Java Interfaces Page 9 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;

Java Interfaces Page 10 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

Java Interfaces Page 11 of 18


this object being “Barney Bush”). This is possible because a StudentVoter is also a
Person type.
public class TestDiamond StudentVoter type
{ object being
public static void main(String[] args) pointed to by same
{ type reference.
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]);
}

Java Interfaces Page 12 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

Java Interfaces Page 13 of 18


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

The polymorphic
method selectionSort
sorts the Person type
array alphabetically.

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.

Java Interfaces Page 14 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.
public class LocalClasses
{
{ Class defined within a stand-alone
class Class1 non-static block.
{

}
}

static
{ Class defined within a stand-alone
class Class2 static block.
{

}
}

public LocalClasses( )
{
class Class3
{ Class defined within a constructor.

}
public static void main(String[ ] args)
{
class Class4 Class defined within a static method. Same
{ way the classes can be defined within non-
static methods as well.
}
}
}

Listing 3.3

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

Java Interfaces Page 15 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.
public class EncloseInterface
{
interface NestedInterface1
{

}
}

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.

public class EncloseInterface


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

class Class1 implements EncloseInterface.NestedInterface1


{
}

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.

Java Interfaces Page 16 of 18


public class EncloseInterface
{
private interface NestedInterface1 Private nested interfaces are
{ only visible within their
member class.
}
{
class someclass implements NestedInterface1
{
}
}
}

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

Java Interfaces Page 17 of 18


public interface InterfaceNesting
{
void method1( );

interface NestedInterface2
{
void method2( );

class ClassInInterface
{

}
}
Only responsible to
class MyClass implements InterfaceNesting implement the method1( ).
{
public void method1( ){ }
}

class YourClass implements InterfaceNesting.NestedInterface2


{
public void method2( ){ } Only responsible to
} implement method2 ( )

class TheirClass extends InterfaceNesting.ClassInInterface


implements InterfaceNesting.NestedInterface2,
InterfaceNesting
{ Must implement both
public void method1( ){ } method1 ( ) and method2 ( )
public void method2( ){ }
}

Listing 3.7

Java Interfaces Page 18 of 18


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.

Topic 5 Stacks Page 1 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”.

Topic 5 Stacks Page 2 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.

Topic 5 Stacks Page 3 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.

Topic 5 Stacks Page 4 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

Topic 5 Stacks Page 5 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 (“”);
} Calls the constructor
for the
public StackUnderflowException(String message) RuntimeException
{ class.
super (message);
}
}
Listing 5.2 Part A

public class StackOverflowException extends RuntimeException


{

Topic 5 Stacks Page 6 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 Creates a stack of size 100, unless


public ArrayStack( ) user calls the constructor with
{ explicit size.
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
{

Topic 5 Stacks Page 7 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


{

Topic 5 Stacks Page 8 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();
}

Topic 5 Stacks Page 9 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.

Topic 5 Stacks Page 10 of 28


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

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.

Topic 5 Stacks Page 11 of 28


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

FIG. 5.5 A

Call to function4 ( ) is popped off the


stack. Control returns to function3( )

FIG. 5.5 B

Topic 5 Stacks Page 12 of 28


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

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

Fig. 5.5 D

Topic 5 Stacks Page 13 of 28


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

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:
ab+
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

Topic 5 Stacks Page 14 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:
Stack

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: topindex location shown by using ‘

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 - * = ?

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

Topic 5 Stacks Page 15 of 28


+ 7 5 + 7 = 12 12
5

First Pop Second Pop Push Result


Apply the back Into
binary Operator Stack
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:
6
12
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:
2
6
12
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:

6 4
12 - 2 12 6-2=4 12

First Pop Second Pop Push Result


Apply the back Into
binary Operator Stack
FIG. 5.6 F

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

Topic 5 Stacks Page 16 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:
cout

* 4 12 * 4 = 48
12
First Pop Second Pop If Stack is Empty
Operation Operation output the result
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.

Topic 5 Stacks Page 17 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:

33 * X + (Y - 12) - Z

output

Stack is
Empty
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.
33* * X + (Y - 12) - Z

output

3
*
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).
33** X + (Y - 12) - Z

output

3X
*
Stack
FIG. 5.7 C

Topic 5 Stacks Page 18 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:

33** X + (Y - 12) - Z

output

3X *
+

Stack
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).

3* X+ ( Y - 12 ) - Z

output

( 3X *
+

Stack
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).

Topic 5 Stacks Page 19 of 28


3* X+ ( Y - 12 ) - Z

output

( 3X * Y
+

Stack
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:
3 * X + ( Y - 12 ) - Z

output

-
( 3X * Y
+

Stack
FIG. 5.7 G
8. Then we read the operand ‘12’ which is outputted(Rule 2). (Figure 5.7H).
3 * X + ( Y - 12 ) - Z

output

-
( 3 X * Y 12
+

Stack
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:

Topic 5 Stacks Page 20 of 28


3* X+ ( Y - 12 ) - Z

output

)
3 X * Y 12 -
(
+
Trash
Stack
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).

3* X+ ( Y - 12 ) - Z

output

)
3 X * Y 12 - + (
_
Trash
Stack
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).
3 * X + ( Y - 12 ) - Z

output

)
3 X * Y 12 - + Z (
_
Trash
Stack
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

Topic 5 Stacks Page 21 of 28


Infix Form to Postfix Form is complete. (Figure 5.7 L).
Postfix
InFix Form Form
3 * X + (Y-12) - Z
output
)
3 X * Y 12 - + Z _ (

Trash
Stack Post Fix Form
Empty
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.

Topic 5 Stacks Page 22 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);
}

Topic 5 Stacks Page 23 of 28


private void convert( )
{
Stack myStack = new Stack();
String nextCharacter;
String topOfStack;
Get the next
for (int i = 0;i<=inputString.length()-1;i++) character.
{
nextCharacter = inputString.substring(i,i+1);
System.out.println(nextCharacter);
if (nextCharacter.equals(")"))
Enforcing rule # 4
{
topOfStack = (String)myStack.pop(); on page 17.
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();
Enforcing
}
} rule #1 on
else if (nextCharacter.equals("(") || nextCharacter.equals("[")) page 17
{
myStack.push(nextCharacter);
}

Topic 5 Stacks Page 24 of 28


else if (nextCharacter.equals("+") || nextCharacter.equals("-") ||
nextCharacter.equals("*") || nextCharacter.equals("/") ||
nextCharacter.equals("^")) Enforcing
{ rule #3 on
topOfStack = (String)myStack.peek( ); page 17.
while (priority(nextCharacter) <= priority(topOfStack))
{
topOfStack = (String)myStack.pop( );
this.outputString+=" ";
outputString += topOfStack;
topOfStack = (String)myStack.peek( );
}
myStack.push(nextCharacter);
}
else Enforcing rule
{ #2 on page 17
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-

Topic 5 Stacks Page 25 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?
else if (nextCharacter.equals("+") || nextCharacter.equals("-") ||
nextCharacter.equals ("*") || nextCharacter.equals("/") ||
nextCharacter.equals ("^"))

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.
while (priority(nextCharacter) <= priority(topOfStack))
{
topOfStack = (String)myStack.pop( );
this.outputString+=" ";
outputString += topOfStack;
topOfStack = (String)myStack.peek( );
}

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:

Topic 5 Stacks Page 26 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.

Infix form entered


by user.

FIG. 5.8 A

Postfix
form
outputted.

FIG. 5.8 B

Topic 5 Stacks Page 27 of 28


FIG. 5.8 C

FIG. 5.8 D

Topic 5 Stacks Page 28 of 28


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).

Topic 6 FIFO Queue Page 1 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.

Topic 6 FIFO Queue Page 2 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

Topic 6 FIFO Queue Page 3 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

Topic 6 FIFO Queue Page 4 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

Topic 6 FIFO Queue Page 5 of 23


FIG 6.4 B

FIG 6.4 C

Topic 6 FIFO Queue Page 6 of 23


FIG 6.4 D

FIG 6.4 E

Topic 6 FIFO Queue Page 7 of 23


FIG 6.4 F

FIG 6.4 G

Topic 6 FIFO Queue Page 8 of 23


FIG 6.4 H

FIG 6.4 I

Topic 6 FIFO Queue Page 9 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).

Topic 6 FIFO Queue Page 10 of 23


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

Front is fixed at zero.


Rear is kept at the empty
FIG. 6.5 A spot behind the last
element added. Note that
there may be some difference
enqueue(A) between various
implementations. For
example, the Fig. 6.4 keeps
the rear pointer at the filled
element.

FIG. 6.5 B

Topic 6 FIFO Queue Page 11 of 23


enqueue (B);

FIG. 6.5 C

Filled Queue. The


front and rear
pointers point to
same location.

FIG. 6.5 D

Topic 6 FIFO Queue Page 12 of 23


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

Three dequeue operations


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

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

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:

Topic 6 FIFO Queue Page 13 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;
}
}

Topic 6 FIFO Queue Page 14 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

Topic 6 FIFO Queue Page 15 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.

dequeued elements.

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

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

Topic 6 FIFO Queue Page 16 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 stack


W
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.

Topic 6 FIFO Queue Page 17 of 23


“Won ton not now” enqueued

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

Rear
Front
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
Topic
FIG.6 6.9
FIFO Queue Page 18 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.
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
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;

Topic 6 FIFO Queue Page 19 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

Topic 6 FIFO Queue Page 20 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();

Topic 6 FIFO Queue Page 21 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.

Topic 6 FIFO Queue Page 22 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

Topic 6 FIFO Queue Page 23 of 23


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.

Topic 7 Linked Lists Page 1 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.

Topic 7 Linked Lists Page 2 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

Topic 7 Linked Lists Page 3 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.

Topic 7 Linked Lists Page 4 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

Topic 7 Linked Lists Page 5 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.

Topic 7 Linked Lists Page 6 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 Stores a reference to
{ an IntList type
//Instance variables object.
private int data;
private IntList Link;

//Chained Constructors
public IntList()
{
this(0);
}

public IntList(int init_data)


{
this(init_data , null);
}

Topic 7 Linked Lists Page 7 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;
}

Topic 7 Linked Lists Page 8 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

Topic 7 Linked Lists Page 9 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

Dummy
Node.

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.

Topic 7 Linked Lists Page 10 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.

Topic 7 Linked Lists Page 11 of 60


Dummy
Node

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.

Topic 7 Linked Lists Page 12 of 60


Dummy
Node

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).

Dummy
Node.

FIG 7.9 E

Topic 7 Linked Lists Page 13 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.

Dummy
Node

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).

Topic 7 Linked Lists Page 14 of 60


Dummy
Node

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

Topic 7 Linked Lists Page 15 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.

Topic 7 Linked Lists Page 16 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

Topic 7 Linked Lists Page 17 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>

Topic 7 Linked Lists Page 18 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;

/**

Topic 7 Linked Lists Page 19 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 item
java.lang.Object 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.

Topic 7 Linked Lists Page 20 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.
*/

Topic 7 Linked Lists Page 21 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 SinglyLinkedList.Node
class

Field Summary
protected front
SinglyLinkedList.Node
protected int numberElements

protected rear
SinglyLinkedList.Node

Constructor Summary
SinglyLinkedList()

Topic 7 Linked Lists Page 22 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 getNode(java.lang.Object value)
SinglyLinkedList.Node 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.

Topic 7 Linked Lists Page 23 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 nodeBefore(SinglyLinkedList.Node someNode)
SinglyLinkedList.Node 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.

Topic 7 Linked Lists Page 24 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

Topic 7 Linked Lists Page 25 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);

Topic 7 Linked Lists Page 26 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--;
}

Topic 7 Linked Lists Page 27 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);

Topic 7 Linked Lists Page 28 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

Topic 7 Linked Lists Page 29 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.

Topic 7 Linked Lists Page 30 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).

Topic 7 Linked Lists Page 31 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

Topic 7 Linked Lists Page 32 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

Topic 7 Linked Lists Page 33 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).

Topic 7 Linked Lists Page 34 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).

Topic 7 Linked Lists Page 35 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).

Topic 7 Linked Lists Page 36 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).

Topic 7 Linked Lists Page 37 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

Topic 7 Linked Lists Page 38 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).

Topic 7 Linked Lists Page 39 of 60


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

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).

Topic 7 Linked Lists Page 40 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

Topic 7 Linked Lists Page 41 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).

Topic 7 Linked Lists Page 42 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).

Topic 7 Linked Lists Page 43 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

Topic 7 Linked Lists Page 44 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.

• 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 ( ).

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

Method elements ( )
Vector V
declared.
public Iterator elements( ){
Vector V = new Vector( );
Node current = front; All SinglyLinkedList
while(current != null) { elements loaded into the
V.addElement(current.item); Vector V
current = current.next;
}
return V.iterator( ); Method iterator returns an
Iterator to Vector V.
}

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

Topic 7 Linked Lists Page 45 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).

Topic 7 Linked Lists Page 46 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).

Topic 7 Linked Lists Page 47 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).

Topic 7 Linked Lists Page 48 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).

Topic 7 Linked Lists Page 49 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.pp
t

Topic 7 Linked Lists Page 50 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).

Topic 7 Linked Lists Page 51 of 60


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

Topic 7 Linked Lists Page 52 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.

Topic 7 Linked Lists Page 53 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).

Topic 7 Linked Lists Page 54 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).

Topic 7 Linked Lists Page 55 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 = ""; Adding to
boolean done = false;
the front
while (!done)
{ of the list.
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;

Topic 7 Linked Lists Page 56 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

Topic 7 Linked Lists Page 57 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.

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

User enters strings:


FIG. 7.45A In, Santa Monica,
College, on, October, 4,
2000 and then clicks
cancel.

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

FIG. 7.45C

Topic 7 Linked Lists Page 58 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.

Topic 7 Linked Lists Page 59 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).

Topic 7 Linked Lists Page 60 of 60


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)

Linked Stacks and Queues Page 1 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);
}

Linked Stacks and Queues Page 2 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;

Linked Stacks and Queues Page 3 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
{

Linked Stacks and Queues Page 4 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).
User enters
Santa Monica
College
October
2002

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);

Linked Stacks and Queues Page 5 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

Linked Stacks and Queues Page 6 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).

Linked Stacks and Queues Page 7 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

Linked Stacks and Queues Page 8 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.

Linked Stacks and Queues Page 9 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)

Linked Stacks and Queues Page 10 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;

Linked Stacks and Queues Page 11 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

3000
2000
1000
3 null
2 3000
1 2000
item next
item next
item next
1000

front
3000

numItems = 3 rear

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.

Linked Stacks and Queues Page 12 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.

Linked Stacks and Queues Page 13 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).

Linked Stacks and Queues Page 14 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.

Linked Stacks and Queues Page 15 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).

Linked Stacks and Queues Page 16 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).

Linked Stacks and Queues Page 17 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).

Linked Stacks and Queues Page 18 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).

Linked Stacks and Queues Page 19 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).

Linked Stacks and Queues Page 20 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).

Linked Stacks and Queues Page 21 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.

Linked Stacks and Queues Page 22 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

Linked Stacks and Queues Page 23 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).

User enters
A October 21
2002 Day in
California

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

Linked Stacks and Queues Page 24 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

Linked Stacks and Queues Page 25 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

Linked Stacks and Queues Page 26 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++;
}

Linked Stacks and Queues Page 27 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;
}

Linked Stacks and Queues Page 28 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.

Linked Stacks and Queues Page 29 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();

Linked Stacks and Queues Page 30 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.

Linked Stacks and Queues Page 31 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).

Linked Stacks and Queues Page 32 of 42


Broken
links.

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
{

Linked Stacks and Queues Page 33 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

Linked Stacks and Queues Page 34 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

Linked Stacks and Queues Page 35 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.

Linked Stacks and Queues Page 36 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++;
}

Linked Stacks and Queues Page 37 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;

Linked Stacks and Queues Page 38 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)

Linked Stacks and Queues Page 39 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
{

Linked Stacks and Queues Page 40 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

Linked Stacks and Queues Page 41 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

Linked Stacks and Queues Page 42 of 42


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
}

Topic 9 Recursion Page 1 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.
Only executed for
public int factorial(int Num)
non-sentinel values of
{
if(Num > 0) Num
return Num * factorial(Num – 1);
else
return 1; Argument to the method factorial ( ) reduces by
} one with every recursive call, thereby each time
bringing it closer to the sentinel value of zero.

Listing 9.3
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.

Topic 9 Recursion Page 2 of 22


Additional Terminology:
The following terms are often used to describe recursion:

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).

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.

Topic 9 Recursion Page 3 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).

Topic 9 Recursion Page 4 of 22


First level
call. Num =
4

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

Topic 9 Recursion Page 5 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.

Topic 9 Recursion Page 6 of 22


FIG. 9.5

Going in at level 3.
Num = 2

Topic 9 Recursion Page 7 of 22


FIG. 9.6

Going in at level 4.
Num = 1.

Topic 9 Recursion Page 8 of 22


FIG. 9.7

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

Topic 9 Recursion Page 9 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

Program control is on last line of


function factorial. It is ready to
back out of level 5, with a return
value of one.

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).

Topic 9 Recursion Page 10 of 22


FIG. 9.11

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.

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.

Topic 9 Recursion Page 11 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.

Topic 9 Recursion Page 12 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.

Topic 9 Recursion Page 13 of 22


FIG. 9.14

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.

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.

Topic 9 Recursion Page 14 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.

Topic 9 Recursion Page 15 of 22


“backing out “
from recursion
“Going in” into
recursion

Recursion
level

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( ))
{ Listing 9.6
System.out.println(iter.next( ) );
}

Topic 9 Recursion Page 16 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.

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.

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;

Topic 9 Recursion Page 17 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

Topic 9 Recursion Page 18 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 ( ).

IOP1/2 = 0)
IOP(3/2 = 1)
IOP(7)
IOP(7/2 = 3)
prints
prints prints Index = 1
Index = 7 Recursion
Index = 3 stops
IOP(1/2 = 0)
IOP(7/2 = 3)
IOP(1/2 = 0)
IOP(3/2 = 1)

prints
Index = 1

IOP(1/2 = 0)

FIG. 9.17

Results are similar to the


branch on right. It will
print 1, 3, 1 .. in that order

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

Topic 9 Recursion Page 19 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.

IOP1/2 = 0)
IOP(3/2 = 1)
IOP(7)
IOP(7/2 = 3)
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

IOP1/2 = 0)
IOP(3/2 = 1)
IOP(7)
IOP(7/2 = 3)
prints
prints prints Index = 1
Index = 7 Index = 3
IOP(1/2 = 0)

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

Topic 9 Recursion Page 20 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.

IOP(7/2 = 3)

prints
Index = 3

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

prints
Index = 1

IOP(1/2 = 0)

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)
IOP(7/2 = 3)

prints prints
Index = 7 Index = 3
FIG. 9.21
IOP(7/2 = 3)

Topic 9 Recursion Page 21 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);
}
}

Topic 9 Recursion Page 22 of 22


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)

Topic 10 recursion Page 1 of 63


Root

Level 1
FIG. 10.1
Level 2

Level 3

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.

FIG. 10.2

Topic 10 recursion Page 2 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.

Topic 10 recursion Page 3 of 63


G eneric Tree
A

B D
C
FIG. 10.4

E F G H J

Definitions and Terminology:


We use the Figure 10.4 to define various terms used to study the tree data structure.

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.

Topic 10 recursion Page 4 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

Topic 10 recursion Page 5 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.
Right
Left child Binary Tree child of A
of A
A

null
B C
FIG. 10.6
D
E F Leaf nodes.
All leaf nodes
have two null
G H J children.

Topic 10 recursion Page 6 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.

Topic 10 recursion Page 7 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.

Topic 10 recursion Page 8 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

Topic 10 recursion Page 9 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:

Topic 10 recursion Page 10 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.
DBGEACHFJ
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

Topic 10 recursion Page 11 of 63


IOT(A) IOT(null)
IOT(sub B)
IOT(sub D)
Visit or Visit or
Print A print B Visit or
Print D

IOT( sub E)
IOT(sub G) IOT(null)
IOT( sub C) IOT(null)

Visit or IOT(null)
Visit or Print E Visit or
Print C Print G

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

Visit or
Print F Visit or
Print H
inOrderTravel (left sub-tree)
IOT( sub J)
IOT(null)
Visit the node
inOrderTravel (right sub-tree)

Visit or
IOT(null)
Print J DBGEACHFJ

IOT(null)
Binary Tree
A

null
B C

D
E F

G H J

FIG. 10.7 In-order traverse of a binary tree

Topic 10 recursion Page 12 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.

Topic 10 recursion Page 13 of 63


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

Binary Tree
A

null
B C

D
E F

G H J

Post-order visit Sequence:


DGEBHJFCA

Topic 10 recursion Page 14 of 63


POT(A)

POT(B)

POT(D) POT(E)

null POT(G)
null

null
Visit/print D null

null
Visit/print G

Visit/print E

Visit/print B

POT(C)

null POT(F)

POT(H)
POT(J)

null null
null null

Visit/print H
Visit/print J

Visit/print F

Visit/print C
FIG. 10.8 B
Topic 10 recursion Page 15 of 63
Visit/print A
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.

Topic 10 recursion Page 16 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

Visit the node


preOrderTravel (left sub-tree)
preOrderTravel (right sub-tree)

Binary Tree
A Pre-order Visit sequence:
ABDEGCFHJ
null
B C

D
E F
FIG. 10.9 A
G H J

Topic 10 recursion Page 17 of 63


Visit or print A
POT(A)

POT(B
Visit or print B
POT(E
POT(D)
Visit or print E
POT(G)
Visit or print D

null null Visit or print G

null
null

null

POT(C

POT(F)
Visit or print C

Visit/print F
POT(J)
null POT(H)

Visit/print H Visit/print J

null null null


null

FIG. 10.9 B

Topic 10 recursion Page 18 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:


ABDEGCFHJ

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.

Topic 10 recursion Page 19 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.

Topic 10 recursion Page 20 of 63


BuildingBinarySearch
Tree.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).

Added the root


node. Ready to
add node = 40.

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).

Added nodes 50
and 40

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).

Topic 10 recursion Page 21 of 63


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

FIG. 10.10C

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).
Inserted all
items
except 90
and 15.

FIG. 10.10 D

Topic 10 recursion Page 22 of 63


Tree
complete

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) contents
java.lang.Comparable contents is the object in the SearchTreeNode, which
contains the data part of the node.
(package private) left
SearchTreeNode left is the self-referential reference pointing to the
left child of the node.
(package private) right
SearchTreeNode right is the self-referential reference pointing to the
right child of the node.

Constructor Summary
(package SearchTreeNode()
private) The argument-less constructor initializes all fields to null creating a
null tree.
(package SearchTreeNode(java.lang.Comparable obj)
private) The single argument constructor takes an object of type

Topic 10 recursion Page 23 of 63


java.lang.Comparable and initializes the contents to it.
(package SearchTreeNode(java.lang.Comparable obj,
private) SearchTreeNode init_left)
Two argument constructor initializes the contents to the first
argument, and reference left to the second argument.
(package SearchTreeNode(java.lang.Comparable obj,
private) 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) isInternal()
boolean 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

Topic 10 recursion Page 24 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

Topic 10 recursion Page 25 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 v
java.util.Vector 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 buildVector(SearchTreeNode node)
abstract void 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


{

Topic 10 recursion Page 26 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

Topic 10 recursion Page 27 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 buildVector(SearchTreeNode node)
void 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

Topic 10 recursion Page 28 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

Topic 10 recursion Page 29 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.

Topic 10 recursion Page 30 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 root
SearchTreeNode The field root is the root node of BinarySearchTree.

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

Topic 10 recursion Page 31 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 tree
or sub-tree.
boolean contains(java.lang.Comparable obj)
Finds whether the BinarySearchTree contains the
Object obj.
private deleteNode(SearchTreeNode node,
SearchTreeNode 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 deleteRightMost(SearchTreeNode node)
SearchTreeNode 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 insertNode(SearchTreeNode node,
SearchTreeNode 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)

Topic 10 recursion Page 32 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 rightMost(SearchTreeNode node)
java.lang.Comparable 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()
{

Topic 10 recursion Page 33 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

Topic 10 recursion Page 34 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;
}
/**

Topic 10 recursion Page 35 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

Topic 10 recursion Page 36 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.
*/

Topic 10 recursion Page 37 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;
}

Topic 10 recursion Page 38 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

Topic 10 recursion Page 39 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 insertNode(SearchTreeNode node,
SearchTreeNode 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)
Block #1
{
if(node == null)
return new SearchTreeNode(item);
else if(item.compareTo(node.contents)<0) Block #2
{
node.left = insertNode(node.left,item);
return node;
} Block #3
else if(item.compareTo(node.contents)>0)
{
node.right = insertNode(node.right,item);
return node;
} Block #4
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.

Topic 10 recursion Page 40 of 63


MethodinsertNode.p
pt

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).

Topic 10 recursion Page 41 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

Topic 10 recursion Page 42 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.

300>260. Execute
root = IN (node-300, 260); block#2
return modified tree-300 or
root
200<260. Execute
block#3
300.left = node-200 = IN (node-200, 260);
return modified subtree-200

250<260. Execute
block#3 200.right = node-250 = IN (node-250, 260);
return modified subtree-250

275>260. Execute
block#2
250.right = node-275 = IN (node-275, 260);
return modified subtree-275

Base case
reached.
260 gets
275.left = IN (null, 260); attached
275.left = node-260 as left
node to
node-275.

Backing out
Going in

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);

Topic 10 recursion Page 43 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.

Topic 10 recursion Page 44 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.

Topic 10 recursion Page 45 of 63


NULL
Temp
Temp

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.

Topic 10 recursion Page 46 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

Topic 10 recursion Page 47 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.

FIG. 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).

Topic 10 recursion Page 48 of 63


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

FIG. 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 deleteNode(SearchTreeNode node,
SearchTreeNode 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 rightMost(SearchTreeNode node)


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

private deleteRightMost(SearchTreeNode node)


SearchTreeNode 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.

Topic 10 recursion Page 49 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) Block #1
throw new NoSuchElementException(
"remove:: obj not in binary search tree.");
if(item.compareTo(node.contents)<0)
node.left = deleteNode(node.left,item); Block #2
else if(item.compareTo(node.contents)>0)
node.right = deleteNode(node.right,item);
else if(item.compareTo(node.contents) == 0) Block #3
{
if(node.left == null)
node = node.right; Block #4
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

Topic 10 recursion Page 50 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.p
pt

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

Topic 10 recursion Page 51 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).

Topic 10 recursion Page 52 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).

FIG. 10. 26

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

Topic 10 recursion Page 53 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.

Topic 10 recursion Page 54 of 63


Right node of 200
replaceWithValue = RM (node-200)
is 250.
Return 275 as the replaceWithValue

Right node of 250


is 275.
Return RM (node-250)
Return 275

Right node of 275


Return RM (node-275) is null
Return 275

Base case
reached.
Return RM (null) 260 gets
return 275 attached
as left
node to
node-275.

Going in Backing out

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).

Topic 10 recursion Page 55 of 63


Right node of 200
Node-200 = DRM (node-200)
is 250.
Return new reference to node 200

Right node of 250


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

Right node of 275 is


Node250.right = DRM (node-275) null. Base case
Return node-260 reached.

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.

Topic 10 recursion Page 56 of 63


D(300)

D(200)

D(250)
D(100)

D(275)
null
null
null
D(260)
Set Node 100 to
null
null
null null

Set 260 to null

Set 275 to null

Set node 250 to null

Set node 200 to null

D(400)

null
null

Set node 400 to null

FIG. 10.29
Set node300 to null

Topic 10 recursion Page 57 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:

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).

Topic 10 recursion Page 58 of 63


100 Linear Binary Search Tree
200

250

260

275
300
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.

Topic 10 recursion Page 59 of 63


Balanced Binary Search Tree

260

200 300

275 400
100 250

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

Topic 10 recursion Page 60 of 63


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

Both return
100, 250, 275,
CPL(node-260,0) 5 each
and 400 are all
leaf nodes.
Returns
10
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)

CPL(node-100,2), CPL(node-250,2) , CPL(node-


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

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.

Topic 10 recursion Page 61 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.

Topic 10 recursion Page 62 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.

Topic 10 recursion Page 63 of 63


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.

CS 20 B : Topic 11 Hashing Page 1 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:

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.

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.

CS 20 B : Topic 11 Hashing Page 2 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

CS 20 B : Topic 11 Hashing Page 3 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 List
static Hashable[] List is the static array of Hashable items.

private max_items
static int max_items is the maximum number of items in the hash table

private Name
java.lang.String Name field stores the name of the employee.

private num_items
static int 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.

CS 20 B : Topic 11 Hashing Page 4 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

CS 20 B : Topic 11 Hashing Page 5 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( )


{

CS 20 B : Topic 11 Hashing Page 6 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;

CS 20 B : Topic 11 Hashing Page 7 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;

CS 20 B : Topic 11 Hashing Page 8 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);
}

CS 20 B : Topic 11 Hashing Page 9 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

CS 20 B : Topic 11 Hashing Page 10 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.

User enters the


Employee ID
number.

FIG. 11.3 A

CS 20 B : Topic 11 Hashing Page 11 of 61


User enters the
Employee
name.

FIG. 11.3 B

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

FIG. 11.3 C

After the data of Table


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

FIG. 11.3 D

CS 20 B : Topic 11 Hashing Page 12 of 61


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

Search Results
displayed.

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

CS 20 B : Topic 11 Hashing Page 13 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.

CS 20 B : Topic 11 Hashing Page 14 of 61


FIG. 11.5

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

CS 20 B : Topic 11 Hashing Page 15 of 61


FIG. 11.6

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).

CS 20 B : Topic 11 Hashing Page 16 of 61


FIG. 11.7

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.

CS 20 B : Topic 11 Hashing Page 17 of 61


FIG. 11.8

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.

CS 20 B : Topic 11 Hashing Page 18 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

CS 20 B : Topic 11 Hashing Page 19 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;
}

CS 20 B : Topic 11 Hashing Page 20 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;
}

CS 20 B : Topic 11 Hashing Page 21 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;

CS 20 B : Topic 11 Hashing Page 22 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

CS 20 B : Topic 11 Hashing Page 23 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

CS 20 B : Topic 11 Hashing Page 24 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.

CS 20 B : Topic 11 Hashing Page 25 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%.

CS 20 B : Topic 11 Hashing Page 26 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).

CS 20 B : Topic 11 Hashing Page 27 of 61


FIG. 11.12 Clustering

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.

CS 20 B : Topic 11 Hashing Page 28 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.

•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

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

CS 20 B : Topic 11 Hashing Page 29 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

CS 20 B : Topic 11 Hashing Page 30 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.

CS 20 B : Topic 11 Hashing Page 31 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.

CS 20 B : Topic 11 Hashing Page 32 of 61


protected rehash()
void 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).

CS 20 B : Topic 11 Hashing Page 33 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.

CS 20 B : Topic 11 Hashing Page 34 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

CS 20 B : Topic 11 Hashing Page 35 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

CS 20 B : Topic 11 Hashing Page 36 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");

CS 20 B : Topic 11 Hashing Page 37 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.");

CS 20 B : Topic 11 Hashing Page 38 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;
}

CS 20 B : Topic 11 Hashing Page 39 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

CS 20 B : Topic 11 Hashing Page 40 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

CS 20 B : Topic 11 Hashing Page 41 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

CS 20 B : Topic 11 Hashing Page 42 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.

CS 20 B : Topic 11 Hashing Page 43 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 X
Data(variable).hashCode( )
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.

CS 20 B : Topic 11 Hashing Page 44 of 61


private Name
java.lang.String 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 Data
EmployeeKey Data is the Employee Key, which includes the employee name
and ID number.

private phone

CS 20 B : Topic 11 Hashing Page 45 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.

CS 20 B : Topic 11 Hashing Page 46 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);

CS 20 B : Topic 11 Hashing Page 47 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);

CS 20 B : Topic 11 Hashing Page 48 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).

CS 20 B : Topic 11 Hashing Page 49 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;

CS 20 B : Topic 11 Hashing Page 50 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 = "";

CS 20 B : Topic 11 Hashing Page 51 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 = "";

CS 20 B : Topic 11 Hashing Page 52 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));

CS 20 B : Topic 11 Hashing Page 53 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.

CS 20 B : Topic 11 Hashing Page 54 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+ " ";
}
}

CS 20 B : Topic 11 Hashing Page 55 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)
{

CS 20 B : Topic 11 Hashing Page 56 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

CS 20 B : Topic 11 Hashing Page 57 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)
{

CS 20 B : Topic 11 Hashing Page 58 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;
}

CS 20 B : Topic 11 Hashing Page 59 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

CS 20 B : Topic 11 Hashing Page 60 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

CS 20 B : Topic 11 Hashing Page 61 of 61


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).

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 1 of 87


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.

For the algorithm ALG1, three efficiency related scenarios are given in Table 12.1
below.
Case Array index at which the Time to search is
Key is located 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
Average 1/2 For large N, negligible in
case comparison to N.
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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 2 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.

The term N4 will increase


most rapidly compared
to other terms.

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
CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 3 of 87
or confirmed that the value is not in the array. The algorithm for the binary search
ALG2 is given below.

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

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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 4 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:
6
Assume X = log2 (10 ). 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
CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 5 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 6 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.
*/

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 7 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?");
}

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 8 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);
}

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 9 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
{

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 10 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 11 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 52500
2000000 60000 60000 50000 50000 55000
3000000 110000 50000 110000 110000 95000
4000000 110000 110000 110000 170000 125000
5000000 170000 160000 160000 160000 162500
6000000 220000 220000 220000 160000 205000
7000000 220000 220000 220000 220000 220000
8000000 220000 220000 220000 220000 220000
9000000 280000 280000 320000 270000 287500
10000000 330000 330000 330000 280000 317500
11000000 320000 390000 330000 330000 342500
Table 12.44

4
All measurements are not shown in the table 12.4.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 12 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.
Average Search Time in Microseconds For Linear Seraching

4.00E+05
Linear Search Time = 0.0302* Array Size + 7954.5
s 3.50E+05
d
n
o
c 3.00E+05
e
s
o
r 2.50E+05
c
i
m
n
i 2.00E+05
e
m
i 1.50E+05
T
h
c
r 1.00E+05 Average
a Linear (Average)
e
S 5.00E+04

0.00E+00
0.00E+00 2.00E+06 4.00E+06 6.00E+06 8.00E+06 1.00E+07 1.20E+07
Array Size

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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 13 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 14 of 87


n n log2 n n log2 n n2 2n

10 0.01 0.003 µs 0.03 µs 0.1µs 1micro-


µs second
20 0.02 0.004 µs 0.09 µs 0.4 µs 1 ms
µs
30 0.03 0.005 µs 0.15 µs 0.9 µs 1s
µs
40 0.04 0.005 µs 0.21 µs 1.6 µs 18.3 min
µs
50 0.05 0.006 µs 0.28 µs 2.5 µs 13 days
µs
100 0.10 0.007 µs 0.66 µs 10 µs 4 x 1013
µs Years

1000 1.00 0.010 µs 9.96 µs 1 ms


µs
10,000 10 µs 0.013 µs 130 µs 100 ms

100,000 0.10 0.017 µs 1.67 ms 10 s


µs
1,000,000 0.01s 0.020 µs 19.92 ms 16.7 m

10 0.10s 0.023 µs 0.23 s 1.16


Million days
100 1.00s 0.027 µs 2.66 s 115.7
Million days
Table 12.7

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 15 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.
Log[O (N )]
T heoretica l B ig O Plots
Log[O (2N )]
Log[O (N *N )]
16 Log[O (N *N *N )]
Log[O (N *N *N *N )]
14
Logarithm of Running Time

12

10

0
0 2000 4000 6000 8000 10000
Input Size (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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 16 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 17 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 18 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 19 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 20 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 21 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 22 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:

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 23 of 87


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;

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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 24 of 87


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]);
}
}
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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 25 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.
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.
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);
}
}
}

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 26 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).

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 27 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 28 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
CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 29 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 30 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 31 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 32 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 33 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 34 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).

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 35 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 36 of 87


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

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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 37 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 38 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 39 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
CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 40 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 41 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).
CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 42 of 87
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
}

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;

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 43 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 44 of 87


FIG. 12.16
The algorithm for method merge is given as follows:(ALG8).

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 45 of 87


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.

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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 46 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++;

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 47 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).

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 48 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).

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 49 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 50 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 51 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 52 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).

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 53 of 87


S1
Pivot
S2
Unknown
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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 54 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 55 of 87


FIG. 12.26
Figure 12.27 shows the process more clearly.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 56 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 57 of 87


entire execution. The pseudo-code for the method partition can now be written
easily as follows (ALG7).

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.
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);
}

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 58 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 59 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.");

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 60 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 61 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 62 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 63 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 = Position of left child = Position of right child
index (index*2 ) + 1 =(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”.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 64 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 65 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 66 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 67 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)

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 68 of 87


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)

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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 69 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.

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)

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;
}

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 70 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.");

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 71 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 72 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.

Must pad the numbers with leading


zero as needed, so that all have the
same number of digits in them.

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).

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 73 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 74 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 75 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:

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 76 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).

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)

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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 77 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).

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.

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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 78 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 79 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++)


{

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 80 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 81 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.

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 82 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)

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 83 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 84 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)
{

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 85 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

CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 86 of 87


CS20B: Topic 12: Analysis of Algorithms 1/10/2008 Page 87 of 87

Anda mungkin juga menyukai