Only this pageAll pages
Powered by GitBook
1 of 61

Data Structures and Algorithms in Java

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

0.2. Quiz

Quiz 0: Getting Started

Coding

Right-click on the src/main/java directory under the project and create the package edu.emory.cs.utils. Right-click on the utils package and create the Java class Utils:

  • Add Utils.java to git.

  • Add the following methods to the Utils class:

static public int getMiddleIndex(int beginIndex, int endIndex) {
    return beginIndex + (endIndex - beginIndex) / 2;
}

static public void main(String[] args) {
    System.out.println(getMiddleIndex(0, 10));
}

Run the program by clicking [Run -> Run]. If you see 5 on the output pane, your program runs successfully.

Testing

Open build.gradle and add the following configurations (if not already), which would allow you to perform JUnit Testing:

test {
    useJUnitPlatform()
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
}

Right-click on the src/test/java directory under the project and create the package edu.emory.cs.utils. Right-click on the utils package and create the Java class UtilsTest:

  • Add UtilsTest.java to Git.

  • Add the following method to the UtilsTest class. Make sure to include all imports:

package edu.emory.cs.utils;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

@Test
public void getMiddleIndexTest() {
    assertEquals(5, Utils.getMiddleIndex(0, 10));
}

Run the test by clicking [Run -> Run]. If you see the test passed, your unit test runs successfully.

Submission

  1. Add the instructors as collaborators in your GitHub repository:

  • Jinho Choi: jdchoi77

  • Peilin Wu: qualidea1217

  • Jeongrok Yu: jeongrok

  • Zinc Zhao: ZincZhao

2. Commit and push the following to your GitHub repository:

  • gradle/wrapper/gradle-wrapper.jar

  • gradle/wrapper/gradle-wrapper.properties

  • .gitignore

  • build.gradle

  • gradlew

  • gradlew.bat

  • settings.gradle

  • src/main/java/edu/emory/cs/utils/Utils.java

  • src/test/java/edu/emory/cs/utils/UtilsTest.java

3. Submit the URL of your GitHub repository to Canvas.

Preface

Jinho D. Choi

This is an advanced programming course in Computer Science that teaches how to design efficient structures and algorithms to process big data and methods to benchmark their performance for large-scale computing. Topics cover data structures such as priority queues, binary trees, tries, and graphs and their applications in constructing algorithms such as sorting, searching, balancing, traversing, and spanning. Advanced topics such as network flow and dynamic programming are also discussed. Throughout this course, students are expected to

  • Have a deep conceptual understanding of various data structures and algorithms.

  • Implement their conceptual understanding in a programming language.

  • Explore the most effective structures and algorithms for given tasks.

  • Properly assess the quality of their implementations.

There are topical quizzes and homework assignments that require sufficient skills in Java programming, Git version control, Gradle software project management, and scientific writing. Intermediate-level Java programming is a prerequisite of this course.

  • Syllabus

  • Schedule

Schedule

Spring 2023

Date
Topic
Assignment

01/11

01/16

MLK Holiday

01/18

01/23

(Continue)

01/25

(Continue)

01/30

02/01

(Continue)

02/06

02/08

(Continue)

02/13

(Continue)

02/15

02/20

(Continue)

02/22

(Continue)

02/27

03/01

(Continue)

03/06

Spring Break

03/08

Spring Break

03/13

03/15

03/20

(Continue)

03/22

03/27

(Continue)

03/29

(Continue)

04/03

04/05

(Continue)

04/10

(Continue)

04/12

04/17

(Continue)

04/19

(Continue)

04/24

Review

0. Getting Started

This chapter helps you set up the development environment for this course.

Contents

Resources

  • Main:

  • Test:

Many Java developers commonly adapt to the environment described in this section. Thus, it is essential to familiarize yourself with this setup.

Syllabus

Spring 2023

General

  • Book:

  • GitHub:

  • Time: MW 11:30AM - 12:45PM

  • Location: Atwood 240

Instructors

  • Associate Professor of Computer Science Office Hours → MW 4PM - 5:30PM, MSC W302F

  • BS in Computer Science; BA in Physics and Astronomy Office Hours → TuTh 10:30AM - 12PM, MSC E308

  • BS in Computer Science and Economics Office Hours -> MW 2:30PM - 4PM, MSC E308

  • BS in Computer Science; BA in Music Office Hours → TuTh 1PM - 2:30PM, MSC E308

Grading

  • 1 + 10 topical quizzes: 70%

  • 3 homework assignments: 30%

  • Your work is governed by the . Honor code violations (e.g., copies from any source, including colleagues and internet sites) will be referred to the .

  • Excuses for exam absence/rescheduling and other serious personal events (health, family, personal related, etc.) that affect course performance must be accompanied by a letter from the .

Notes

  • For every topic, one quiz will be assigned to check if you keep up with the materials.

  • Homework assignments assess conceptual understanding, programming ability, and analytical writing skills relevant to this course.

  • All quizzes and assignments must be submitted individually. Discussions are allowed; however, your work must be original.

  • Late submissions within a week will be accepted with a grading penalty of 15% and will not be accepted once the solutions are discussed in class.

0.1. Environment Setup

Development kit, version control system, integrated development environment, and project management for Java programming.

Development Kit

Install the latest version of the (JDK) on your local machine:

  • The required version: 17.x.x (or higher)

Although Java 17 is not the most recent version, it is the latest long-term support (LTS) release, which is preferred.

Version Control

Install Git using any of the following instructions:

Run the following commands on a terminal by replacing user.email and user.name with your email address and name:

Integrated Development Environment

Install the latest version of on your local machine:

  • The recommended version: 2022.3.x (Ultimate Edition)

  • Apply for the with your school email address to use the ultimate version.

Even if you already have an IDE that you are familiar with for Java programming, we strongly recommend you use IntelliJ because provides IDEs for many popular programming languages with similar interfaces, which makes it easier for you to adapt.

Project Management

Launch IntelliJ and create a project by clicking the [New Project] button at the top:

  • Name: dsa-java

  • Location: local_path/dsa-java

  • Check "Create Git repository"

  • Language: Java

  • Build system: Gradle

  • JDK: 17

  • Gradle DSL: Groovy

  • Uncheck "Add sample code"

  • Advanced Settings:

    • GroupId: edu.emory.cs

    • ArtifactId: dsa-java

For JDK, you should be able to see version 17 if it is properly installed. If you cannot find the version, click [Add JDK] and select the following directory.

  • Windows: C:\Program Files\Java\jdk-17.x.x

  • Mac: /Library/Java/JavaVirtualMachines/jdk-17.x.x.jdk

Open and make sure distributionUrl indicates the latest version of Gradle:

Click [Settings - Build, Execution, Deployment] on the menu:

  • Click [Build Tools - Gradle] and set Gradle JVM to 17.

  • Click [Compiler - Java Compiler] and set Project bytecode version to 17.

Click [File - Project Structure] and select [Project Settings]:

  • Click [Project Settings - Project] and set SDK to 17 and Project language level to SDK default.

  • Click [Project Settings - Modules - Dependencies] and set Module SDK to 17.

  • Go to [Platform Settings - SDKs] and select 17.

Open and make sure sourceCompatibility and targetCompatibility are set to java version 17 (add the following configurations if they do not exist already):

Lastly, check mavenCentral() is configured as a repository in your build.gradle:

There is another popular build tool called . However, we will use Gradle for this course because it is faster and simpler to build a Java-based project.

GitHub Integration

To integrate the project with your repository, click [Settings]:

  • Choose [Version Control - Github] on the left pane.

  • Click [+] and login with your GitHub ID and password.

  • If you use two-factor authentication, log in with your .

Create under the project and add the following contents:

Click [Git - GitHub - Share Project on Github] and create a repository:

  • Make sure to check private.

  • Repository name: dsa-java

  • Remote: origin

  • Description: Data Structures and Algorithms in Java

Add all files and make the initial commit. Check if the repository is created under your GitHub account: https://github.com/your_id/dsa-java.

We recommend you create a GitHub account with your school email address, allowing you to add unlimited collaborators to the repository.

0. Getting Started
Quiz 0
1. Java Essentials
Quiz 1
2. Priority Queues
Quiz 2
3. Soring Algorithms
Homework 1
Quiz 3
4. Binary Search Trees
Quiz 4
5. Tries
Quiz 5
Homework 2
6. Disjoint Sets
Quiz 6
7. Graph
Quiz 7
8. Minimum Spanning Trees
Homework 3
Quiz 8
9. Network Flow
Quiz 9
10. Dynamic Programming
Quiz 10
Environment Setup
Quiz
src/main/java/edu/emory/cs/utils/Utils.java
src/test/java/edu/emory/cs/utils/UtilsTest.java
https://emory.gitbook.io/dsa-java/
https://github.com/emory-courses/dsa-java
Jinho Choi
Peilin Wu
Jeongrok Yu
Zinc Zhao
Emory Honor Code
Emory Honor Council
Office of Undergraduate Education
git config --global user.email "your_id@emory.edu"
git config --global user.name "Your Name"
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
compileJava {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}
repositories {
    mavenCentral()
}
/.gradle/
/.idea/
/.vscode/
/build/
Java SE Development Kit
Git Installation Guide by Bitbucket
Git Installation Guide by GitHub
IntelliJ IDEA
academic license
JetBrains
Gradle
gradle/wrapper/gradle-wrapper.properties
build.gradle
Maven
GitHub
personal access token
.gitignore
Most-Used Java IDEs of 2021 - 2021 Java Developer Productivity Report
Large Multi-project Build Times - Gradle vs Maven: Performance Comparison

1. Java Essentials

This chapter explains essential object-oriented programming features in Java to implement data structures and algorithms.

Contents

  1. Abstraction

  2. Implementation

  3. Unit Testing

  4. Quiz

Resources

  • Main: src/main/java/edu/emory/cs/algebraic

  • Test: src/test/java/edu/emory/cs/algebraic

References

  • TIOBE Programming Community Index

  • PYPL Popularity of Programming Language

Please follow every example described in this section. Programming is an act of writing, not reading. By the end of this chapter, you should be able to reproduce the entire codebase yourself from scratch without consulting those examples.

1.1. Abstraction

Different types of objects and inheritances in Java.

Class

A class is a template to instantiate an object.

What is the relationship between a class and an object?

Let us create a class called Numeral that we want to be a super class of all numeral types:

package edu.emory.cs.algebraic;

public class Numeral {
}
  • L1: packageindicates the name of the package that this class belongs to in a hierarchy.

  • L3: public is an access-level modifier.

What are the acceptable access-level modifiers to declare a top-level class?

Let us declare a method, add() , that is an operation expected by all numeral types:

public class Numeral {
    /**
     * Adds `n` to this numeral.
     * @param n the numeral to be added.
     */
    public void add(Numeral n) { /* cannot be implemented */ }
}
  • L4: @param adds a javadoc comment about the parameter.

The issue is that we cannot define the methods unless we know what specific numeral type this class should implement; in other words, it is too abstract to define those methods. Thus, we need to declare Numeral as a type of abstract class.

What are the advantages of havingNumeral as a super class of all numeral types?

There are two types of abstract classes in Java, abstract class and interface.

Can an object be instantiated by an abstract class or an interface?

Interface

Let us define Numeral as an interface:

public interface Numeral {
    void add(Numeral n);
}
  • L2: abstract method

    • All methods in an interface are public that does not need to be explicitly coded.

    • Abstract methods in an interface are declared without their bodies.

Who defines the bodies of the abstract methods?

Let us create a new interface called SignedNumeral that inherits Numeral and adds two methods, flipSign() and subtract():

Can an interface inherit either an abstract class or a regular class?

public interface SignedNumeral extends Numeral {
    /** Flips the sign of this numeral. */
    void flipSign();

    /**
     * Subtracts `n` from this numeral.
     * @param n the numeral to be subtracted.
     */
    default void subtract(Numeral n) {
        n.flipSign();
        add(n);
        n.flipSign();
    }
}
  • L1: extends inherits exactly one class or interface.

  • L9: default allows an interface to define a method with its body (introduced in Java 8).

Can we call add() that is an abstract method without a body in the default method subtract()?

Although the logic of subtract() seems to be correct, n.flipSign() gives a compile error because n is a type of Numeral that does not include flipSign(), which is defined in SignedNumeral that is a subclass of Numeral.

What kind of a compile error does n.flipSign() cause?

There are three ways of handling this error: casting, polymorphism, and generics.

Casting

The first way is to downcast the type of n to SignedNumeral, which forces the compiler to think that n can invoke the flipSign() method:

default void subtract(Numeral n) {
    ((SignedNumeral)n).flipSign();
    add(n);
    ((SignedNumeral)n).flipSign();
}

This removes the compile error; however, it will likely cause a worse kind, a runtime error.

Why is a runtime error worse than a compile error?

Downcasting, although allowed in Java, is generally not recommended unless there is no other way of accomplishing the job without using it.

How can downcasting cause a runtime error in the above case?

Polymorphism

The second way is to change the type of n to SignedNumeral in the parameter setting:

default void subtract(SignedNumeral n) {
    n.flipSign();
    add(n);
    n.flipSign();
}

This seems to solve the issue. Then, what about add() defined in Numeral? Should we change its parameter type to SignedNumeral as well?

It is often the case that you do not have access to change the code in a super class unless you are the author of it. Even if you are the author, changing the code in a super class is not recommended.

Why is it not recommended to change the code in a super class?

How about we override the add() method as follows?

public interface SignedNumeral extends Numeral {
    @Override
    void add(SignedNumeral n);
    ...
  • L2: @Override is a predefined annotation type to indicate the method is overridden.

The annotation @Override gives an error in this case because it is not considered an overriding.

What are the criteria to override a method?

When @Override is discarded, the error goes away and everything seems to be fine:

public interface SignedNumeral extends Numeral {
    void add(SignedNumeral n);
    ...

However, this is considered an overloading, which defines two separate methods for add(), one taking n as Numeral and the other taking it as SignedNumeral. Unfortunately, this would decrease the level of abstraction that we originally desired.

What are good use cases of method overriding and overloading?

Generics

The third way is to use generics, introduced in Java 5:

public interface Numeral<T extends Numeral<T>> {
    void add(T n);
}
  • L1: T is a generic type that is a subtype of Numeral.

    • A generic type can be recursively defined as T extends Numeral<T>.

  • L2: T is considered a viable type in this interface such that it can be used to declare add().

Can we define more than one generic type per interface or class?

The generic type T can be specified in a subclass of Numeral:

public interface SignedNumeral extends Numeral<SignedNumeral> {
    void flipSign();

    default void subtract(SignedNumeral n) {
        n.flipSign();
        add(n);
        n.flipSign();
    }
}
  • L1: T is specified as SignedNumeral.

This would implicitly assign the parameter type of add() as follows:

void add(SignedNumeral n);

The issue is that the implementation of add() may require specific features defined in the subclass that is not available in SignedNumeral. Consider the following subclass inheriting SignedNumeral:

public class LongInteger implements SignedNumeral {
    @Override
    public void flipSign() { /* to be implemented */ }

    @Override
    public void add(SignedNumeral n) { /* to be implemented */ }
}
  • L1: implements inherits multiple interfaces.

  • L2-6: LongInteger is a regular class, so all abstract methods declared in the super classes must be defined in this class.

Since the n is typed to SignedNumeral in L6, it cannot call any method defined in LongInteger, which leads to the same issue addressed in the casting section.

Would the type of n being SignedNumeral an issue for the subtract() method as well?

Thus, SignedNumeral needs to define its own generic type and pass it onto Numeral:

public interface SignedNumeral<T extends SignedNumeral<T>> extends Numeral<T> {
    void flipSign();

    default void subtract(T n) {
        n.flipSign();
        add(n);
        n.flipSign();
    }
}
  • L1: T is a generic type inheriting SignedNumeral, that implies all subclasses of SignedNumeral.

T can be safely passed onto Numeral because if it is a subclass of SignedNumeral, it must be a subclass of Numeral, which is how T is defined in the Numeral class.

Generics are used everywhere in Java, so it is important to understand the core concept of generics and be able to adapt it in your code to make it more modular.

Enum

Let us create an enum class called Sign to represent the "sign" of the numeral:

public enum Sign {
    POSITIVE,
    NEGATIVE;
}
  • All items in an enum have the scope of static and the access-level of public.

  • Items must be delimited by , and ends with ;.

The items in the enum can be assigned with specific values to make them more indicative (e.g., +, -):

public enum Sign {
    POSITIVE('+'),
    NEGATIVE('-');

    private final char value;

    Sign(char value) {
        this.value = value;
    }

    /** @return the value of the corresponding item. */
    public char value() {
        return value;
    }
}
  • L5: final makes this field a constant, not a variable, such that the value cannot be updated later.

  • L8: this points to the object created by this constructor.

  • L11: @return adds a javadoc comment about the return value of this method.

Why should the member field value be private in the above example?

Note that value in L8 indicates the local parameter declared in the constructor whereas value in L13 indicates the member field declared in L5.

Limit of Interface

In SignedNumeral, it would be convenient to have a member field that indicates the sign of the numeral:

public interface SignedNumeral<T extends SignedNumeral<T>> extends Numeral<T> {
    Sign sign = Sign.POSITIVE;
    ...
  • L2: All member fields of an interface are static and public.

Can you declare a member field in an interface without assigning a value?

Given the sign field, it may seem intuitive to define flipSign() as a default method:

/** Flips the sign of this numeral. */
default void flipSign() {
    sign = (sign == Sign.POSITIVE) ? Sign.NEGATIVE : Sign.POSITIVE;
}
  • L3: condition ? A : B is a ternary expression that returns A if the condition is true; otherwise, it returns B.

Is there any advantage of using a ternary operator instead of using a regular if statement?

Unfortunately, this gives a compile error because sign is a constant whose value cannot be reassigned. An interface is not meant to define so many default methods, which were not even allowed before Java 8. For such explicit implementations, it is better to declare SignedNumeral as an abstract class instead.

Abstract Class

Let us turn SignedNumeral into an abstract class:

public abstract class SignedNumeral<T extends SignedNumeral<T>> implements Numeral<T> {
    /** The sign of this numeral. */
    protected Sign sign;

    /**
     * Create a signed numeral.
     * the default sign is {@link Sign#POSITIVE}.
     */
    public SignedNumeral() {
        this(Sign.POSITIVE);
    }

    /**
     * Create a signed numeral.
     * @param sign the sign of this numeral.
     */
    public SignedNumeral(Sign sign) {
        this.sign = sign;
    }
    ...
  • L9: the default constructor with no parameter.

  • L17: another constructor with the sign parameter.

  • L10: this() calls the constructor in L17.

Why calling this(Sign.POSITIVE) in L10 instead of stating this.sign = Sign.POSITIVE?

    ...
    /** @return true if this numeral is positive; otherwise, false. */
    public boolean isPositive() {
        return sign == Sign.POSITIVE;
    }

    /** @return true if this numeral is negative; otherwise, false. */
    public boolean isNegative() {
        return sign == Sign.NEGATIVE;
    }

    /** Flips the sign of this numeral. */
    public void flipSign() {
        sign = isPositive() ? Sign.NEGATIVE : Sign.POSITIVE;
    }
    
    /**
     * Subtracts `n` from this numeral.
     * @param n the numeral to be subtracted.
     */
    public void subtract(T n) {
        n.flipSign(); add(n); n.flipSign();
    }

    /**
     * Multiplies `n` to this numeral.
     * @param n the numeral to be multiplied.
     */
    public abstract void multiply(T n);
}
  • L29: abstract indicates that this is an abstract method.

Member fields and methods in an abstract class can be decorated by any modifiers, which need to be explicitly coded.

Is there anything that is not allowed in an abstract class but allowed in a regular class?

In summary, SignedNumeral includes 2 abstract methods, add() inherited from Numeral, and multiply() declared in this class.

Can you define an abstract class or an interface without declaring an abstract method?

1.4. Quiz

Quiz 1: Java Essentials

Coding

  • Create a class called under the main package that extends the class.

  • Override the method so that the add() method in can handle integers with different signs.

Testing

  • Create the class under the test package.

  • Test the correctness of your LongIntegerQuiz using the unit tests.

  • Add more tests for a more thorough assessment if necessary.

Quizzes

  1. What is the advantage of using Generics?

  2. How do you make the class you define comparable?

  3. What is the advantage of overriding member methods in the Object class?

  4. What kind of methods should be defined as static?

Submission

1. Commit and push everything under the following packages to your GitHub repository:

  • Main:

  • Test:

2. Submit answers to the above to Canvas.

1.3. Unit Testing

LongInteger: unit tests.

Test: LongInteger()

Let us create a testing class called and make a for the constructors:

  • L6: the annotation indicates that this method is used for unit testing.

  • L7: methods used for unit testing must be public.

  • L8: tests the default constructor

    • : compares two parameters where the left parameter is the expected value and the right parameter is the actual value.

  • L12-15: tests the constructor with a string parameter.

  • L18-19: tests the copy constructor.

When should we use import static instead of import?

When you run this test, you see a prompt similar to the followings:

Test: multiply()

Let us define another method for testing the multiply() method:

  • L11-12: a can hold the integer 152415787517146788750190521 (), which is much larger than the maximum value of long that is .

Test: compareTo()

Let us define a method for testing the compareTo() method:

  • : passes if the parameter returns true.

provides an effective way of ensuring the correctness of your program. Making a unit test for every method is standard practice, although this standard is often dismissed due to time pressure.

2.1. Simple Priority Queues

Lazy and eager priority queues.

A priority queue (PQ) is a data structure that supports the following two operations:

  • add(): adds a comparable key to the PQ.

  • remove(): removes the key with the highest (or lowest) priority in the PQ.

A PQ that removes the key with the highest priority is called a maximum PQ (max-PQ), and with the lowest priority is called a minimum PQ (min-PQ).

Does a priority queue need to be sorted at all time to support those two operations? What are the use cases of priority queues?

Abstract Priority Queue

Let us define that is an abstract class to be inherited by all priority queues:

  • L1: declares the type T that is the type of input keys to be stored in this PQ.

    • T must be by its priority.

  • L2: is a comparator that can compare keys of the generic type T.

    • final: must be initialized in every constructor.

    • Comparators: , .

  • L6: the javadoc {@link} hyperlinks to the specified methods.

What are comparable data types in Java? Can you define your own comparator?

Let us define three abstract methods, add(), remove(), and size() in AbstractPriorityQueue:

Given the abstract methods, we can define the regular method isEmpty():

Lazy Priority Queue

Let us define whose core methods satisfy the following conditions:

  • add(): takesto add a key to the PQ.

  • remove(): takes to remove the key with the highest/lowest priority from the PQ.

In other words, all the hard work is done at the last minute when it needs to remove the key.

  • L1: declares T and passes it to its super class, AbstractPriorityQueue.

  • L2: defines a list to store input keys.

  • L17-19: overrides the size() method.

Can you add keys to the member field keys when it is declared as final (a constant)? Why does all constructors in LazyPriorityQueue need to call the super constructor?

We then override the core methods, add() and remove():

  • L6-8: appends a key to the list in .

  • L15-20: removes a key to the list in .

    • L16: edge case handling.

    • L17: finds the max-key in the list using in .

    • L18: removes a key from the list in .

Is ArrayList the best implementation of List for LazyPriorityQueue? Why does remove() in L18 cost ?

Eager Priority Queues

Let us define whose core methods satisfy the following conditions:

  • add(): takesto add a key to the PQ.

  • remove(): takes to remove the key with the highest/lowest priority from the PQ.

In other words, all the hard work is done as soon as a key is added.

What are the situations that LazyPQ is preferred over EagerPQ and vice versa?

  • The implementations of the two constructors and the size() method are identical to the ones in LazyPriorityQueue.

Should we create an abstract class that implements the above code and make it as a super class of LazyPQ and EagerPQ? What level of abstraction is appropriate in object-oriented programming?

We then override the core methods, add() and remove():

  • L6-12: inserts a key to the list in .

    • L8: finds the index of the key to be inserted in the list using binary search in .

    • L10: reverse engineers the return value of .

    • L11: inserts the key at the index position in .

  • L19-21: removes a key from the list in .

What are the worst-case complexities of add() and remove() in LazyPQ and EagerPQ in terms of assignments and comparison?

2. Priority Queues

This chapter discusses different types of priority queues and benchmarks their performance in terms of the worst-case complexities.

Contents

Resources

  • Main:

  • Test:

References

1.2. Implementation

LongInteger: implementation.

We are going to create a class called inheriting SignedNumeral that can store an indefinite size of an integer value beyond the such as int and long.

What is so special about primitive data types in Java?

Java SE provides a similar class called although the implementations of LongIntegerandBigIntegerare completely independent.

Field

Let us declare the member field digits that is an array of bytes holding the values of this integer:

  • L1: LongInteger is passed to specify the generic type T in SignedNumeral.

The i'th dimension of digits is the i'th least significant digit in the integer such that the integer 12345 would be stored as digits = {5, 4, 3, 2, 1}, which makes it convenient to implement the arithmetic methods, add() and multiply().

Is the array of bytes the most efficient way of storing a long integer?

Constructors

Let us define the following three constructors:

  • L2: the default constructor that initializes this integer with 0 by calling the constructor in L20.

  • L10: a copy constructor that initializes this integer with n.

    • super(): calls the corresponding constructor in the super class, SignedNumeral.

    • : creates a new array by copying n.digits.

  • L20: a constructor that initializes this integer with n by passing it to the set() method.

Do the constructors in L10 and L20 call any constructor in the super class?

Arrays.copyOf() is a referenced by the class type Arrays, not an object. Java provides many classes with static methods that are commonly used (e.g., , ).

Can you call non-static methods or fields in the body of a static method?

The static keyword must not be abused to quickly fix compile errors unless it is intended.

Method: set()

Let us define the set() method that takes a string and sets the sign and the value of this integer:

  • L1-7: javadoc comments.

    • L4: this method throws if n is null.

    • L5: this method throws if the format of n is invalid.

  • L10-11: throws the NullPointerException.

  • L14-18: checks the first character of n and sets this.sign using the expression.

    • String member methods: , .

    • yield: returns the value of this switch statement for the condition (introduced in Java 14).

  • L21-30: sets the value of n to this.digits .

    • L23: for-loop can handle multiple variables such as i and j.

    • L24: gets the value of n.charAt(i).

    • L25-28: throws the InvalidParameterException if v is not a digit.

    • L29: stores the value in the reverse order.

    • L27: is a static method in String.

When should we use statements over blocks for error handling and vice versa?

This type of method is called a setter. Java encourages making member fields private and creating getters and setters to access the fields for , which is not necessarily encouraged by other languages.

Method: add()

Let us override the add() method that calls two helper methods:

  • L3-4: adds n to this integer that has the same sign by calling addSameSign().

  • L5-6: adds n to this integer that has a different sign by calling addDifferentSign().

The following shows an implementation of addSameSign() based on the simple arithmetic:

  • L7-9: creates the byte array result by copying values in this integer.

    • L8: the dimension of result can be 1 more than m after the addition.

    • Static methods: , .

  • L12-19: adds n to results (if exists) from the least significant digit.

    • L15-18: pass a carry to the next digit.

  • L22: trims the most significant digit if it is 0.

What are tradeoffs to make the size of result to be m instead of m+1 and vice versa?

The following shows addDifferentSign() that throws :

The implementation of addDifferentSign() is quite similar to addSameSign() although it involves a few more logics. We will leave this as an .

In practice, addSameSign()and addDifferentSign() should be private. We made them protected for exercise purposes.

Method: multiply()

Let us override the multiply() method:

  • L4: sets the sign after the multiplication.

  • L7-15: multiplies n to this integer:

    • L7: the max-dimension of results is digits.length + n.digits.length.

    • L12-13: pass a carry to the next digit.

  • L18-20: trims the most significant digit iteratively if it is 0.

    • L20: ++m increments m before the comparison.

What is the worst-case complexity of the multiply() method?

Method: main()

Let us create a runnable class called that contains the main method:

Can we define the main method in LongInteger instead without creating LongIntegerRun?

  • L2: the parameter args is passed from the command line.

Why does the main method need to be static?

This prints something like the following:

  • [: one-dimensional array.

  • L: the element of this array is an object.

  • java.lang.String: the type of object.

  • d716361: the hash code of this array in hexadecimal.

What is the hash code of an object?

Every object implicitly inherits that defines a few member methods including , which gets called automatically by the println() method to retrieve the string representation of this object. We can use the helper method that gives a more readable representation:

How is the Arrays.toString() method implemented?

Since no argument is passed to the main method at the moment, this prints an empty array:

If you set the arguments to 123 -456 using the [Run - Edit Configurations - Program arguments]setting, it prints the following array:

Given those two arguments, we can create two integers:

This prints something like the following, which are returned by a.toString():

How is the toString() method implemented in the Object class?

Method: toString()

To print a more readable representation, we need to override the toString() method in LongInteger:

  • L5: provides an efficient way of concatenating different data types into one string.

What are the advantages of using StringBuilder instead of concatenating values with the + operator as follows:

Given the overridden method, the above main method now prints the following:

What are the advantages of overriding toString() instead of creating a new method with the same code, and calling the new method to get the string representation of LongInteger?

Method: compareTo()

Java does not allow , so it is not possible to use logical operators to compare the two integers above, a and b:

In fact, any object that is comparable must inherit the interface as follows:

  • L2: LongInteger is passed to Comparable as a generic type.

Is extends always used to inherit a class whereas implements is used to inherit an interface?

The Comparable interface contains one abstract method called that returns a negative value if this object is smaller than n, a positive value if this object is greater than n, and zero if this object equals to n. The compareTo() method must be overridden by the LongInteger class:

The compareAbs() method compares the absolute values of this and n:

  • L7: if digits has more dimensions, its absolute value is greater.

  • L10-13: compares the significant digits iteratively.

Is it safe to use the same variable i to iterate both digits and n.digits?

Once LongInteger properly inherits Comparable by overriding compareTo(), objects instantiated by this class can be compared using many built-in methods.

  • L2: is a specific implementation of the interface .

    • All collections in Java inheriting uses generics.

    • <>: the diamond operator that infers the generic type from its declaration.

  • L11: sorts the list in ascending order using and .

  • L14: sorts the list in descending order using and .

What is the advantage of declaring list as List instead of ArrayList? What kind of sorting algorithm does Collections.sort() use?

The above code prints the following sorted lists:

What would be the case that needs to distinguish -0 from 0?