arrow-left
Only this pageAll pages
gitbookPowered 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

hashtag
Coding

Right-click on the src/main/javaarrow-up-right directory under the project and create the package edu.emory.cs.utilsarrow-up-right. Right-click on the utils package and create the Java class Utilsarrow-up-right:

  • Add Utils.java to git.

  • Add the following methods to the Utils class:

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

hashtag
Testing

Open and add the following configurations (if not already), which would allow you to perform :

Right-click on the directory under the project and create the package . Right-click on the utils package and create the Java class :

  • Add UtilsTest.java to Git.

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

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

hashtag
Submission

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

  • Jinho Choi: jdchoi77

  • Peilin Wu: qualidea1217

  • Jeongrok Yu: jeongrok

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

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

Zinc Zhao: ZincZhao

build.gradlearrow-up-right
  • gradlewarrow-up-right

  • gradlew.batarrow-up-right

  • settings.gradlearrow-up-right

  • src/main/java/edu/emory/cs/utils/Utils.javaarrow-up-right

  • src/test/java/edu/emory/cs/utils/UtilsTest.javaarrow-up-right

  • build.gradlearrow-up-right
    JUnit Testingarrow-up-right
    src/test/javaarrow-up-right
    edu.emory.cs.utilsarrow-up-right
    UtilsTestarrow-up-right
    gradle/wrapper/gradle-wrapper.jararrow-up-right
    gradle/wrapper/gradle-wrapper.propertiesarrow-up-right
    .gitignorearrow-up-right
    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));
    }
    test {
        useJUnitPlatform()
    }
    
    dependencies {
        testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
        testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
    }
    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));
    }

    0. Getting Started

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

    hashtag
    Contents

    1. Environment Setup

    hashtag
    Resources

    • Main:

    • Test:

    circle-info

    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

    hashtag
    General

    • Book: https://emory.gitbook.io/dsa-java/arrow-up-right

    • GitHub:

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

    • Location: Atwood 240

    hashtag
    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

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

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

    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.

    0.1. Environment Setup

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

    hashtag
    Development Kit

    Install the latest version of the Java SE Development Kitarrow-up-right (JDK) on your local machine:

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

    circle-info

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

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

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

    circle-info

    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.

    hashtag
    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"

    circle-info

    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

    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.

    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:

    circle-info

    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.

    hashtag
    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

    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.

    circle-info

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

    1. Java Essentials

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

    hashtag
    Contents

    1. Abstraction

    hashtag
    Resources

    • Main:

    • Test:

    hashtag
    References

    circle-info

    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.

    Quiz
    src/main/java/edu/emory/cs/utils/Utils.javaarrow-up-right
    src/test/java/edu/emory/cs/utils/UtilsTest.javaarrow-up-right

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

    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 Office of Undergraduate Educationarrow-up-right.

    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.

    https://github.com/emory-courses/dsa-javaarrow-up-right
    Jinho Choienvelope
    Peilin Wuenvelope
    Jeongrok Yu envelope
    Emory Honor Codearrow-up-right
    Emory Honor Councilarrow-up-right
    Syllabus
    Schedule
    Implementation
    Unit Testing
    Quiz
    src/main/java/edu/emory/cs/algebraicarrow-up-right
    src/test/java/edu/emory/cs/algebraicarrow-up-right
    TIOBE Programming Community Indexarrow-up-right
    PYPL Popularity of Programming Languagearrow-up-right

    Language: Java

  • Build system: Gradle

  • JDK: 17

  • Gradle DSL: Groovy

  • Uncheck "Add sample code"

  • Advanced Settings:

    • GroupId: edu.emory.cs

    • ArtifactId: dsa-java

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

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

    Description: Data Structures and Algorithms in Java

    Git Installation Guide by Bitbucketarrow-up-right
    Git Installation Guide by GitHubarrow-up-right
    IntelliJ IDEAarrow-up-right
    academic licensearrow-up-right
    JetBrainsarrow-up-right
    Gradlearrow-up-right
    gradle/wrapper/gradle-wrapper.propertiesarrow-up-right
    build.gradlearrow-up-right
    Mavenarrow-up-right
    GitHubarrow-up-right
    personal access tokenarrow-up-right
    .gitignorearrow-up-right
    Most-Used Java IDEs of 2021 - 2021 Java Developer Productivity Reportarrow-up-right
    Large Multi-project Build Times - Gradle vs Maven: Performance Comparisonarrow-up-right
    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/

    Schedule

    Spring 2023

    Date
    Topic
    Assignment

    01/11

    01/16

    MLK Holiday

    01/18

    01/23

    (Continue)

    01/25

    (Continue)

    Quiz 1

    01/30

    2. Priority Queues

    02/01

    (Continue)

    Quiz 2

    02/06

    3. Soring Algorithms

    02/08

    (Continue)

    Homework 1

    02/13

    (Continue)

    Quiz 3

    02/15

    4. Binary Search Trees

    02/20

    (Continue)

    02/22

    (Continue)

    Quiz 4

    02/27

    5. Tries

    03/01

    (Continue)

    Quiz 5

    03/06

    Spring Break

    Homework 2

    03/08

    Spring Break

    03/13

    6. Disjoint Sets

    Quiz 6

    03/15

    7. Graph

    03/20

    (Continue)

    Quiz 7

    03/22

    8. Minimum Spanning Trees

    03/27

    (Continue)

    Homework 3

    03/29

    (Continue)

    Quiz 8

    04/03

    9. Network Flow

    04/05

    (Continue)

    04/10

    (Continue)

    Quiz 9

    04/12

    10. Dynamic Programming

    04/17

    (Continue)

    04/19

    (Continue)

    Quiz 10

    04/24

    Review

    0. Getting Started
    Quiz 0
    1. Java Essentials

    3.3. Divide & Conquer Sort

    Merge sort, quick sort, intro sort

    hashtag
    Divide & Conquer

    • Divide the problem into sub-problems (recursively).

    • Conquer sub-problems, which effectively solve the super problem.

    Why do people ever want to use QuickSort?

    hashtag
    Merge Sort

    • Divide the input array into two sub-arrays.

    • Sort each of the sub-arrays and merge them into the back.

    Let us create the class inheriting AbstractSort:

    • L2: holds the copy of the input array.

    Let us then override the sort() method that calls the helper method:

    • L4-5: increases the size of the temp array if necessary.

      • L5: unchecked type Object to T[].

    What is the advantage of declaring the member field temp?

    The helper method can be defined as follows:

    • L11: sorts the left sub-array.

    • L12: sorts the right sub-array.

    • L13: merges the left and right sub-arrays.

    Finally, the merge() method can be defined as follows:

    • L10-11: copies the input array to the temporary array and counts the assignments.

    • L14-15: no key left in the 1st half.

    • L16-17

    How many assignments are made for the number of keys by the above version of MergeSort?

    hashtag
    Quick Sort

    • Pick a pivot key in the input array.

    • Exchange keys between the left and right partitions such that all keys in the left and right partitions are smaller or bigger than the pivot key, respectively.

    • Repeat this procedure in each partition, recursively.

    Let us create the class inheriting AbstractSort:

    Let us then override the sort() method:

    • L3: stops when the pointers are crossed.

    • L6: sorts the left partition.

    • L7: sorts the right partition.

    The partition() method can be defined as follows:

    • L5: finds the left pointer where endIndex > fst > pivot.

    • L6: finds the right pointer where beginIndex < snd

    circle-info

    The programming design in L5 and L6 are not ideal since the while loops do not include anybody that can confuse other programmers.

    hashtag
    Intro Sort

    • Although Quick Sort is the fastest on average, the worst-case complexity is .

    • sorting algorithms that guarantee faster worst-case complexities than Quick Sort: Quick Sort for random cases and a different algorithm for the worst case.

    How can we determine if Quick Sort is meeting the worse-case?

    Let us define the IntroSoft class inheriting QuickSort:

    • L2: declares a sorting algorithm to handle the worst cases.

    • L14: resets the counters for both the main and auxiliary sorting algorithms.

    We then override the sort() method by passing the maximum depth to the auxiliary method:

    • L9: returns as the maximum depth.

    Finally, the auxiliary method sort() can be defined as follows:

    • L4-5: switches to the other sorting algorithm if the depth of the partitioning exceeds the max depth.

    Does the max-depth need to be set to ?

    hashtag
    Benchmarks

    The following shows runtime speeds, assignment costs, and comparison costs between several sorting algorithms for the random, best, and worst cases.

    Average

    : no key left in the 2nd half.
  • L18-19: the 2nd key is greater than the 1st key.

  • L20-21: the 1st key is greater than or equal to the 2nd key.

  • <
    pivot
    .
  • L7: the left and right pointers are crossed.

  • L8: swaps between keys in the left and right partitions.

  • L11: swaps the keys in the beginIndex and pivot.

  • Complexity

    MergeSort

    TimSort

    QuickSort

    IntroSort

    Best

    O(nlog⁡n)O(n \log n)O(nlogn)

    O(nlog⁡n)O(n \log n)O(nlogn)

    O(nlog⁡n)O(n \log n)O(nlogn)

    O(nlog⁡n)O(n \log n)O(nlogn)

    Worst

    O(nlog⁡n)O(n \log n)O(nlogn)

    O(nlog⁡n)O(n\log n)O(nlogn)

    O(n2)O(n^2)O(n2)

    nnn
    O(n2)O(n^2)O(n2)
    ∃\exists∃
    ⇒\Rightarrow⇒
    2log⁡n2 \log n2logn
    2log⁡n2 \log n2logn
    MergeSortarrow-up-right
    QuickSortarrow-up-right

    3.6. Homework

    This section explains the homework assignment regarding Hybrid Sort.

    hashtag
    Hybrid Sort

    Your task is to write a program that sorts a 2D-array of comparable keys into an 1D-array where all the keys are sorted in ascending order. Here is a sample input to your program:

    Here is the expected output given the sample input:

    Each row in the input array has one of the following properties:

    • Sorted in ascending order (e.g., the 1st row).

    • Sorted in descending order (e.g., the 2nd row).

    • Mostly sorted in ascending order (e.g., the 3rd row).

    • Mostly sorted in descending order (e.g., the 4th row).

    • Randomly distributed (e.g., the 5th row).

    The easiest way is to merge all rows in the input array into an 1D-array then sort it using . The implementation of this strategy is provided to establish the baseline: . Your goal is to design a sorting mechanism that gives a noticeable speed-up over this baseline.

    hashtag
    Tasks

    • Create a class inheriting under the package .

    • Override the sort() method and evaluate your program for robustness and runtime speeds using :

    hashtag
    Submission

    • Commit and push everything under the package.

    • Submit hw1.pdf to Canvas.

    hashtag
    Notes

    • You are not allowed to:

      • Use any implementation that is not written by you nor provided by this course.

      • Call any sorting methods built-in Java or 3rd-party libraries.

    hashtag
    Results

    Baseline: 20697

    • Cannot run: 7

    • Extremely slow: 6

    • Code not found: 1

    3.5. Quiz

    Quiz 3: Sorting Algorithms

    hashtag
    Coding

    hashtag
    Shell Sort: Hibbard

    public class MergeSort<T extends Comparable<T>> extends AbstractSort<T> {
        private T[] temp;
    
        public MergeSort() {
            this(Comparator.naturalOrder());
        }
    
        public MergeSort(Comparator<T> comparator) {
            super(comparator);
        }
    }
    @Override
    @SuppressWarnings("unchecked")
    public void sort(T[] array, int beginIndex, int endIndex) {
        if (temp == null || temp.length < array.length)
            temp = (T[])Array.newInstance(array[0].getClass(), array.length);
        sort(array, temp, beginIndex, endIndex);
    }
    /**
     * @param input the input array.
     * @param temp the array to hold the copy of the input array.
     * @param beginIndex the beginning index of the 1st half (inclusive).
     * @param endIndex the ending index of the 2nd half (exclusive).
     */
    protected void sort(T[] input, T[] copy, int beginIndex, int endIndex) {
        if (beginIndex + 1 >= endIndex) return;
        int middleIndex = Utils.getMiddleIndex(beginIndex, endIndex);
    
        sort(input, copy, beginIndex, middleIndex);
        sort(input, copy, middleIndex, endIndex);
        merge(input, copy, beginIndex, middleIndex, endIndex);
    }
    /**
     * @param input the input array.
     * @param temp the array to hold the copy of the input array.
     * @param beginIndex  the beginning index of the 1st half (inclusive).
     * @param middleIndex the ending index of the 1st half (exclusive).
     * @param endIndex    the ending index of the 2nd half (exclusive).
     */
    protected void merge(T[] input, T[] copy, int beginIndex, int middleIndex, int endIndex) {
        int fst = beginIndex, snd = middleIndex, n = endIndex - beginIndex;
        System.arraycopy(input, beginIndex, copy, beginIndex, n);
        assignments += n;
    
        for (int k = beginIndex; k < endIndex; k++) {
            if (fst >= middleIndex)
                assign(input, k, copy[snd++]);
            else if (snd >= endIndex)
                assign(input, k, copy[fst++]);
            else if (compareTo(copy, fst, snd) < 0)
                assign(input, k, copy[fst++]);
            else
                assign(input, k, copy[snd++]);
        }
    }
    public class QuickSort<T extends Comparable<T>> extends AbstractSort<T> {
        public QuickSort() {
            this(Comparator.naturalOrder());
        }
    
        public QuickSort(Comparator<T> comparator) {
            super(comparator);
        }
    }
    @Override
    public void sort(T[] array, int beginIndex, int endIndex) {
        if (beginIndex >= endIndex) return;
    
        int pivotIndex = partition(array, beginIndex, endIndex);
        sort(array, beginIndex, pivotIndex);
        sort(array, pivotIndex + 1, endIndex);
    }
    protected int partition(T[] array, int beginIndex, int endIndex) {
        int fst = beginIndex, snd = endIndex;
    
        while (true) {
            while (++fst < endIndex && compareTo(array, beginIndex, fst) >= 0);
            while (--snd > beginIndex && compareTo(array, beginIndex, snd) <= 0);
            if (fst >= snd) break;
            swap(array, fst, snd);
        }
    
        swap(array, beginIndex, snd);
        return snd;
    }
    public class IntroSort<T extends Comparable<T>> extends QuickSort<T> {
        private final AbstractSort<T> engine;
    
        public IntroSort(AbstractSort<T> engine) {
            this(engine, Comparator.naturalOrder());
        }
    
        public IntroSort(AbstractSort<T> engine, Comparator<T> comparator) {
            super(comparator);
            this.engine = engine;
        }
    
        @Override
        public void resetCounts() {
            super.resetCounts();
            if (engine != null) engine.resetCounts();
        }
    
    }
    @Override
    public void sort(T[] array, int beginIndex, int endIndex) {
        final int maxdepth = getMaxDepth(beginIndex, endIndex);
        sort(array, beginIndex, endIndex, maxdepth);
        comparisons += engine.getComparisonCount();
        assignments += engine.getAssignmentCount();
    }
    
    protected int getMaxDepth(int beginIndex, int endIndex) {
        return 2 * (int)Utils.log2(endIndex - beginIndex);
    }
    private void sort(T[] array, int beginIndex, int endIndex, int maxdepth) {
        if (beginIndex >= endIndex) return;
    
        if (maxdepth == 0)    // encounter the worst case
            engine.sort(array, beginIndex, endIndex);
        else {
            int pivotIndex = partition(array, beginIndex, endIndex);
            sort(array, beginIndex, pivotIndex, maxdepth - 1);
            sort(array, pivotIndex + 1, endIndex, maxdepth - 1);
        }
    }
    Integer[][] input = {{0,  1,  2,  3},
                         {7,  6,  5,  4},
                         {0,  3,  1,  2},
                         {4,  7,  6,  5},
                         {9,  8, 11, 10}};
    [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 10, 11]
    Try different input arrays; you are responsible for the robustness of your program.
  • Try different configurations for speed comparison (e.g., row size, column size, shuffle ratio).

  • Write the report hw1.pdf describing the logic behind your mechanism and the speed comparison against the baseline.

  • Merge all rows first and apply a fancy sorting algorithm to the 1D array as the baseline does.

  • The input matrix can:

    • Contain an arbitrary number of rows, where the size of each row is also arbitrary.

    • Include any type of the keys that are Comparablearrow-up-right.

  • 23417
    28756
    18676
    22038
    23622
    29390
    18933
    22073
    24988
    43903
    19787
    22217
    25289
    67900
    19851
    22222
    25531
    99835
    20378
    22480
    25536
    217868
    6873
    21512
    23027
    25573
    15545
    21599
    23209
    25825
    18443
    21802
    23411
    25928
    18618
    IntroSortarrow-up-right
    HybridSortBaselinearrow-up-right
    HybridSortHWarrow-up-right
    HybridSortarrow-up-right
    hybridsortarrow-up-right
    HybridSortTestarrow-up-right
    hybridarrow-up-right
    22037

    Create the ShellSortQuizarrow-up-right class under the sort.comparisonarrow-up-right package that inherits ShellSortarrow-up-right.

  • Override the populateSequence() and getSequenceStartIndex() methods such that it performs Shell Sort by using the Hibbard sequence: 2k−1⇒{1,3,7,15,…}2^k - 1 \Rightarrow \{1, 3, 7, 15, \ldots \}2k−1⇒{1,3,7,15,…}.

  • Feel free to use the code in ShellSortKnutharrow-up-right.

  • hashtag
    Radix Sort: MSD

    • Create the RadixSortQuizarrow-up-right class under the sort.distributionarrow-up-right package that inherits RadixSortarrow-up-right.

    • Override the sort() method such that it performs Radix Sort from the most significant digit (MSD) to the least significant digit (LSD).

    • Feel free to use the code in .

    hashtag
    Testing

    • Create the SortQuizTestarrow-up-right class under the test sortarrow-up-right package.

    • Test the correctness of your TernaryHeapQuiz using the testRobustness()arrow-up-right method.

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

    hashtag
    Benchmarking

    • Compare runtime speeds between ShellSortKnuth and ShellSortQuiz for random, ascending, and descending cases using the testRuntime()arrow-up-right method.

    • Compare runtime speeds between LSDRadixSort and RadixSortQuiz for random cases.

    • Create a PDF file quiz3.pdf and write a report that includes charts and explanations to compare runtime speeds between:

      • ShellSortKnuth and ShellSortQuiz.

      • LSDRadixSort and RadixSortQuiz

    hashtag
    Submission

    • Push everything under the sort package to your GitHub repository:

      • Main: src/main/java/edu/emory/cs/sortarrow-up-right

      • Test: src/test/java/edu/emory/cs/sortarrow-up-right

    • Submit quiz3.pdf to Canvas.

    O(nlog⁡n)O(n \log n)O(nlogn)
    O(nlog⁡n)O(n \log n)O(nlogn)
    O(nlog⁡n)O(n \log n)O(nlogn)
    O(nlog⁡n)O(n \log n)O(nlogn)
    O(nlog⁡n)O(n \log n)O(nlogn)

    4.1. Binary Search Trees

    This section discusses abstraction of binary search trees.

    circle-info

    This section assumes that you have already learned core concepts about binary search tress from the prerequisite. Thus, it focuses on the abstraction that can be applied to other types of binary search trees introduced in the following sections.

    hashtag
    Abstract Binary Node

    Let us create an abstract class, :

    • L1: defines two generic types, T for the type of the key and N is for the type of the binary node.

    • L8: calls the setKey() method to assign the value of key in L2

    Let us define boolean methods for the member fields:

    What is the input parameter node is null for the isLeftChild() and isRightChild() methods?

    Let us then define getters to access the member fields:

    We can also define helper methods inferred by the getters:

    • L9: this needs to be casted to N since the input parameter of isLeftChild() is N.

    Is it safe to downcast this to N?

    Finally, let us define setters and their helper methods:

    • L5,10: sets node to be a child of this in two steps:

      • L6,11: replaces the parent of node with this

    hashtag
    Binary Node

    Let us define the class inheriting AbstractBinaryNode:

    • L1: defines only 1 generic type T for the comparable key and passes itself for the generic type N to theAbstractBinaryNode class.

    Is there any abstract method from AbstractBinaryNode to be defined in BinaryNode?

    hashtag
    Abstract Binary Search Tree

    Let us define an abstract class, :

    • L1: defines two generic types, T for the type of the key and N is for the type of the binary node.

    • L4: initializes the member field root to null.

    Why does Java not allow a generic type to be instantiated (e.g., node = new N())?

    Let us define searching methods:

    What are the worst-case complexities of findNode(), findMinNode(), and findMaxNode()?

    Let us define the add() method:

    • L5: creates a node with the key to be the root if this tree does not include any node.

    • L7: finds the appropriate location for the key and creates the node.

    What does the add() method above do when the input key already exists in the tree?

    Let us define the remove() method:

    • L6: removes a node with two children using the Hibbard algorithm.

    • L7: removes a node with no or one child

    The removeSelf() method makes the node's only child as the child of its parent and removes it:

    • L6: finds the child of node.

    • L7: replaces node with its child.

    The removeHibbard() method finds a node that can be the parent of the left- and the right-children of node and makes it a child of its parent:

    Which nodes are guaranteed to be the parent of those left- and right- children?

    The following demonstrates how the above removeHibbard() method works:

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

    hashtag
    Binary Search Tree

    Let us define the BinarySearchTree inheriting AbstractBinarySearchTree:

    3.4. Distribution-based Sort

    Bucket sort, radix sort.

    Unlike comparison-based algorithms in Sections 3.2 and 3.3, distribution-based sorting algorithms do not make comparisons between keys in the input array; instead, they distribute keys into ordered buckets and merge keys in the buckets.

    hashtag
    Bucket Sort

    Let us create an abstract class, AbstractBucketSortarrow-up-right inheriting AbstractSort:

    • L2: declares a list of buckets where each bucket is represented by a .

    • L6: does not pass any comparator to AbstractSort since no comparison is needed.

    • L7: creates a pre-defined number of buckets.

    What kind of input keys can work with distribution-based sorting algorithms?

    We then define a helper method sort():

    • L7: bucketIndex is a function that takes a comparable key and returns the appropriate bucket index of the key.

    • L9-10: adds the keys within the range in the input array to the buckets.

    How many assignments are made by the sort() method in BucketSort?

    hashtag
    Integer Bucket Sort

    Let us create the IntegerBucketSort class inheriting BucketSort that sorts integers within a specific range in ascending order:

    • L2: takes the smallest integer in the range.

    • L8: passes the number of buckets, max - min, to be initialized to the super constructor.\

    We then override the sort() method:

    • L3: passes a lambda expression that takes key and returns key - MIN indicating the index of the bucket that key should be assigned to.

    Is it possible to implement DoubleBucketSort that can handle double instead of integer keys?

    hashtag
    Radix Sort

    Let us create an abstract class, RadixSort, inheriting BucketSort<Integer>:

    • L3: creates 10 buckets to sort any range of integers.

    • L6: returns the order of the most significant digit in the input array.

    hashtag
    LSD Radix Sort

    Let us create the LSDRadixSort class inheriting RadixSort that sorts the integer keys from the least significant digit (LSD) to the most significant digit (MSD):

    • L5: iterates from LSD to MSD.

    • L7: the lambda expression returns the bucket index, the value in the 'th significant digit.

    hashtag
    Benchmarks

    The followings compare runtime speeds between advanced sorting algorithms where the input arrays consist of random integer keys:

    public abstract class BucketSort<T extends Comparable<T>> extends AbstractSort<T> {
        protected List<Deque<T>> buckets;
    
        /** @param numBuckets the total number of buckets. */
        public BucketSort(int numBuckets) {
            super(null);
            buckets = Stream.generate(ArrayDeque<T>::new).limit(numBuckets).collect(Collectors.toList());
        }
    }
    .
    sort.distributionarrow-up-right
    .
    .
  • L7,12: sets node to be the child.

  • L20: replaces the parent of node with this in two steps:

    • L22: node gets abandoned by its current parent.

    • L23: this becomes the new parent of node.

  • L32: replaces oldChild of this node with newChild.

  • L9: creates a binary node typed N, required for the add() method.

    AbstractBinaryNodearrow-up-right
    BinaryNodearrow-up-right
    AbstractBinarySearchTreearrow-up-right
    L13-16: puts all keys in the buckets back to the input array while keeping the order.
    nnn
    Dequearrow-up-right

    4.2. Balanced BST

    This section discusses abstraction of binary search trees.

    A binary search tree gives the worst-case complexity of O(n)O(n)O(n)for searching a key when it is not balanced, where nnn is the number of nodes in the tree:

    Search

    Insert

    Delete

    Unbalanced

    To ensure the complexity, it needs to be balanced at all time (or at most time).

    hashtag
    Rotation

    There are 4 cases of unbalanced trees to be considered:

    • Left linear [3, 2, 1]

    • Right linear [1, 2, 3]

    • Left zig-zag

    These cases can be balanced by performing rotations as follows:

    What should happen to the subtrees (red/orange/green/blue triangles) after the rotations?

    hashtag
    Abstract Balanced Binary Search Tree

    Let us create an abstract class that inherits the abstract class AbstractBinarySearchTree:

    Where does node end up being after the rotations?

    The followings demonstrate how the rotateLeft() method works:

    Let us override the add() and remove() methods that call the abstract method balance():

    • L3: calls the add() method in the super class, AbstractBinarySearchTree.

    • L4: performs balancing on node that is just added.

    What are the worst-case complexities of the add() and remove() methods above?

    5.1. Concept

    This section gives an overview of tries.

    hashtag
    Overview

    A trie is a tree where each node has:

    • 0-to-many children,

    • A key whose type is character,

    • A value that can be any type of object, and

    • An end-state that indicates if the node and its ancestors together represent the end of a certain word.

    Let us consider the following list of strings:

    Given the strings as keys, a binary search tree can be constructed as follows using a balancing algorithm:

    How many character comparisons does it need to make to search a string in a balanced BST?

    With the same list of strings, a trie can be constructed as follows:

    • 1st cell: key (a character).

    • 2nd cell: value (the index of the string that the node represents).

    • 3rd cell: end-state (T for true, F for false).

    What is the worst-case complexity of searching a string in a trie?

    5.3. Quiz

    This section provides exercises for better understanding in tries.

    Let L be a list of country names in String where spaces are allowed (e.g., "United States", "South Korea"). Consider a trie where all country names in L and their unique IDs are put as key-value pairs as follows:

    hashtag
    Implementation

    • Create the class under the package that inherits by passing Integer as the generic type.

    • Update the getEntities() method that takes an input string and returns the list of , where each entity consists of the begin index (inclusive) and end index (exclusive) of the first and the last characters of the corresponding country name respectively as well as the ID of the country name.

    hashtag
    Report

    • Write a report that briefly describe your approach and explains the worst-case complexity of your approach, and save it as quiz5.pdf.

    hashtag
    Notes

    • Substring matching of country names is not required. If your dictionary has "United States" as a country name and the input string contains "United Nation", the "United" should not be matched as a country name.

    • Substring matching within input words are expected. If your dictionary has "America" as a country name and the input contains "American", the first 7 characters, "America"

    5.2. Implementation

    This section describes how to implement a trie.

    hashtag
    Trie Node

    Let us define a class, TrieNodearrow-up-right:

    • L1: the generic type T represents the type of value in L6.

    • L2: is used to store the children.

    • L4: true if this node represents the last character of a certain word; otherwise, false.

    • L9: gives the complexity for searching a key.

    What other types of maps are implemented in Java?

    Let us define getter methods:

    • L10: returns the map consisting of children's characters as keys and their nodes as values.

    Is there any risk of returning this.children in L11?

    Let us then define setter methods:

    • L7: returns the previously assigned value of this node.

    • L13: returns the child with the specific key if exists; otherwise, a new child with the key.

    What does the removeChild() method return if key does not exist in this node?

    Finally, let us define checker methods:

    • L1: returns true if this node represents the last character of a certain word; false.

    hashtag
    Trie

    Let us create the class, :

    • L1: defines a generic type T that can be any type.

    • L5: initializes the root with the null character.

    Let us define the find() method that takes a specific key in string and returns the node corresponding the last character of the key:

    • L5: iterates through every character in the string.

    • L6: finds the node whose key is the current character.

    • L7

    Given the find() method, let us define the get() method that takes a string key and returns the value of the node corresponding to the key in this trie:

    • L3: checks if both the key exists and the key is a word.

    We then define the put() method that inserts a key-value pair to the trie:

    • L4-5: iterates through all characters in the key and adds children as necessary.

    • L7: sets the end state of the node corresponding the last character to true.

    What does node.addChild(c) return if the child with the key c already exists?

    The following demonstrates how the put() method works:

    Finally, let us define the remove() method that removes all appropriate nodes corresponding to the key:

    • L2: retrieves the node corresponding to the last character in the key.

    • L4: returns false the key does not exist or the key is not a word.

    The following demonstrates how the remove() method works:

    5.4. Homework

    This section explains the homework assignment regarding Autocomplete.

    hashtag
    Auto-Complete

    Most virtual keyboards provide the option of auto-complete, which gives a list of candidate words that you wish to type given a prefix you enter. For instance, when you type "sh", it gives a list of candidate words such as "she", "shell", "ship", etc.

    If you select a word from the candidates, it should learn your selection as the top candidate for that prefix. For instance, if you select "ship" from the example above, the next time you enter the same prefix "sh", it should give a list of candidates where the first item is "ship" instead of "she".

    Your task is to write a program that gives a list of candidate words for any prefix and learns the selected candidates.

    hashtag
    Tasks

    • Create a class called under the package:

      • This class extends the abstract class , which extends .

      • The value type of the generic T

    hashtag
    Extra credit

    • Create a class called under the package.

    • Feel free to change the generic type, representing the value type of each TrieNode, to anything other than List<String> (List<something else>).

    hashtag
    Notes

    • Do not change the dictionary file. If you find anything peculiar about the dictionary file, please let me know so everyone works on the same copy of the dictionary file.

    • Please test your program yourself. We will evaluate your program using our unit tests and measure the performance (both speed and accuracy).

    • Take a look at if you are not familiar with methods in the standard library.

    6.1. Concept

    This section describes disjoint sets

    A disjoint set enables to be join sets efficiently. There are two methods implemented in this structure, inSameSet() and union().

    In the following example, each key in {0, 1, 2, 3, 4} is initially in its own set:

    inSameSet(1, 3) checks if the keys 1 and 3 are in the same set:

    1.4. Quiz

    Quiz 1: Java Essentials

    hashtag
    Coding

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

    1.3. Unit Testing

    LongInteger: unit tests.

    hashtag
    Test: LongInteger()

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

    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?

    circle-info

    3.2. Comparison-based Sort

    Selection sort, heap sort, insertion sort, shell sort.

    hashtag
    Selection-based Sort

    Selection-based sorting algorithms take the following steps:

    • For each key where and

    5. Tries

    This chapter discusses tries, also known as prefix trees.

    hashtag
    Contents

    3.1. Abstraction

    The abstract class for all sorting algorithms.

    hashtag
    Abstract Sort

    Let us create an abstract class :

    • L2-4

    1.1. Abstraction

    Different types of objects and inheritances in Java.

    hashtag
    Class

    A is a template to instantiate an .

    What is the relationship between a class and an object?

    Let us create a class called

    2.2. Binary Heap

    Operations: swim and sink.

    A binary heap is a PQ that takesfor both add() and remove(), where keys are stored in a balanced binary tree in which every node has a higher priority than its children (when used as a max-PQ).

    When should we use a binary heap over a lazy PQ or an eager PQ?

    hashtag

    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.

    2.5. Quiz

    Quiz 2: Priority Queues

    hashtag
    Coding

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

    3. Sorting Algorithms

    This chapter discusses different types of sorting algorithms and benchmarks their performance in terms of comparisons and assignments.

    hashtag
    Contents

    2.3. Unit Testing

    Robustness tests.

    hashtag
    Auxiliary Method

    Let us create and define testRobustnessAux() that checks the robustness of any PQ inheriting AbstractPriorityQueue:

    2.4. Benchmarking

    Speed vs. complexity.

    hashtag
    Time

    Let us define a static nested class Time under that stores runtimes for the add() and remove() methods in any PQ:

    4. Binary Search Trees

    This chapter discusses binary search trees using different balancing algorithms.

    hashtag
    Contents

    4.3. Red-Black Trees

    This section discusses a type of balanced binary search trees called Red-Black Trees.

    Red-Black trees preserve the balance at most time by satisfying the following conditions:

    • Every node is either red or black.

    • The root and all leaves (null) are black.

    4.4. Quiz

    This section provides exercises for better understanding in balanced binary search trees.

    hashtag
    Interpretation

    • Explain how the remove() method works in the class:

    2. Priority Queues

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

    hashtag
    Contents

    4.2. AVL Trees

    This section discusses a type of balanced binary search trees called AVL Trees.

    AVL (Adelson-Velsky and Landis) trees preserve the balance at all time by keeping track of the height of every node through a balance factor.

    hashtag
    AVL Node

    Let us create the class inheriting AbstractBinaryNode:

    6. Disjoint Sets

    This chapter discuss disjoint sets, also known as union-find.

    hashtag
    Contents

    6.3. Quiz

    This section provides exercises for better understanding in disjoint sets.

    hashtag
    Implementation

    • Create the class under the package.

    7.1. Implementation

    This section describes how to implement a graph structure.

    hashtag
    Types of Graphs

    There are several types of graphs:

    Can we represent undirected graphs using an implementation of directed graphs?

    6.2. Implementation

    This sections discusses the implementation of disjoint sets.

    Let us create the class:

    • L2: the size of subset (-1 implies 1 ID, -2 implies 2 IDs, and so on).

    7.3. Topological Sorting

    This section discusses two algorithms for topological sorting.

    The task of topological sorting is to sort vertices in a linear order such that for each vertex, all target vertices associated with its incoming edges must appear prior to it.

    hashtag
    By Incoming Scores

    The followings demonstrate how to perform a topological sort by incoming scores, where the incoming score of a vertex is define as the sum of all source vertices:

    public abstract class AbstractBinaryNode<T extends Comparable<T>, N extends AbstractBinaryNode<T, N>> {
        protected T key;
        protected N parent;
        protected N left_child;
        protected N right_child;
    
        public AbstractBinaryNode(T key) {
            setKey(key);
        }
    }
    public boolean hasParent() { return parent != null; }
    
    public boolean hasLeftChild() { return left_child != null; }
    
    public boolean hasRightChild() { return right_child != null; }
    
    public boolean hasBothChildren() {
        return hasLeftChild() && hasRightChild();
    }
    
    /** @return true if the specific node is the left child of this node. */
    public boolean isLeftChild(N node) {
        return left_child == node;
    }
    
    /** @return true if the specific node is the right child of this node. */
    public boolean isRightChild(N node) {
        return right_child == node;
    }
    public T getKey() { return key; }
    
    public N getParent() { return parent; }
    
    public N getLeftChild() { return left_child; }
    
    public N getRightChild() { return right_child; }
    public N getGrandParent() {
        return hasParent() ? parent.getParent() : null;
    }
    
    @SuppressWarnings("unchecked")
    public N getSibling() {
        if (hasParent()) {
            N parent = getParent();
            return parent.isLeftChild((N)this) ? parent.getRightChild() : parent.getLeftChild();
        }
    
        return null;
    }
    
    public N getUncle() {
        return hasParent() ? parent.getSibling() : null;
    }
    public void setKey(T key) { this.key = key; }
    
    public void setParent(N node) { parent = node; }
    
    public void setLeftChild(N node) {
        replaceParent(node);
        left_child = node;
    }
    
    public void setRightChild(N node) {
        replaceParent(node);
        right_child = node;
    }
    
    /**
     * Replaces the parent of the specific node to be this node. 
     * @param node the node whose parent to be replaced.
     */
    @SuppressWarnings("unchecked")
    protected void replaceParent(N node) {
        if (node != null) {
            if (node.hasParent()) node.getParent().replaceChild(node, null);
            node.setParent((N)this);
        }
    }
    
    /**
     * Replaces the old child with the new child if exists.
     * @param oldChild the old child of this node to be replaced.
     * @param newChild the new child to be added to this node.
     */
    public void replaceChild(N oldChild, N newChild) {
        if (isLeftChild(oldChild)) setLeftChild(newChild);
        else if (isRightChild(oldChild)) setRightChild(newChild);
    }
    public class BinaryNode<T extends Comparable<T>> extends AbstractBinaryNode<T, BinaryNode<T>> {
        public BinaryNode(T key) {
            super(key);
        }
    }
    public abstract class AbstractBinarySearchTree<T extends Comparable<T>, N extends AbstractBinaryNode<T, N>> {
        protected N root;
    
        public AbstractBinarySearchTree() {
            setRoot(null);
        }
    
        /** @return a new node with the specific key. */
        abstract public N createNode(T key);
        
        public boolean isRoot(N node) { return root == node; }
    
        public N getRoot() { return root; }
    
        public void setRoot(N node) {
            if (node != null) node.setParent(null);
            root = node;
        }
    }
    /** @return the node with the specific key if exists; otherwise, {@code null}. */
    public N get(T key) {
        return findNode(root, key);
    }
    
    /** @return the node with the specific key if exists; otherwise, {@code null}. */
    protected N findNode(N node, T key) {
        if (node == null) return null;
        int diff = key.compareTo(node.getKey());
    
        if (diff < 0)
            return findNode(node.getLeftChild(), key);
        else if (diff > 0)
            return findNode(node.getRightChild(), key);
        else
            return node;
    }
    
    /** @return the node with the minimum key under the subtree of {@code node}. */
    protected N findMinNode(N node) {
        return node.hasLeftChild() ? findMinNode(node.getLeftChild()) : node;
    }
    
    /** @return the node with the maximum key under the subtree of {@code node}. */
    protected N findMaxNode(N node) {
        return node.hasRightChild() ? findMaxNode(node.getRightChild()) : node;
    }
    public N add(T key) {
        N node = null;
    
        if (root == null)
            setRoot(node = createNode(key));
        else
            node = addAux(root, key);
    
        return node;
    }
    
    private N addAux(N node, T key) {
        int diff = key.compareTo(node.getKey());
        N child, newNode = null;
    
        if (diff < 0) {
            if ((child = node.getLeftChild()) == null)
                node.setLeftChild(newNode = createNode(key));
            else
                newNode = addAux(child, key);
        }
        else if (diff > 0) {
            if ((child = node.getRightChild()) == null)
                node.setRightChild(newNode = createNode(key));
            else
                newNode = addAux(child, key);
        }
    
        return newNode;
    }
    /** @return the removed node with the specific key if exists; otherwise, {@code null}. */
    public N remove(T key) {
        N node = findNode(root, key);
    
        if (node != null) {
            if (node.hasBothChildren()) removeHibbard(node);
            else removeSelf(node);
        }
    
        return node;
    }
    /** @return the lowest node whose subtree has been updatede. */
    protected N removeSelf(N node) {
        N parent = node.getParent();
        N child = null;
    
        if (node.hasLeftChild()) child = node.getLeftChild();
        else if (node.hasRightChild()) child = node.getRightChild();
        replaceChild(node, child);
    
        return parent;
    }
    
    private void replaceChild(N oldNode, N newNode) {
        if (isRoot(oldNode))
            setRoot(newNode);
        else
            oldNode.getParent().replaceChild(oldNode, newNode);
    }
    /** @return the lowest node whose subtree has been updatede. */
    protected N removeHibbard(N node) {
        N successor = node.getRightChild();
        N min = findMinNode(successor);
        N parent = min.getParent();
    
        min.setLeftChild(node.getLeftChild());
    
        if (min != successor) {
            parent.setLeftChild(min.getRightChild());
            min.setRightChild(successor);
        }
    
        replaceChild(node, min);
        return parent;
    }
    public class BinarySearchTree<T extends Comparable<T>> extends AbstractBinarySearchTree<T, BinaryNode<T>> {
        /**
         * @param key the key of this node.
         * @return a binary node with the specific key.
         */
        @Override
        public BinaryNode<T> createNode(T key) {
            return new BinaryNode<T>(key);
        }
    }
    /**
     * @param array       the input array.
     * @param beginIndex  the index of the first key to be sorted (inclusive).
     * @param endIndex    the index of the last key to be sorted (exclusive).
     * @param bucketIndex takes a key and returns the index of the bucket that the key to be added.
     */
    protected void sort(T[] array, int beginIndex, int endIndex, Function<T, Integer> bucketIndex) {
        // add each element in the input array to the corresponding bucket
        for (int i = beginIndex; i < endIndex; i++)
            buckets.get(bucketIndex.apply(array[i])).add(array[i]);
    
        // merge elements in all buckets to the input array
        for (Deque<T> bucket : buckets) {
            while (!bucket.isEmpty())
                array[beginIndex++] = bucket.remove();
        }
    }
    public class IntegerBucketSort extends BucketSort<Integer> {
        private final int MIN;
    
        /**
         * @param min the minimum integer (inclusive).
         * @param max the maximum integer (exclusive).
         */
        public IntegerBucketSort(int min, int max) {
            super(max - min);
            MIN = min;
        }
    }
    @Override
    public void sort(Integer[] array, int beginIndex, int endIndex) {
        sort(array, beginIndex, endIndex, key -> key - MIN);
    }
    public abstract class RadixSort extends BucketSort<Integer> {
        public RadixSort() {
            super(10);
        }
        
        protected int getMaxBit(Integer[] array, int beginIndex, int endIndex) {
            Integer max = Arrays.stream(array, beginIndex, endIndex).reduce(Integer::max).orElse(null);
            return (max != null && max > 0) ? (int)Math.log10(max) + 1 : 0;
        }
    }
    public class LSDRadixSort extends RadixSort {
        @Override
        public void sort(Integer[] array, int beginIndex, int endIndex) {
            int maxBit = getMaxBit(array, beginIndex, endIndex);
            for (int bit = 0; bit < maxBit; bit++) {
                int div = (int)Math.pow(10, bit);
                sort(array, beginIndex, endIndex, key -> (key / div) % 10);
            }
        }
    }
    public class TrieNode<T> {
        private final Map<Character, TrieNode<T>> children;
        private TrieNode<T> parent;
        private boolean end_state;
        private char key;
        private T value;
    
        public TrieNode(TrieNode<T> parent, char key) {
            children = new HashMap<>();
            setEndState(false);
            setParent(parent);
            setKey(key);
            setValue(null);
        }
    }
    "sh" -> ["she", "ship", "shell, ...]
    "sh" -> ["ship", "she", "shell, ...]
    can be a collection of strings (e.g.,
    List<String>
    ).
  • You must create a constructor that takes two parameters, dict_file and max, and calls its super constructorarrow-up-right, which reads all words in the dictionary file (e.g., dict.txtarrow-up-right).

  • Override the getCandidates() method that takes a prefix and returns a list of candidate words matching the prefix:

    • The max-number of candidates in the list must be the return value of the getMax() method.

    • The most recently selected candidate should appear as the 1st item, the 2nd most recently selected candidate should appear as the 2nd item, and so on.

    • The rest of the candidate list should be filled with the shortest words matching the prefix.

    • If there are more than one candidate with the same lengths, they should be sorted alphabetically.

    • Make sure the same candidate does not appear more than once.

  • Override the pickCandidate() method that takes a prefix and a selected candidate, and saves the information:

    • This is the most recently selected candidate for that particular prefix. It must appear as the first item in the candidate list when the same prefix is entered next time.

  • Submit AutocompleteHW.java to Canvas.

  • The getCandidates() method:
    • Must show the most frequently picked candidate first, the 2nd-most frequently picked candidate next, and so on.

    • If there are more than one candidate with the same frequency, sort them by recency.

    • The rest of candidates should be filled as in the original task.

  • You must submit both AutocompleteHW and AutocompleteHWExtra to earn the extra credit.

  • If you are having trouble with implementing getCandidates(), think about how to traverse the trie given any node.

  • If you are having trouble with implementing pickCandidate(), take a look at Trie#putarrow-up-right.

  • You are not allowed to use any type of Maparrow-up-right to store candidates for this homework.

  • Your program should be able to handle prefixes or candidates that do not exist in the dictionary.

  • All picked candidates should be introduced as real words to the trie.

  • Whitespaces are not allowed as input; thus, you should trim all input strings.

  • AutocompleteHWarrow-up-right
    autocompletearrow-up-right
    Autocompletearrow-up-right
    Triearrow-up-right
    AutocompleteHWExtraarrow-up-right
    autocompletearrow-up-right
    Maparrow-up-right

    Override the addDifferentSign()arrow-up-right method so that the add() method in LongIntegerQuizarrow-up-right can handle integers with different signs.

    hashtag
    Testing

    • Create the LongIntegerQuizTestarrow-up-right class under the test algebraicarrow-up-right package.

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

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

    hashtag
    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?

    hashtag
    Submission

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

    • Main: src/main/java/edu/emory/cs/algebraicarrow-up-right

    • Test: src/test/java/edu/emory/cs/algebraicarrow-up-right

    2. Submit answers to the above quizzes to Canvas.

    LongIntegerQuizarrow-up-right
    algebraicarrow-up-right
    LongIntegerarrow-up-right
    Implementation
  • Quiz

  • hashtag
    References

    • Triearrow-up-right

    Concept

    Each node in TernaryHeapQuiz takes up to 3 children, so it becomes a ternary instead of a binary tree.

  • Override the required abstract methods, add()arrow-up-right, remove()arrow-up-right, and size()arrow-up-right, such that both add() and remove() give O(log3n)O(log_3 n)O(log3​n).

  • Feel free to use the code in BinaryHeaparrow-up-right.

  • hashtag
    Testing

    • Create the TernaryHeapQuizTestarrow-up-right class under the test queuearrow-up-right package.

    • Test the correctness of your TernaryHeapQuiz using the testRobustness()arrow-up-right method.

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

    hashtag
    Benchmarking

    • Compare runtime speeds between BinaryHeap and TernaryHeapQuiz for add() and remove() using the testRuntime()arrow-up-right method.

    • Create a PDF file quiz2.pdf and write a report that includes the following:

      • A table and a chart to compare speeds between the two priority queues for those two methods, add() and remove(), with respect to different input sizes.

      • A brief explanation of why a certain PQ is faster than the other PQ with respect to different input sizes.

    hashtag
    Submission

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

    • Main: src/main/java/edu/emory/cs/queuearrow-up-right

    • Test: test/java/edu/emory/cs/queuearrow-up-right

    2. Submit quiz2.pdf to Canvas.

    TernaryHeapQuizarrow-up-right
    queuearrow-up-right
    AbstractPriorityQueuearrow-up-right
    Binary Heap
  • Unit Testing

  • Benchmarking

  • Quiz

  • hashtag
    Resources

    • Main: src/main/java/edu/emory/cs/queuearrow-up-right

    • Test: test/java/edu/emory/cs/queuearrow-up-right

    hashtag
    References

    • Binary Heaparrow-up-right

    • Fibonacci Heaparrow-up-right

    Simple Priority Queues
    Implementation
  • Quiz

  • hashtag
    References

    • Disjoint Setarrow-up-right

    Concept

    8.5. Quiz

    This section provides exercises for better understanding of minimum spanning trees.

    hashtag
    Complexity

    Explain the worst-case complexities of the following algorithms in terms of V and E according to our implementations:

    • Prim's algorithm

    • Kruskal's algorithm

    • Chu-Liu-Edmonds' algorithm

    hashtag
    Comparison

    Provide an example of a graph where Prim's and Kruskal's algorithms find different minimum spanning trees from the same graph and explain how these trees are found. If you cannot find an example, explain why these algorithms always find the same minimum spanning tree given the same graph.

    circle-info

    Different MSTs can be found from the same graph only if there are multiple edges with the minimum weight for any of which can be polled from the priority queue. Consider cases where the compareTo() method is overridden differently from the original implementation.

    hashtag
    Report

    Write a report quiz7.pdf that includes the complexity and comparison analyses.

    See TrieQuizTestarrow-up-right for an example of how to unit-test your approach.

    should be recognized as a country name.
  • The worst-case complexity needs to be explained in terms of the number of characters n in the input string.

  • TrieQuizarrow-up-right
    triearrow-up-right
    Triearrow-up-right
    Entityarrow-up-right
    The union(1, 3) method joins two sets including 1 and 3 as one set:

    If we check if the keys 1 and 3 are in the same set, it should return true although for the keys 1 and 4, it should return false.

    The union(3, 4) method joins the two sets including 3 and 4 as one:

    If we check if 1, 3 and 4 are in the same set, it should return true:

    0: {0}
    1: {1}
    2: {2}
    3: {3}
    4: {4}
    inSameSet(1, 3) -> false
    L6: the @Testarrow-up-right annotation indicates that this method is used for unit testing.
  • L7: methods used for unit testing must be public.

  • L8: tests the default constructor

    • assertEquals()arrow-up-right: 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:

    hashtag
    Test: multiply()

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

    • L11-12: a can hold the integer 152415787517146788750190521 (≈287\approx 2^{87}≈287), which is much larger than the maximum value of long that is 263−12^{63}-1263−1.

    hashtag
    Test: compareTo()

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

    • assertTrue()arrow-up-right: passes if the parameter returns true.

    circle-info

    Unit testingarrow-up-right 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.

    LongIntegerTestarrow-up-right
    unit testarrow-up-right
    : member fields:
    • comparator: specifies the precedence of comparable keys.

    • comparisons: counts the number of comparisons performed during sorting.

    • assignments: counts the number of assignments performed during sorting.

    circle-info

    The two-member fields, comparisons and assignments, are used to micro-benchmark sorting algorithms inheriting AbstractSort.

    Let us define three helper methods, compareTo(), assign(), and swap():

    • L7: compares two keys in the array and increments the count.

    • L18: assigns the value to the specific position in the array and increments the count.

    • L29: swaps the values of the specific positions in the array by calling the assign() method.

    These helper methods allow us to analyze the exact counts of those operations performed by different sorting algorithms.

    How is it different to benchmark runtime speeds from counting these two operations?

    Finally, let us define the default sort() that calls an overwritten abstract method, sort():

    • L5: calls the abstract method sort() that overwrites it.

    • L15: sorts the array in the range of (beginIndex, endIndex) as specified in comparator.

    When would it be useful to sort a specific range in the input array?

    AbstractSortarrow-up-right

    L5-6: every set is initialized with the size of 1.

  • L9: a copy constructor.

  • Let us define the find() method:

    public int find(int id) {
        return (subsets[id] < 0) ? id : find(subsets[id]);
    }
    public int find(int id) {
        return (subsets[id] < 0) ? id : (subsets[id] = find(subsets[id]));
    }
    • L2: returns the ID of the subset where the specific key belongs to.

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

    We then define the inSameSet() method:

    • L2: returns true if the two specific keys are in the same set.

    Finally, let us define the union() method:

    • L2-4: return the subset ID where both key1 and key2 belongs to.

    • L6-10: merges the set containing key2 to the set containing key1.

    • L11-15: merges the set containing key1 to the set containing key2.

    DisjointSetarrow-up-right
    0: {0}
    1: {1, 3}
    2: {2}
    3: {1, 3}
    4: {4}
    inSameSet(1, 3) -> true
    inSameSet(1, 4) -> false
    0: {0}
    1: {1, 3, 4}
    2: {2}
    3: {1, 3, 4}
    4: {1, 3, 4}
    `inSameSet(1, 4)` -> true
    `inSameSet(3, 4)` -> true
    import org.junit.jupiter.api.Test;
    import static org.junit.jupiter.api.Assertions.assertEquals;
    import static org.junit.jupiter.api.Assertions.assertTrue;
    
    public class LongIntegerTest {
        @Test
        public void testConstructors() {
            // default constructor
            assertEquals("0", new LongInteger().toString());
    
            // constructor with a string parameter
            assertEquals("12", new LongInteger("12").toString());
            assertEquals("34", new LongInteger("+34").toString());
            assertEquals("-56", new LongInteger("-56").toString());
            assertEquals("-0", new LongInteger("-0").toString());
    
            // copy constructor
            assertEquals("12", new LongInteger(new LongInteger("12")).toString());
            assertEquals("-34", new LongInteger(new LongInteger("-34")).toString());
        }
    }
    Tests passed: 1 of 1 test
    @Test
    public void testMultiply() {
        LongInteger a = new LongInteger("123456789");
    
        a.multiply(new LongInteger("1"));
        assertEquals("123456789", a.toString());
    
        a.multiply(new LongInteger("-1"));
        assertEquals("-123456789", a.toString());
    
        a.multiply(new LongInteger("-1234567890123456789"));
        assertEquals("152415787517146788750190521", a.toString());
    
        a.multiply(new LongInteger("0"));
        assertEquals("0", a.toString());
    
        a.multiply(new LongInteger("-0"));
        assertEquals("-0", a.toString());
    }
    @Test
    public void testCompareTo() {
        assertTrue(0 < new LongInteger("0").compareTo(new LongInteger("-0")));
        assertTrue(0 > new LongInteger("-0").compareTo(new LongInteger("0")));
    
        assertTrue(0 < new LongInteger("12").compareTo(new LongInteger("-34")));
        assertTrue(0 > new LongInteger("-12").compareTo(new LongInteger("34")));
    
        assertTrue(0 > new LongInteger("-34").compareTo(new LongInteger("12")));
        assertTrue(0 < new LongInteger("34").compareTo(new LongInteger("-12")));
    }
    public abstract class AbstractSort<T extends Comparable<T>> {
        private final Comparator<T> comparator;
        protected long comparisons;
        protected long assignments;
    
        /** @param comparator specifies the precedence of comparable keys. */
        public AbstractSort(Comparator<T> comparator) {
            this.comparator = comparator;
            resetCounts();
        }
    
        /** @return the total number of comparisons performed during sort. */
        public long getComparisonCount() {
            return comparisons;
        }
    
        /** @return the total number of assignments performed during sort. */
        public long getAssignmentCount() {
            return assignments;
        }
    
        public void resetCounts() {
            comparisons = assignments = 0;
        }
    }
    /**
     * @param array an array of comparable keys.
     * @param i     the index of the first key.
     * @param j     the index of the second key.
     * @return array[i].compareTo(array[j]).
     */
    protected int compareTo(T[] array, int i, int j) {
        comparisons++;
        return comparator.compare(array[i], array[j]);
    }
    
    /**
     * array[index] = value.
     * @param array an array of comparable keys.
     * @param index the index of the array to assign.
     * @param value the value to be assigned.
     */
    protected void assign(T[] array, int index, T value) {
        assignments++;
        array[index] = value;
    }
    
    /**
     * Swaps array[i] and array[j].
     * @param array an array of comparable keys.
     * @param i     the index of the first key.
     * @param j     the index of the second key.
     */
    protected void swap(T[] array, int i, int j) {
        T t = array[i];
        assign(array, i, array[j]);
        assign(array, j, t);
    }
    /**
     * Sorts the array in ascending order.
     * @param array an array of comparable keys.
     */
    public void sort(T[] array) {
       sort(array, 0, array.length);
    }
    
    /**
     * Sorts the array[beginIndex:endIndex].
     * @param array      an array of comparable keys.
     * @param beginIndex the index of the first key to be sorted (inclusive).
     * @param endIndex   the index of the last key to be sorted (exclusive).
     */
    abstract public void sort(T[] array, int beginIndex, int endIndex);
    public class DisjointSet {
        private final int[] subsets;
    
        public DisjointSet(int size) {
            subsets = new int[size];
            Arrays.fill(subsets, -1);
        }
    
        public DisjointSet(DisjointSet set) {
            subsets = Arrays.copyOf(set.subsets, set.subsets.length);
        }
    }
    public boolean inSameSet(int key1, int key2) {
        return find(key1) == find(key2);
    }
    public int union(int key1, int key2) {
        int r1 = find(key1);
        int r2 = find(key2);
        if (r1 == r2) return r1;
    
        if (subsets[r1] < subsets[r2]) {
            subsets[r1] += subsets[r2];
            subsets[r2] = r1;
            return r1;
        }
        else {
            subsets[r2] += subsets[r1];
            subsets[r1] = r2;
            return r2;
        }
    }
    [3, 1, 2]
  • Right zig-zag ⇒\Rightarrow⇒ [1, 3, 2]

  • L14: performs balancing on node that is the lowest node in the tree affected by this removal.

  • L24: defines a balancing algorithm for a specific type of balanced binary search trees.

  • Balanced

    O(log⁡n)O(\log n)O(logn)

    O(log⁡n)+αO(\log n) + \alphaO(logn)+α

    O(log⁡n)+βO(\log n) + \betaO(logn)+β

    O(log⁡n)O(\log n)O(logn)
    ⇒\Rightarrow⇒
    ⇒\Rightarrow⇒
    ⇒\Rightarrow⇒
    O(n)O(n)O(n)
    O(n)O(n)O(n)
    O(n)+βO(n) + \betaO(n)+β
    AbstractBalancedBinarySearchTreearrow-up-right
    : the child does not exist, implying that
    key
    is not introduced in this trie.
    L8: returns the value of the previously key if exists; otherwise, null.
    L7-10: if the retrieved node has children, sets its end-state to false and returns true.
  • L12-23: removes ancestors who have descendants only related to this key.

  • O(1)O(1)O(1)
    Maparrow-up-right
    HashMaparrow-up-right
    Triearrow-up-right
    Java SE provides a similar class called BigIntegerarrow-up-right although the implementations of LongIntegerandBigIntegerare completely independent.

    hashtag
    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?

    hashtag
    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 static methodarrow-up-right referenced by the class type Arrays, not an object. Java provides many classes with static methods that are commonly used (e.g., Arraysarrow-up-right, Collectionsarrow-up-right).

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

    circle-info

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

    hashtag
    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 NullPointerExceptionarrow-up-right 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: , .

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

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

    When should we use throwarrow-up-right statements over try..catcharrow-up-right blocks for error handling and vice versa?

    circle-info

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

    hashtag
    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 UnsupportedOperationExceptionarrow-up-right:

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

    circle-info

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

    hashtag
    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?

    hashtag
    Method: main()

    Let us create a runnable class called LongIntegerRunarrow-up-right 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 Objectarrow-up-right that defines a few member methods including toString()arrow-up-right, which gets called automatically by the println() method to retrieve the string representation of this object. We can use the helper method Arrays.toString()arrow-up-right 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?

    hashtag
    Method: toString()

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

    • L5: StringBuilderarrow-up-right 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?

    hashtag
    Method: compareTo()

    Java does not allow operator overloadingarrow-up-right, 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 Comparablearrow-up-right 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 compareTo()arrow-up-right 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: ArrayListarrow-up-right is a specific implementation of the interface Listarrow-up-right.

      • All collections in Java inheriting AbstractCollectionarrow-up-right 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?

    LongIntegerarrow-up-right
    primitive typesarrow-up-right
    :
    • Search the maximum key AmA_mAm​ where m∈[1,i)m \in [1, i)m∈[1,i).

    • Swap AiA_iAi​ and AmA_mAm​

    The complexities differ by different search algorithms:

    Algorithm

    Search

    Compare

    Swap

    Selection Sort

    Heap Sort

    Selection Sort uses linear search to find the minimum (or maximum) key, whereas Heap Sort turns the input array into a heap, so the search complexity becomes O(log⁡n)O(\log n)O(logn) instead of O(n)O(n)O(n) .

    Can the search be faster than O(log⁡n)O(\log n)O(logn)?

    hashtag
    Selection Sort

    Let us create the SelectionSortarrow-up-right class inheriting AbstractSort:

    Let us then override the sort() method:

    • L3-12: O(n2)O(n^2)O(n2)

      • L3: iterates all keys within the range →O(n)\rightarrow O(n)→O(n).

      • L4-9: finds the index of the maximum key within the range .

      • L11: swaps the maximum key with the last key in the range .

    How does the sort() method work with Comparator.reverseOrder()?

    hashtag
    Heap Sort

    Let us create the HeapSortarrow-up-right class inheriting AbstractSort:

    Before we override the sort() method, let us define the following helper methods:

    • L1-7: finds the right position of the k'th key by using the sink operation.

    • L9-11: finds the parent index of the k'th key given the beginning index.

    • L13-15: finds the left child index of the k'th key given the beginning index.

    How is this sink() method different from the one in the BinaryHeaparrow-up-right class?

    Finally, we override the sort() method:

    • L4-5: turns the input array into a heap →O(nlog⁡n)\rightarrow O(n \log n)→O(nlogn):

      • L4: iterates from the parent of the key in the ending index →O(n)\rightarrow O(n)→O(n) .

      • L5: sinks the key .

    • L8-11: selection sort :

      • L8: iterates all keys within the range .

      • L9

    What is the worst-case scenario for Selection Sort and Heap Sort?

    hashtag
    Insertion-based Sort

    Insertion-based sorting algorithms take the following steps:

    • For each key AiA_iAi​ where ∣A∣=n|A| = n∣A∣=n and i∈[1,n)i \in [1, n)i∈[1,n) :

      • Keep swapping AjA_jAj​ and AiA_iAi​ until Aj≤AiA_j \leq A_iAj​≤Ai​.

    The complexities differ by different sequences:

    Algorithm

    Sequence

    Compare

    Swap

    Insertion Sort

    Adjacent

    Shell Sort

    Knuth

    hashtag
    Insertion Sort

    Let us create the InsertionSortarrow-up-right class inheriting AbstractSort:

    Let us then define an auxiliary method, sort():

    • L4: iterates keys in the input array →O(n)\rightarrow O(n)→O(n).

    • L5: compares keys in the sublist of the input array →O(nh)\rightarrow O(\frac{n}{h})→O(hn​).

    • L6: swaps the two keys.

    Given the auxiliary method, the sort() method can be defined as follows where h = 1:

    How many swaps does Insertion Sort make for the following array? [7, 1, 2, 3, 4, 5, 6, 14, 8, 9, 10, 11, 12, 13, 0]

    hashtag
    Shell Sort

    hashtag
    Gap Sequence

    Shell Sort groups keys whose gap is defined by a particular gap sequencearrow-up-right:

    • Knuth: (3k−1)/2⇒{1,4,13,40,121,…}(3^k - 1) / 2 \Rightarrow \{1, 4, 13, 40, 121, \ldots\}(3k−1)/2⇒{1,4,13,40,121,…}

    • Hibbard: 2k−1⇒{1,3,7,15,31,63,…}2^k - 1 \Rightarrow \{1, 3, 7, 15, 31, 63, \ldots\}2k−1⇒{1,3,7,15,31,63,…}

    • Pratt: 2p⋅3q⇒{1,2,3,4,6,8,9,12,…}2^p \cdot 3^q \Rightarrow \{1, 2, 3, 4, 6, 8, 9, 12, \ldots\}2p⋅3q⇒{1,2,3,4,6,8,9,12,…}

    • Shell: , where

    For the above example, by using the Hibbard sequence, it first groups keys whose gap is 7:[7, 14, 0], [1, 8], [2, 9], [3, 10], [4, 11], [5, 12], [6, 13]

    It then sorts each group using Insertion Sort, which results in the following array: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

    The above procedure is repeated for gaps 3 and 1; however, the array is already sorted, so no more swapping is necessary.

    hashtag
    Implementation

    Let us create an abstract class, ShellSortarrow-up-right, inheriting AbstractSort:

    • L2: stores a particular gap sequence.

    • L7: pre-populate the gap sequence that can handle the input size up to 10000.

    Then, let us define two abstract methods, populateSequence() and getSequenceStartIndex():

    • L5: populates a particular sequence for the input size n.

    • L11: returns the index of the first gap to be used given the input size n.

    Let us then override the sort() method:

    • L4: should not re-populate the sequence unless it has to.

    • L6: iterates the sequence →O(s)\rightarrow O(s)→O(s) where sss is the number of gaps in the sequence.

    • L7: sorts the gap group by using the auxiliary method.

    hashtag
    Knuth Sequence

    Let us create the ShellSortKnutharrow-up-right class that populates the Knuth Sequence for ShellSort:

    The two abstract methods can be overridden as follows:

    • L2: populates the Knuth sequence up to the gap ≤n3\leq \frac{n}{3}≤3n​.

    • L13: returns the index of the first key ≤n3\leq \frac{n}{3}≤3n​.

    Why should we use n3\frac{n}{3}3n​ as the largest gap in the sequence?

    hashtag
    Demonstration

    hashtag
    Unit Tests & Benchmarks

    The codes for unit testing and benchmarking are available in SortTestarrow-up-right:

    AiA_iAi​
    ∣A∣=n|A| = n∣A∣=n
    i∈[n,0)i \in [n, 0)i∈[n,0)
    that we want to be a super class of all numeral types:
    • L1: packageindicates the name of the package that this class belongs to in a hierarchy.

    • L3: public is an access-level modifierarrow-up-right.

    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:

    • L4: @param adds a javadocarrow-up-right 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?

    hashtag
    Interface

    Let us define Numeral as an interfacearrow-up-right:

    • 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?

    • L1: extends inherits exactly one class or interface.

    • L9: default allows an interface to define a method with its bodyarrow-up-right (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.

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

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

    Why is a runtime error worse than a compile error?

    Downcastingarrow-up-right, 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?

    hashtag
    Polymorphism

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

    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 overridearrow-up-right the add() method as follows?

    • L2: @Override is a predefined annotationarrow-up-right 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:

    However, this is considered an overloadingarrow-up-right, 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?

    hashtag
    Generics

    The third way is to use genericsarrow-up-right, introduced in Java 5:

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

    • L1: T is specified as SignedNumeral.

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

    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:

    • L1: implements inherits multiple interfacesarrow-up-right.

    • L2-6: LongInteger is a regular class, so all abstract methodsarrow-up-right 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:

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

    circle-info

    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.

    hashtag
    Enum

    Let us create an enumarrow-up-right class called Signarrow-up-right to represent the "sign" of the numeral:

    • All items in an enum have the scope of staticarrow-up-right 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., +, -):

    • L5: final makes this field a constant, not a variablearrow-up-right, such that the value cannot be updated later.

    • L8: this points to the objectarrow-up-right created by this constructor.

    • L11: @return adds a 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.

    hashtag
    Limit of Interface

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

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

    • L3: condition ? A : B is a ternary expressionarrow-up-right 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.

    hashtag
    Abstract Class

    Let us turn SignedNumeralarrow-up-right into an abstract classarrow-up-right:

    • 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?

    • 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?

    classarrow-up-right
    objectarrow-up-right
    Numeralarrow-up-right
    Balanced Binary Tree

    A balanced binary tree can be implemented by a list such that given the kkk'th key in the list:

    • The index of its parent: ⌊k2⌋\lfloor \frac{k}{2} \rfloor⌊2k​⌋

    • The index of its left child: 2⋅k2 \cdot k2⋅k.

    • The index of its right child: 2⋅k+12 \cdot k + 12⋅k+1.

    • e.g., given the list [1, 2, 3, 4, 5], 1 is the root, 2 and 3 are the left and right children of 1, and 4 and 5 are the left and right children of 2, respectively.

    Is the tree represented by the list [1, 2, 3, 4, 5] balanced?

    hashtag
    Implementation

    Let us create the BinaryHeaparrow-up-right class inheriting AbstractPriorityQueue:

    • L11: adding null as the first item makes it easier to calculate the indices of parents and children.

    • L16: keys has the extra item of null from L11 that is not an input key.

    How does adding null make the calculation of the indices easier?

    Additionally, let us define the helper method compare() that compares two keys in the list:

    • L8: calls the compare() method in the member field priority declared in the super class.

    hashtag
    Add() with Swim

    The add() method in a heap uses the operation called swim as follows:

    • L3: appends the new key to the list.

    • L7-10: keeps swimming until the list becomes a heap:

      • L8: compares the new key to its parent if exists.

      • L9: if the new key has a higher priority than its parent, them.

    How many keys are compared at most for the `add operation?

    hashtag
    Remove() with Sink

    The remove() method in a heap uses the operation called sink as follows:

    • L3: checks if the heap is empty.

    • L4: swaps the root and the last key in the list.

    • L3: removes the (swapped) last key with the highest priority in this heap.

    • L10-16: keeps sinking until the list becomes a heap:

      • L12: finds the child with a higher priority.

      • L13

    How many keys are compared at most for the remove operation?

    O(log⁡n)O(\log n)O(logn)
    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?

    hashtag
    Abstract Priority Queue

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

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

      • T must be comparablearrow-up-right 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():

    hashtag
    Lazy Priority Queue

    Let us define LazyPriorityQueuearrow-up-right whose core methods satisfy the following conditions:

    • add(): takesO(1)O(1)O(1)to add a key to the PQ.

    • remove(): takesO(n)O(n)O(n) 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 O(1)O(1)O(1).

    • L15-20: removes a key to the list in O(n)O(n)O(n).

      • 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 O(n+n)O(n+n)O(n+n)?

    hashtag
    Eager Priority Queues

    Let us define EagerPriorityQueuearrow-up-right whose core methods satisfy the following conditions:

    • add(): takesO(n)O(n)O(n)to add a key to the PQ.

    • remove(): takesO(1)O(1)O(1) 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 O(n)O(n)O(n) .

      • L8: finds the index of the key to be inserted in the list using binary search in O(log⁡n)O(\log n)O(logn).

      • 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?

    Comparison-based Sort
  • Divide & Conquer Sort

  • Distribution-based Sort

  • Quiz

  • Homework

  • hashtag
    Resources

    • Main: src/main/java/edu/emory/cs/sortarrow-up-right

    • Test: src/test/java/edu/emory/cs/sortarrow-up-right

    hashtag
    References

    • Comparison-based Algorithms

      • Selection Sortarrow-up-right →\rightarrow→ Heap Sortarrow-up-right

      • Insertion Sortarrow-up-right →\rightarrow→ Shell Sortarrow-up-right

    • Divide and Conquer Algorithms

    • Distribution-based Algorithms

    Abstraction
    L6: the generic type T is defined specifically for this method such that the priority queue pq, the list keys, and the comparator comp take the same type T that is comparable.
  • L7: any collection that inherits the interface Iterablearrow-up-right has the member method forEach()arrow-up-right that takes a Consumerarrow-up-right and applies each key in the collection to the consumer.

  • L8: any collection has the member method stream()arrow-up-right that returns the Streamarrow-up-right of the collection.

    • sorted()arrow-up-right: creates a stream of the collection whose keys are sorted

    • collect()arrow-up-right: creates a collection specified by the .

    • : returns a collector that transforms the stream into a list.

  • L9: iterates each of the sorted keys and compares it to the returned value of pq.remove().

  • What are the advantages of defining a method-level generic type?

    hashtag
    Functional Programming

    The following code shows a traditional way of iterating over a list (that is equivalent to L7 above):

    Java 5 introduced an enhanced for loop that simplified the traditional for loop:

    Java 8 introduced lambda expressionsarrow-up-right that enabled functional programming in Java. The following code takes each key (as a variable) in keys and applies it to pq.add():

    The syntax of the above code can be simplified as follows (as shown in L7):

    What are the main differences between object-oriented programming and functional programming?

    circle-info

    Since Java 8, higher-order methods can be defined by parameterizing types of interfaces in the functionarrow-up-right package.

    hashtag
    Test: Robustness

    Let us define the testRobustness() method that calls the auxiliary method testRobustnessAux():

    • L7-9: tests different types of max-PQs. The keys need to be sorted in reverse order for it to test the remove() method.

    • L11-13: tests different types of min-PQs. The keys need to be sorted in the natural order for it to test the remove() method.

    The generic types of the PQs in L4-5 are explicitly coded as <Integer>, whereas they are simplified to <> in L7-13. When do generic types need to be explicitly coded?

    circle-info

    The testRobustness() method illustrates the benefit of defining AbstractPriorityQueue such that any type of PQ can be passed to testRobustnessAux() for unit-testing.

    PriorityQueueTestarrow-up-right
    What is the advantage of defining static nested class over non-static nested class?

    hashtag
    Runtime Estimation

    Let us define addRuntime() that estimates the runtime for add() and remove()for a particular PQ:

    • L5-8: estimates the runtime for add():

      • L5: gets the snapshot of the starting time using currentTimeMillis()arrow-up-right.

      • L7: gets the snapshot of the ending time using .

      • L8: accumulates the runtime to times.add.

    • L11-14: estimates the runtime for remove().

    Is the approach using currentTimeMillis() a proper way of benchmarking the speed?

    We then define benchmark() that estimates the speeds of multiple PQs using the same lists of keys by calling addRuntime():

    • L1: method declaration:

      • AbstractPriorityQueue[]: It is unusual to declare an array of objects involving generics in Java (you will see the unique use case of it in the testSpeedAux() method).

      • : passes a function as a parameter that returns a key of the generic type T.

    • L2: generates an array of Time using several stream operations. The dimension of times[] is the same as the dimension of queues such that times[i] accumulates the speeds taken by the queue[i], respectively.

    • L4: benchmarks the PQs multiple times as specified by iter.

    • L5: generates the list of keys specified by the supplier sup.

    • L6-7: estimate the runtimes by using the same list of keys for each PQ.

    Why do we need to benchmark the priority queues multiple times?

    hashtag
    Benchmark

    Let us define testSpeedAux() that takes multiple PQs and prints out the benchmarking results:

    • L1: annotates safeVarargsarrow-up-right indicating that this method takes vararg parameters.

    • L2: declares a final method that cannot be overridden by its subclasses.

      • It can take a 0-to-many number of abstract PQs.

      • The type of queues is AbstractPriorityQueue<Integer>[] such that it can be directly passed to the benchmark() method.

    • L5: creates a generator.

    • L8: benchmarks different sizes of PQs.

    • L10: warms up the Java Virtual Machine (JVM) before the actual benchmarking.

      • rand::nextnt that is equivalent to the lambda expression of () -> rand.nextInt().

    • L12: benchmarks add() and remove() in the PQs.

    • L14-18: prints the benchmark results.

      • L14: uses to concatenate strings with the specific delimiter.

      • L16

    Why is it recommended to declare the method final if it takes vararg parameters? Why does JVM need to be warmed-up before the actual benchmarking? Why should you use StringJoiner instead of concatenating strings with the + operator?

    hashtag
    Speed Comparison

    The followings show runtime comparisons between LazyPQ, EagerPQ, and BinaryHeap:

    PriorityQueueTestarrow-up-right
    Balanced BST
  • AVL Trees

  • Red-Black Trees

  • Quiz

  • hashtag
    References

    • Binary Search Treesarrow-up-right

    • Balanced Binary Search Treesarrow-up-right

      • AVL Treesarrow-up-right

    Binary Search Trees

    Every red node must have two black children.

  • Every path from a given node to any of its leaves goes through the same number of black nodes.

  • hashtag
    Red-Black Node

    Let us create the RedBlackNodearrow-up-right class inheriting AbstractBinaryNode:

    • L2: if is_red is true, the color of this node is red; otherwise, it is black.

    • L6: the color of the node is black by default upon instantiation.

    Let us the RedBlackTreearrow-up-right class inheriting AbstractBalancedBinarySearchTree:

    Finally, let us override balance() method that handles 3 cases:

    • L3: sets the color to black if node is the root.

    • L5: if the color of node's parent is red:

      • L8: checks if the color of node's uncle is also red.

      • L10: the color of node's uncle is black.

    What about the case when the color of node's parent is black?

    The following shows a method that handles when both the parent and the uncle are red:

    Are there any structural changes after this method is called?

    The following shows a method that handles when the parent is red but the uncle is black:

    • L7: the case of left zig-zag.

    • L11: the case of right zig-zag.

    • L19: the case of left linear.

    • L21: the case of right linear.

    The followings demonstrate how the balance() method works in Red-Black trees:

    How does a Red-Black tree know when it is unbalanced?

    Explain what gets assigned to lowest in L71arrow-up-right.

  • Explain how the balance() methods in AVLTreearrow-up-right and RedBlackTreearrow-up-right keep the trees balanced (or fails to keep them balanced) after a removal.

  • Write a report including your explanation and save it as quiz4.pdf.

  • hashtag
    Implementation

    • Create the BalacnedBinarySearchTreeQuizarrow-up-right class under the tree.balancedarrow-up-right package that inherits theAbstractBalancedBinarySearchTreearrow-up-right class.

    • Override the balance() method that first checks the following conditions:

      • node is the only child &

      • node’s parent is the right child of node's grand parent &

      • node’s uncle has only one child.

    • If all of the above conditions are satisfied, balance() makes multiple rotations such that the left subtree is always full before any node gets added to the right subtree.

    • For the example below, after adding the keys (in red) to the trees 1, 2, 3, and 4, they all need to be transformed into the tree 5 by the balance() method.

    • The transform must be performed only by rotations; other setter methods such as setLeftChild() or setRightChild() are not allowed in this assignment.

    AbstractBalancedBinarySearchTreearrow-up-right

    L2: keeps track of the height of this node.

  • L6: a node with no children has the height of 1.

  • We then override the setLeftChild() and setRigthChild() methods such that the height of this node gets adjusted accordingly with the new child by calling the resetHeights() method:

    • L15: adjusts the height of this node and its ancestors, recursively.

    • L20-22: retrieve the height of this node.

    • L24-27: resets the height of this node if different and makes the recursive call to its parent.

    What are the heights of 3, 5, and 7 in the figure below when the height of 4 changes from 3 to 4?

    Finally, we define the getBalanceFactor() method that returns the height difference between the left-subtree and the right-subtree of this node:

    How can we tell if the node is unbalanced by using the balance factor?

    hashtag
    AVL Tree

    Let us create the AVLTreearrow-up-right class that inherits AbstractBalancedBinarySearchTree and override the createNote(), rotateLeft(), and rotateRight() methods:

    • L10,16: resets the height of node after rotation.

    node.resetHeights() does not reset heights of nodes in its subtree. Would this cause an issue?

    We then override the balance() method:

    • L6: the left-subtree is longer.

      • L9-10: the case of left zig-zag.

      • L12: the case of left linear.

    • L14: the right-subtree is longer.

      • L17: the case of right zig-zag.

      • L20

    The followings demonstrate how the balance() method works in AVL Trees:

    What is the worst-case complexity of the balance() method in AVLTree?

    AVLNodearrow-up-right

    Assume that the find()arrow-up-right method in the DisjointSet class uses the baseline approach:

  • A disjoint set can be represented by a tree. Update the main()arrow-up-right method in the DisjointSetQuiz class that would result the following tree:

  • hashtag
    Report

    Write a report quiz6.pdf that includes the followings:

    • Describe how the values in the subsets[]arrow-up-right array changes after each call in the main() method.

    • Describe how the values in the subsets[]arrow-up-right array would change after calling find(0) once all keys are added as above, assuming that the find() method in DisjointSet class uses the efficient approach:

    DisjointSetQuizarrow-up-right
    setarrow-up-right
    hashtag
    Edge

    Let us create the Edgearrow-up-right class representing a directed edge under the grapharrow-up-right package:

    • L1: inherits the Comparablearrow-up-right interface.

    • L2-3: the source and target vertices are represented by unique IDs in int.

    • L10: constructor overwriting.

    • L14: copy constructor.

    Let us the define the init() method, getters, and setters:

    Let us override the compareTo() method that compares this edge to another edge by their weights:

    • L4: returns 1 if the weight of this edge is greater.

    • L5: returns -1 if the weight of the other edge is greater.

    • L6: returns 0 is the weights of both edges are the same.

    hashtag
    Graph

    Let us create the Grapharrow-up-right class under the grapharrow-up-right package:

    • L2: a list of edge lists where each dimension of the outer list indicates a target vertex and the inner list corresponds to the list of incoming edges to that target vertex.

    • L5: creates an empty edge list for each target vertex.

    • L9: copies the list of edge lists from g to this graph.

    • L13: returns true if all incoming edge lists are empty using the method.

    • L17: the size of the graph is the number of vertices in the graph.

    For example, a graph with 3 vertices 0, 1, and 2 and 3 edges 0 -> 1, 2 -> 1, and 0 -> 2, the incoming_edges is as follows:

    Let us define setters for edges:

    • L2-4: retrieves the incoming edge list of target, and adds the new edge to the edge list.

    • L9-10: add edges to both directions.

    Let us then define getters:

    • L6: uses the flatMap()arrow-up-right method that merges the list of edge lists into one list of edges.

    • L10: uses the filter()arrow-up-right and boxed()arrow-up-right methods to find vertices with no incoming edges.

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

    Let us define the getOutgoingEdges() method that returns the list of outgoing edges:

    • L1: returns a list of edge deque, where each dimension in the outer list represents the deque of outgoing edges for the corresponding source vertex.

    • L2: generates the list of empty edge deques.

    • L4-7: visits every target vertex to retrieve the incoming edge lists, and assigns edges to appropriate source vertices.

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

    hashtag
    By Depth-First

    The followings demonstrate how to perform a topological sort by depth-first traversing:

    hashtag
    Implementation

    Let us define the topological_sort() method under the Graph class that performs topological sort by either incoming scores or depth-first search:

    • L2: starts with vertices with no incoming edges.

    • L3: retrieves outgoing edges of each source vertex.

    • L4: consists of the list of vertices in topological order.

    • L6: iterates until no more vertex to visit:

      • L8-9: adds a source vertex to order.

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

    ["she", "shell", "see", "shore", "selling", "sell"]

    7.4. Quiz

    This section provides exercises for better understanding in disjoint sets.

    hashtag
    Implementation

    • Create the GraphQuizarrow-up-right class under the grapharrow-up-right package.

    • Update the numberOfCycles() method that returns the number of all cycles in the graph.

    • Make sure to find all atomic cycles; do not count cycles that can be created by simply combining other cycles.

    hashtag
    Report

    Write a report quiz6.pdf that includes the followings:

    • Explain the worst-case complexity of the algorithm for your numberOfCycle() method.

    • For the method in the Graph class, explain why the condition for the exception indicates that the graph includes a cycle.

    7. Graphs

    This chapter implementations of basic algorithms related to graphs.

    hashtag
    Contents

    1. Implementation

    hashtag
    References

    8. Minimum Spanning Trees

    This chapter discusses algorithms to find minimum spanning trees in undirected and directed graphs.

    hashtag
    Contents

    1. Abstraction

    hashtag
    References

    7.2. Cycle Detection

    This section describes an algorithm to detect a cycle in a graph.

    A cycle in a graph can be detected by traversing through the graph:

    Let us define the containsCycle() method under the Graph class that returns true if the graph contains any cycle; otherwise, false:

    public boolean containsCycle() {
        Deque<Integer> notVisited = IntStream.range(0, size()).boxed().collect(Collectors.toCollection(ArrayDeque::new));
    
        while (!notVisited.isEmpty()) {
            if (containsCycleAux(notVisited.poll(), notVisited, new HashSet<>()))
                return true;
        }
    
        return false;
    }
    • L2: initially all vertices are not visited.

    • L4: iterates until all vertices are visitied:

      • L5-6: if the recursive call finds a cycle, returns true.

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

    • L2-3: marks the target vertex visited.

    • L5: for each incoming edge of the target vertex:

    Why do we need to pass the new HashSet for every call in L5?

    8.1. Abstraction

    This section discusses spanning trees in a graph.

    hashtag
    Overview

    A spanning tree in a graph is a tree that contains all vertices in the graphs as its nodes. A minimum spanning tree is a spanning tree whose sum of all edge weights is the minimum among all the other spanning trees in the graph.

    Can a graph have more than one minimum spanning tree?

    hashtag
    Spanning Tree

    Let us define the class under the package:

    • L1: inherits the interface.

    • L2: contains all edges in this spanning tree.

    • L3

    We then define getters and setters:

    • L3: the size of the spanning tree is determined by the number of edges.

    Finally, we override the compareTo() method that makes the comparison to another spanning tree by the total weight:

    hashtag
    MST

    Let us create the interface that is inherited by all minimum spanning tree algorithms:

    • L2: an abstract method that takes a graph and returns a minimum spanning tree.

    9.2. Ford-Fulkerson Algorithm

    This section describes the implementation of Ford-Fulkerson Algorithm

    hashtag
    Subgraph

    Let us define the class that consists of a subset of vertices and edges from the original graph:

    Let us define helper methods:

    8.6. Homework

    This section describes the homework assignment to develop an algorithm to find all minimum spanning tress given an undirected graph.

    hashtag
    Finding All Minimum Spanning Trees

    Your task is to write a program that finds all minimum spanning trees given an undirected graph.

    hashtag

    8.2. Prim's Algorithm

    This section discusses Prim's Algorithm that finds a minimum spanning tree in an undirected graph.

    Prim's algorithm finds a minimum spanning tree by finding the minimum edge among all incoming edges of visited vertices.

    Let us define the class inheriting the MST interface:

    • L9: adds the vertex 0 to the visited set and its incoming edges to the queue.

    8.4. Edmonds' Algorithm

    This section discusses Chu-Liu-Edmonds' Algorithm that finds a MST in a directed graph.

    Chu-Liu-Edmonds' Algorithm takes the following steps to find a MST in a directed graph:

    1. Initially, every vertex is considered a subtree.

    2. For each subtree, keep 1 incoming edge with the minimum weight.

    8.3. Kruskal’s Algorithm

    This section discusses Kruskal's Algorithm that finds a minimum spanning tree in an undirected graph.

    Kruskal's algorithm finds a minimum spanning tree by finding the minimum edge among subtrees.

    Let us create the class inheriting the MST interface:

    • L4: adds all edges to the priority queue.

    9. Network Flow

    This chapter discusses the optimization problem called Network Flows that finds the maximum flow given a flow network.

    hashtag
    Contents

    public abstract class AbstractBalancedBinarySearchTree<T extends Comparable<T>, N extends AbstractBinaryNode<T, N>> extends AbstractBinarySearchTree<T, N> {
        /**
         * Rotates the specific node to the left.
         * @param node the node to be rotated.
         */
        protected void rotateLeft(N node) {
            N child = node.getRightChild();
            node.setRightChild(child.getLeftChild());
    
            if (node.hasParent())
                node.getParent().replaceChild(node, child);
            else
                setRoot(child);
    
            child.setLeftChild(node);
        }
    
        /**
         * Rotates the specific node to the right.
         * @param node the node to be rotated.
         */
        protected void rotateRight(N node) {
            N child = node.getLeftChild();
            node.setLeftChild(child.getRightChild());
    
            if (node.hasParent())
                node.getParent().replaceChild(node, child);
            else
                setRoot(child);
    
            child.setRightChild(node);
        }
    }
    @Override
    public N add(T key) {
        N node = super.add(key);
        balance(node);
        return node;
    }
    
    @Override
    public N remove(T key) {
        N node = findNode(root, key);
    
        if (node != null) {
            N lowest = node.hasBothChildren() ? removeHibbard(node) : removeSelf(node);
            if (lowest != null && lowest != node) balance(lowest);
        }
    
        return node;
    }
    
    /**
     * Preserves the balance of the specific node and its ancestors.
     * @param node the node to be balanced.
     */
    protected abstract void balance(N node);
    public TrieNode<T> getParent() { return parent; }
    
    public char getKey() { return key; }
    
    public T getValue() { return value; }
    
    public TrieNode<T> getChild(char key) { return children.get(key); }
    
    /** @return the map whose keys and values are children's characters and nodes. */
    public Map<Character, TrieNode<T>> getChildrenMap() {
        return children;
    }
    public void setParent(TrieNode<T> node) { parent = node; }
    
    public void setKey(char key) { this.key = key; }
    
    public void setEndState(boolean endState) { end_state = endState; }
    
    public T setValue(T value) {
        T tmp = this.value;
        this.value = value;
        return tmp;
    }
    
    public TrieNode<T> addChild(char key) {
        TrieNode<T> child = getChild(key);
    
        if (child == null) {
            child = new TrieNode<>(this, key);
            children.put(key, child);
        }
    
        return child;
    }
    
    public TrieNode<T> removeChild(char key) {
        return children.remove(key);
    }
    public boolean isEndState() { return end_state; }
    
    public boolean hasValue() { return value != null; }
    
    public boolean hasChildren() { return !children.isEmpty(); }
    public class Trie<T> {
        private final TrieNode<T> root;
    
        public Trie() {
            root = new TrieNode<>(null, (char)0);
        }
    
        public TrieNode<T> getRoot() { return root; }
    }
    /** @return the node with the specific key if exists; otherwise, {@code null}. */
    public TrieNode<T> find(String key) {
        TrieNode<T> node = root;
    
        for (char c : key.toCharArray()) {
            node = node.getChild(c);
            if (node == null) return null;
        }
    
        return node;
    }
    public T get(String key) {
        TrieNode<T> node = find(key);
        return (node != null && node.isEndState()) ? node.getValue() : null;
    }
    
    public boolean contains(String key) {
        return get(key) != null;
    }
    public T put(String key, T value) {
        TrieNode<T> node = root;
    
        for (char c : key.toCharArray())
            node = node.addChild(c);
    
        node.setEndState(true);
        return node.setValue(value);
    }
    public boolean remove(String key) {
        TrieNode<T> node = find(key);
    
        if (node == null || !node.isEndState())
            return false;
    
        if (node.hasChildren()) {
            node.setEndState(false);
            return true;
        }
    
        TrieNode<T> parent = node.getParent();
    
        while (parent != null) {
            parent.removeChild(node.getKey());
    
            if (parent.hasChildren() || parent.isEndState())
                break;
            else {
                node = parent;
                parent = parent.getParent();
            }
        }
    
        return true;
    }
    String s = "";
    if (sign == Sign.NEGATIVE) s += "-";
    for (int i = digits.length - 1; i >= 0; i--)
        s += digits[i];
    return s;
    public class LongInteger extends SignedNumeral<LongInteger> {
        /** The values of this integer (excluding the sign). */
        protected byte[] digits;
        ...
    /** Creates a long integer with the default value of "0". */
    public LongInteger() {
        this("0");
    }
    
    /**
     * Creates a long integer by copying the specific object.
     * @param n the object to be copied.
     */
    public LongInteger(LongInteger n) {
        super(n.sign);
        digits = Arrays.copyOf(n.digits, n.digits.length);
    }
    
    /**
     * Creates a long integer with the specific sign and values.
     * @param n the sign and values to be set.
     * @see #set(String)
     */
    public LongInteger(String n) {
        set(n);
    }
    /**
     * Sets the sign and values of this integer.
     * @param n the sign and values to be set.
     * @throws NullPointerException when `n` is null.
     * @throws InvalidParameterException when `n` contains non-digit character
     *         except for the first character that can be [+-\d].
     */
    public void set(String n) {
        // 'n' must not be null
        if (n == null)
            throw new NullPointerException();
    
        // set this.sign
        sign = switch (n.charAt(0)) {
            case '-' -> { n = n.substring(1); yield Sign.NEGATIVE; }
            case '+' -> { n = n.substring(1); yield Sign.POSITIVE; }
            default -> Sign.POSITIVE;
        };
    
        // set this.digits
        digits = new byte[n.length()];
    
        for (int i = 0, j = n.length() - 1; i < n.length(); i++, j--) {
            byte v = (byte)(n.charAt(i) - 48);
            if (0 > v || v > 9) {
                String s = String.format("%d is not a valid value", v);
                throw new InvalidParameterException(s);
            }
            digits[j] = v;
        }
    }
    @Override
    public void add(LongInteger n) {
        if (sign == n.sign)
            addSameSign(n);
        else
            addDifferentSign(n);
    }
    /**
     * Adds the specific integer that has the same sign as this integer.
     * @param n the integer to be added with the same sign.
     */
    protected void addSameSign(LongInteger n) {
        // copy this integer to result[]
        int m = Math.max(digits.length, n.digits.length);
        byte[] result = new byte[m + 1];
        System.arraycopy(digits, 0, result, 0, digits.length);
    
        // add n to result
        for (int i = 0; i < n.digits.length; i++) {
            if (i < n.digits.length)
                result[i] += n.digits[i];
            if (result[i] >= 10) {
                result[i] -= 10;
                result[i + 1] += 1;
            }
        }
    
        // set this.digits
        digits = result[m] == 0 ? Arrays.copyOf(result, m) : result;
    }
    /**
     * Adds the specific integer that has a different sign from this integer.
     * @param n the integer to be added with a different sign
     */
    protected void addDifferentSign(LongInteger n) {
        throw new UnsupportedOperationException();
    }
    @Override
    public void multiply(LongInteger n) {
        // set this.sign
        sign = (sign == n.sign) ? Sign.POSITIVE : Sign.NEGATIVE;
    
        // multiply this and n and save it to result
        byte[] result = new byte[digits.length + n.digits.length];
        for (int i = 0; i < digits.length; i++) {
            for (int j = 0; j < n.digits.length; j++) {
                int k = i + j, prod = digits[i] * n.digits[j];
                result[k] += prod;
                result[k + 1] += result[k] / 10;
                result[k] %= 10;
            }
        }
    
        // set this.digits
        int m; for (m = result.length - 1; m > 0; m--)
            if (result[m] != 0) break;
        digits = ++m < result.length ? Arrays.copyOf(result, m) : result;
    }
    public class LongIntegerRun {
        static public void main(String[] args) {
            System.out.println(args);
        }
    }
    [Ljava.lang.String;@d716361
    static public void main(String[] args) {
        System.out.println(Arrays.toString(args));
    }
    []
    [123, -456]
    static public void main(String[] args) {    
        LongInteger a = new LongInteger(args[0]);
        LongInteger b = new LongInteger(args[1]);
        
        System.out.println(a);
        System.out.println(b);
    }
    edu.emory.cs.algebraic.LongInteger@4d7e1886
    edu.emory.cs.algebraic.LongInteger@3cd1a2f1
    public class LongInteger extends SignedNumeral<LongInteger> {
        ...
        @Override
        public String toString() {
            StringBuilder build = new StringBuilder();
            if (sign == Sign.NEGATIVE) build.append("-");
            for (int i = digits.length - 1; i >= 0; i--)
                build.append(digits[i]);
            return build.toString();
        }
        ...
    123
    -456
    boolean c = a < b;  // gives a compile error
    public class LongInteger extends SignedNumeral<LongInteger> 
                             implements Comparable<LongInteger> {
    ...
    }
    @Override
    public int compareTo(LongInteger n) {
        if (isPositive())
            return n.isNegative() ? 1 : compareAbs(n);
        else
            return n.isPositive() ? -1 : -compareAbs(n);
    }
    /**
     * @param n the object to be compared.
     * @return a negative integer, zero, or a positive integer as the absolute value of this object is
     * less than, equal to, or greater than the absolute value of the specified object.
     */
    public int compareAbs(LongInteger n) {
        int diff = digits.length - n.digits.length;
    
        if (diff == 0) {
            for (int i = digits.length - 1; i >= 0; i--) {
                diff = digits[i] - n.digits[i];
                if (diff != 0) break;
            }
        }
    
        return diff;
    }
    static public void main(String[] args) {
        List<LongInteger> list = new ArrayList<>();
        
        list.add(new LongInteger("78"));
        list.add(new LongInteger("-45"));
        list.add(new LongInteger("0"));
        list.add(new LongInteger("6"));
        list.add(new LongInteger("-0"));
        list.add(new LongInteger("-123"));
        
        list.sort(Comparator.naturalOrder());
        System.out.println(list);
    
        list.sort(Comparator.reverseOrder());
        System.out.println(list);
    }
    [-123, -45, -0, 0, 6, 78]
    [78, 6, 0, -0, -45, -123]
    public class SelectionSort<T extends Comparable<T>> extends AbstractSort<T> {
        public SelectionSort() {
            this(Comparator.naturalOrder());
        }
    
        public SelectionSort(Comparator<T> comparator) {
            super(comparator);
        }
    }
    @Override
    public void sort(T[] array, final int beginIndex, final int endIndex) {
        for (int i = endIndex; i > beginIndex; i--) {
            int max = beginIndex;
    
            for (int j = beginIndex + 1; j < i; j++) {
                if (compareTo(array, j, max) > 0)
                    max = j;
            }
    
            swap(array, max, i - 1);
        }
    }
    public class HeapSort<T extends Comparable<T>> extends AbstractSort<T> {
        public HeapSort() {
            this(Comparator.naturalOrder());
        }
    
        public HeapSort(Comparator<T> comparator) {
            super(comparator);
        }
    }
    private void sink(T[] array, int k, int beginIndex, int endIndex) {
        for (int i = getLeftChildIndex(beginIndex, k); i < endIndex; k = i, i = getLeftChildIndex(beginIndex, k)) {
            if (i + 1 < endIndex && compareTo(array, i, i + 1) < 0) i++;
            if (compareTo(array, k, i) >= 0) break;
            swap(array, k, i);
        }
    }
    
    private int getParentIndex(int beginIndex, int k) {
        return beginIndex + (k - beginIndex - 1) / 2;
    }
    
    private int getLeftChildIndex(int beginIndex, int k) {
        return beginIndex + 2 * (k - beginIndex) + 1;
    }
    @Override
    public void sort(T[] array, int beginIndex, int endIndex) {
        // heapify all elements
        for (int k = getParentIndex(beginIndex, endIndex - 1); k >= beginIndex; k--)
            sink(array, k, beginIndex, endIndex);
    
        // swap the endIndex element with the root element and sink it
        while (endIndex > beginIndex + 1) {
            swap(array, beginIndex, --endIndex);
            sink(array, beginIndex, beginIndex, endIndex);
        }
    }
    public class InsertionSort<T extends Comparable<T>> extends AbstractSort<T> {
        public InsertionSort() {
            this(Comparator.naturalOrder());
        }
    
        public InsertionSort(Comparator<T> comparator) {
            super(comparator);
        }
    }
    protected void sort(T[] array, int beginIndex, int endIndex, final int h) {
        int begin_h = beginIndex + h;
    
        for (int i = begin_h; i < endIndex; i++)
            for (int j = i; begin_h <= j && compareTo(array, j, j - h) < 0; j -= h)
                swap(array, j, j - h);
    }
    @Override
    public void sort(T[] array, int beginIndex, int endIndex) {
        sort(array, beginIndex, endIndex, 1);
    }
    public abstract class ShellSort<T extends Comparable<T>> extends InsertionSort<T> {
        protected List<Integer> sequence;
    
        public ShellSort(Comparator<T> comparator) {
            super(comparator);
            sequence = new ArrayList<>();
            populateSequence(10000);
        }
    }
    /**
     * Populates the gap sequence with respect to the size of the list.
     * @param n the size of the list to be sorted.
     */
    protected abstract void populateSequence(int n);
    
    /**
     * @param n the size of the list to be sorted.
     * @return the starting index of the sequence with respect to the size of the list.
     */
    protected abstract int getSequenceStartIndex(int n);
    @Override
     public void sort(T[] array, int beginIndex, int endIndex) {
         int n = endIndex - beginIndex;
         populateSequence(n);
    
         for (int i = getSequenceStartIndex(n); i >= 0; i--)
             sort(array, beginIndex, endIndex, sequence.get(i));
     }
    public class ShellSortKnuth<T extends Comparable<T>> extends ShellSort<T> {
        public ShellSortKnuth() {
            this(Comparator.naturalOrder());
        }
    
        public ShellSortKnuth(Comparator<T> comparator) {
            super(comparator);
        }
    }
    @Override
    protected void populateSequence(int n) {
        n /= 3;
    
        for (int t = sequence.size() + 1; ; t++) {
            int h = (int) ((Math.pow(3, t) - 1) / 2);
            if (h <= n) sequence.add(h);
            else break;
        }
    }
    
    @Override
    protected int getSequenceStartIndex(int n) {
        int index = Collections.binarySearch(sequence, n / 3);
        if (index < 0) index = -(index + 1);
        if (index == sequence.size()) index--;
        return index;
    }
    package edu.emory.cs.algebraic;
    
    public class Numeral {
    }
    public class Numeral {
        /**
         * Adds `n` to this numeral.
         * @param n the numeral to be added.
         */
        public void add(Numeral n) { /* cannot be implemented */ }
    }
    public interface Numeral {
        void add(Numeral n);
    }
    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();
        }
    }
    default void subtract(Numeral n) {
        ((SignedNumeral)n).flipSign();
        add(n);
        ((SignedNumeral)n).flipSign();
    }
    default void subtract(SignedNumeral n) {
        n.flipSign();
        add(n);
        n.flipSign();
    }
    public interface SignedNumeral extends Numeral {
        @Override
        void add(SignedNumeral n);
        ...
    public interface SignedNumeral extends Numeral {
        void add(SignedNumeral n);
        ...
    public interface Numeral<T extends Numeral<T>> {
        void add(T n);
    }
    public interface SignedNumeral extends Numeral<SignedNumeral> {
        void flipSign();
    
        default void subtract(SignedNumeral n) {
            n.flipSign();
            add(n);
            n.flipSign();
        }
    }
    void add(SignedNumeral n);
    public class LongInteger implements SignedNumeral {
        @Override
        public void flipSign() { /* to be implemented */ }
    
        @Override
        public void add(SignedNumeral n) { /* to be implemented */ }
    }
    public interface SignedNumeral<T extends SignedNumeral<T>> extends Numeral<T> {
        void flipSign();
    
        default void subtract(T n) {
            n.flipSign();
            add(n);
            n.flipSign();
        }
    }
    public enum Sign {
        POSITIVE,
        NEGATIVE;
    }
    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;
        }
    }
    public interface SignedNumeral<T extends SignedNumeral<T>> extends Numeral<T> {
        Sign sign = Sign.POSITIVE;
        ...
    /** Flips the sign of this numeral. */
    default void flipSign() {
        sign = (sign == Sign.POSITIVE) ? Sign.NEGATIVE : Sign.POSITIVE;
    }
    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;
        }
        ...
        ...
        /** @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);
    }
    public class BinaryHeap<T extends Comparable<T>> extends AbstractPriorityQueue<T> {
        private final List<T> keys;
    
        public BinaryHeap() {
            this(Comparator.naturalOrder());
        }
    
        public BinaryHeap(Comparator<T> priority) {
            super(priority);
            keys = new ArrayList<>();
            keys.add(null);
        }
    
        @Override
        public int size() {
            return keys.size() - 1;
        }
    }
    /**
     * @param i1 the index of the first key in {@link #keys}.
     * @param i2 the index of the second key in {@link #keys}.
     * @return a negative integer, zero, or a positive integer as the first key is
     * less than, equal to, or greater than the second key.
     */
    private int compare(int k1, int k2) {
        return priority.compare(keys.get(k1), keys.get(k2));
    }
    @Override
    public void add(T key) {
        keys.add(key);
        swim(size());
    }
    
    private void swim(int k) {
        for (; 1 < k && compare(k / 2, k) < 0; k /= 2)
            Collections.swap(keys, k / 2, k);
    }
    @Override
    public T remove() {
        if (isEmpty()) return null;
        Collections.swap(keys, 1, size());
        T max = keys.remove(size());
        sink();
        return max;
    }
    
    private void sink() {
        for (int k = 1, i = 2; i <= size(); k = i, i *= 2) {
            if (i < size() && compare(i, i + 1) < 0) i++;
            if (compare(k, i) >= 0) break;
            Collections.swap(keys, k, i);
        }
    }
    public abstract class AbstractPriorityQueue<T extends Comparable<T>> {
        protected final Comparator<T> priority;
    
        /**
         * Initializes this PQ as either a maximum or minimum PQ.
         * @param priority if {@link Comparator#naturalOrder()}, this is a max PQ;
         *                 if {@link Comparator#reverseOrder()}, this is a min PQ.
         */
        public AbstractPriorityQueue(Comparator<T> priority) {
            this.priority = priority;
        }
    }
    /**
     * Adds a comparable key to this PQ.
     * @param key the key to be added.
     */
    abstract public void add(T key);
    
    /**
     * Removes the key with the highest/lowest priority if exists.
     * @return the key with the highest/lowest priority if exists; otherwise, null.
     */
    abstract public T remove();
    
    /** @return the size of this PQ. */
    abstract public int size();
    /** @return true if this PQ is empty; otherwise, false. */
    public boolean isEmpty() {
        return size() == 0;
    }
    public class LazyPriorityQueue<T extends Comparable<T>> extends AbstractPriorityQueue<T> {
        private final List<T> keys;
    
        /** Initializes this as a maximum PQ. */
        public LazyPriorityQueue() {
            this(Comparator.naturalOrder());
        }
    
        /** @see AbstractPriorityQueue#AbstractPriorityQueue(Comparator). */
        public LazyPriorityQueue(Comparator<T> priority) {
            super(priority);
            keys = new ArrayList<>();
        }
        
        
        @Override
        public int size() {
            return keys.size();
        }
    }
    /**
     * Appends a key to {@link #keys}.
     * @param key the key to be added.
     */
    @Override
    public void add(T key) {
        keys.add(key);
    }
    
    /**
     * Finds the key with the highest/lowest priority, and removes it from {@link #keys}.
     * @return the key with the highest/lowest priority if exists; otherwise, null.
     */
    @Override
    public T remove() {
        if (isEmpty()) return null;
        T max = Collections.max(keys, priority);
        keys.remove(max);
        return max;
    }
    public class EagerPriorityQueue<T extends Comparable<T>> extends AbstractPriorityQueue<T> {
        private final List<T> keys;
    
        public EagerPriorityQueue() {
            this(Comparator.naturalOrder());
        }
    
        public EagerPriorityQueue(Comparator<T> priority) {
            super(priority);
            keys = new ArrayList<>();
        }
        
        @Override
        public int size() {
            return keys.size();
        }
    }
    /**
     * Adds a key to {@link #keys} by the priority.
     * @param key the key to be added.
     */
    @Override
    public void add(T key) {
        // binary search (if not found, index < 0)
        int index = Collections.binarySearch(keys, key, priority);
        // if not found, the appropriate index is {@code -(index +1)}
        if (index < 0) index = -(index + 1);
        keys.add(index, key);
    }
    
    /**
     * Remove the last key in the list.
     * @return the key with the highest priority if exists; otherwise, {@code null}.
     */
    @Override
    public T remove() {
        return isEmpty() ? null : keys.remove(keys.size() - 1);
    }
    /**
     * @param pq   a priority queue.
     * @param keys a list of comparable keys.
     * @param comp a comparator used for sorting.
     */
    <T extends Comparable<T>> void testRobustness(AbstractPriorityQueue<T> pq, List<T> keys, Comparator<T> comp) {
        keys.forEach(pq::add);
        keys = keys.stream().sorted(comp).collect(Collectors.toList());
        keys.forEach(key -> assertEquals(key, pq.remove()));
    }
    for (int i=0; i<keys.size(); i++)
        pq.add(keys.get(i));
    for (T key : keys)
        pq.add(key);
    keys.forEach(key -> pq.add(key));
    keys.forEach(pq::add);
    @Test
    public void testRobustness() {
        List<Integer> keys = List.of(4, 1, 3, 2, 5, 6, 8, 3, 4, 7, 5, 9, 7);
        Comparator<Integer> natural = Comparator.naturalOrder();
        Comparator<Integer> reverse = Comparator.reverseOrder();
    
        testRobustness(new LazyPriorityQueue<>(), keys, reverse);
        testRobustness(new EagerPriorityQueue<>(), keys, reverse);
        testRobustness(new BinaryHeap<>(), keys, reverse);
    
        testRobustness(new LazyPriorityQueue<>(reverse), keys, natural);
        testRobustness(new EagerPriorityQueue<>(reverse), keys, natural);
        testRobustness(new BinaryHeap<>(reverse), keys, natural);
    }
    static class Time {
        long add = 0;
        long remove = 0;
    }
    <T extends Comparable<T>> void addRuntime(AbstractPriorityQueue<T> queue, Time time, List<T> keys) {
        long st, et;
    
        // runtime for add()
        st = System.currentTimeMillis();
        keys.forEach(queue::add);
        et = System.currentTimeMillis();
        time.add += et - st;
    
        // runtime for remove()
        st = System.currentTimeMillis();
        while (!queue.isEmpty()) queue.remove();
        et = System.currentTimeMillis();
        time.remove += et - st;
    }
    <T extends Comparable<T>> Time[] benchmark(AbstractPriorityQueue<T>[] queues, int size, int iter, Supplier<T> sup) {
        Time[] times = Stream.generate(Time::new).limit(queues.length).toArray(Time[]::new);
    
        for (int i = 0; i < iter; i++) {
            List<T> keys = Stream.generate(sup).limit(size).collect(Collectors.toList());
            for (int j = 0; j < queues.length; j++)
                addRuntime(queues[j], times[j], keys);
        }
    
        return times;
    }
    @SafeVarargs
    final void testRuntime(AbstractPriorityQueue<Integer>... queues) {
        final int begin_size = 1000;
        final int end_size = 10000;
        final int inc = 1000;
        final Random rand = new Random();
    
        for (int size = begin_size; size <= end_size; size += inc) {
            // JVM warm-up
            benchmark(queues, size, 10, rand::nextInt);
            // benchmark all priority queues with the same keys
            Time[] times = benchmark(queues, size, 1000, rand::nextInt);
    
            StringJoiner joiner = new StringJoiner("\t");
            joiner.add(Integer.toString(size));
            joiner.add(Arrays.stream(times).map(t -> Long.toString(t.add)).collect(Collectors.joining("\t")));
            joiner.add(Arrays.stream(times).map(t -> Long.toString(t.remove)).collect(Collectors.joining("\t")));
            System.out.println(joiner.toString());
        }
    }
    public class RedBlackNode<T extends Comparable<T>> extends AbstractBinaryNode<T, RedBlackNode<T>> {
        private boolean is_red;
    
        public RedBlackNode(T key) {
            super(key);
            setToRed();
        }
    
        public void setToRed() { is_red = true; }
    
        public void setToBlack() { is_red = false; }
    
        public boolean isRed() { return is_red; }
    
        public boolean isBlack() { return !is_red; }
    }
    public class RedBlackTree<T extends Comparable<T>> extends AbstractBalancedBinarySearchTree<T, RedBlackNode<T>> {
        @Override
        public RedBlackNode<T> createNode(T key) {
            return new RedBlackNode<T>(key);
        }
    }
    @Override
    protected void balance(RedBlackNode<T> node) {
        if (isRoot(node))
            node.setToBlack();
        else if (node.getParent().isRed()) {
            RedBlackNode<T> uncle = node.getUncle();
    
            if (uncle != null && uncle.isRed())
                balanceWithRedUncle(node, uncle);
            else
                balanceWithBlackUncle(node);
        }
    }
    private void balanceWithRedUncle(RedBlackNode<T> node, RedBlackNode<T> uncle) {
        node.getParent().setToBlack();
        uncle.setToBlack();
        RedBlackNode<T> grandParent = node.getGrandParent();
        grandParent.setToRed();
        balance(grandParent);
    }
    private void balanceWithBlackUncle(RedBlackNode<T> node) {
        RedBlackNode<T> grandParent = node.getGrandParent();
    
        if (grandParent != null) {
            RedBlackNode<T> parent = node.getParent();
    
            if (grandParent.isLeftChild(parent) && parent.isRightChild(node)) {
                rotateLeft(parent);
                node = parent;
            }
            else if (grandParent.isRightChild(parent) && parent.isLeftChild(node)) {
                rotateRight(parent);
                node = parent;
            }
    
            node.getParent().setToBlack();
            grandParent.setToRed();
    
            if (node.getParent().isLeftChild(node))
                rotateRight(grandParent);
            else
                rotateLeft(grandParent);
        }
    }
    public class AVLNode<T extends Comparable<T>> extends AbstractBinaryNode<T, AVLNode<T>> {
        private int height;
    
        public AVLNode(T key) {
            super(key);
            height = 1;
        }
    
        public int getHeight() {
            return height;
        }
    
        public void setHeight(int height) {
            this.height = height;
        }
    }
    @Override
    public void setLeftChild(AVLNode<T> node) {
        super.setLeftChild(node);
        resetHeights();
    }
    
    @Override
    public void setRightChild(AVLNode<T> node) {
        super.setRightChild(node);
        resetHeights();
    }
    
    /** Resets the heights of this node and its ancestor, recursively. */
    public void resetHeights() {
        resetHeightsAux(this);
    }
    
    private void resetHeightsAux(AVLNode<T> node) {
        if (node != null) {
            int lh = node.hasLeftChild() ? node.getLeftChild().getHeight() : 0;
            int rh = node.hasRightChild() ? node.getRightChild().getHeight() : 0;
            int height = Math.max(lh, rh) + 1;
    
            if (height != node.getHeight()) {
                node.setHeight(height);
                resetHeightsAux(node.getParent());  // recursively update parent height
            }
        }
    }
    /** @return height(left-subtree) - height(right-subtree). */
    public int getBalanceFactor() {
        if (hasBothChildren())
            return left_child.getHeight() - right_child.getHeight();
        else if (hasLeftChild())
            return left_child.getHeight();
        else if (hasRightChild())
            return -right_child.getHeight();
        else
            return 0;
    }
    public class AVLTree<T extends Comparable<T>> extends AbstractBalancedBinarySearchTree<T, AVLNode<T>> {
        @Override
        public AVLNode<T> createNode(T key) {
            return new AVLNode<>(key);
        }
    
        @Override
        protected void rotateLeft(AVLNode<T> node) {
            super.rotateLeft(node);
            node.resetHeights();
        }
    
        @Override
        protected void rotateRight(AVLNode<T> node) {
            super.rotateRight(node);
            node.resetHeights();
        }
    }
    @Override
    protected void balance(AVLNode<T> node) {
        if (node == null) return;
        int bf = node.getBalanceFactor();
    
        if (bf == 2) {
            AVLNode<T> child = node.getLeftChild();
    
            if (child.getBalanceFactor() == -1)
                rotateLeft(child);
    
            rotateRight(node);
        }
        else if (bf == -2) {
            AVLNode<T> child = node.getRightChild();
    
            if (child.getBalanceFactor() == 1)
                rotateRight(child);
    
            rotateLeft(node);
        }
        else
            balance(node.getParent());
    }
    public int find(int id) {
        return (subsets[id] < 0) ? id : find(subsets[id]);
    }
    public int find(int id) {
        return (subsets[id] < 0) ? id : (subsets[id] = find(subsets[id]));
    }
    public class Edge implements Comparable<Edge> {
        private int source;
        private int target;
        private double weight;
    
        public Edge(int source, int target, double weight) {
            init(source, target, weight);
        }
    
        public Edge(int source, int target) {
            this(source, target, 0);
        }
    
        public Edge(Edge edge) {
            this(edge.getSource(), edge.getTarget(), edge.getWeight());
        }
    }
    private void init(int source, int target, double weight) {
        setSource(source);
        setTarget(target);
        setWeight(weight);
    }
    
    public int getSource() { return source; }
    public int getTarget() { return target; }
    public double getWeight() { return weight; }
    
    public void setSource(int vertex) { source = vertex; }
    public void setTarget(int vertex) { target = vertex; }
    public void setWeight(double weight) { this.weight = weight; }
    public void addWeight(double weight) { this.weight += weight; }
    @Override
    public int compareTo(Edge edge) {
        double diff = weight - edge.weight;
        if (diff > 0) return 1;
        else if (diff < 0) return -1;
        else return 0;
    }
    public class Graph {
        private final List<List<Edge>> incoming_edges;
    
        public Graph(int size) {
            incoming_edges = Stream.generate(ArrayList<Edge>::new).limit(size).collect(Collectors.toList());
        }
        
        public Graph(Graph g) {
            incoming_edges = g.incoming_edges.stream().map(ArrayList::new).collect(Collectors.toList());
        }
        
        public boolean hasNoEdge() {
            return IntStream.range(0, size()).allMatch(i -> getIncomingEdges(i).isEmpty());
        }
        
        public int size() {
            return incoming_edges.size();
        }
    }
    incoming_edges.set(0, new ArrayList());
    incoming_edges.set(1, new ArrayList(new Edge(0, 1), new Edge(2, 1));
    incoming_edges.set(2, new ArrayList(new Edge(0, 2)));
    public Edge setDirectedEdge(int source, int target, double weight) {
        List<Edge> edges = getIncomingEdges(target);
        Edge edge = new Edge(source, target, weight);
        edges.add(edge);
        return edge;
    }
    
    public void setUndirectedEdge(int source, int target, double weight) {
        setDirectedEdge(source, target, weight);
        setDirectedEdge(target, source, weight);
    }
    public List<Edge> getIncomingEdges(int target) {
        return incoming_edges.get(target);
    }
    
    public List<Edge> getAllEdges() {
        return incoming_edges.stream().flatMap(List::stream).collect(Collectors.toList());
    }
    
    public Deque<Integer> getVerticesWithNoIncomingEdges() {
        return IntStream.range(0, size()).filter(i -> getIncomingEdges(i).isEmpty()).boxed().collect(Collectors.toCollection(ArrayDeque::new));
    }
    public List<Deque<Edge>> getOutgoingEdges() {
        List<Deque<Edge>> outgoing_edges = Stream.generate(ArrayDeque<Edge>::new).limit(size()).collect(Collectors.toList());
    
        for (int target = 0; target < size(); target++) {
            for (Edge incoming_edge : getIncomingEdges(target))
                outgoing_edges.get(incoming_edge.getSource()).add(incoming_edge);
        }
    
        return outgoing_edges;
    }
    public List<Integer> topological_sort(boolean depth_first) {
        Deque<Integer> global = getVerticesWithNoIncomingEdges();
        List<Deque<Edge>> outgoingEdgesAll = getOutgoingEdges();
        List<Integer> order = new ArrayList<>();
    
        while (!global.isEmpty()) {
            Deque<Integer> local = new ArrayDeque<>();
            int vertex = global.poll();
            order.add(vertex);
            Deque<Edge> outgoingEdges = outgoingEdgesAll.get(vertex);
            
            while (!outgoingEdges.isEmpty()) {
                Edge edge = outgoingEdges.poll();
    
                //Remove edge in all incomingEdges of the target vertex
                List<Edge> incomingEdges = getIncomingEdges(edge.getTarget());
                incomingEdges.remove(edge);
    
                //If the vertex has no incoming edges, add it to the local queue awaited to be added to the global deque
                if (incomingEdges.isEmpty())
                    local.add(edge.getTarget());
            }
    
            //Transfer all vertices in local to global
            while (!local.isEmpty())
                if (depth_first) global.addFirst(local.removeLast());
                else global.addLast(local.removeFirst());
        }
    
        if (!hasNoEdge()) throw new IllegalArgumentException("Cyclic graph.");
        return order;
    }
    Tasks
    • Create a class called MSTAllHWarrow-up-right that inherits the MSTAllarrow-up-right interface.

    • Override the getMinimumSpanningTrees() method.

    • Feel free to use any classes in the grapharrow-up-right package without modifying.

    • Write a report that explains the logic and worst-case complexity of your program and save it as hw3.pdf.

    circle-info

    You may want to start with the implementation of either Prim's or Kruskal's algorithm and update the code to find all minimum spanning trees.

    hashtag
    Extra Credit

    • Create an interesting graph and submit both the source code (as in MSTAllHWTestarrow-up-right) and the diagram representing your graph (up to 1 point).

    hashtag
    Notes

    • Test your code with graphs consisting of zero to many spanning trees.

    • Your output must include only minimum spanning trees.

    • Your output should not include redundant trees. For example, if there are three vertices, 0, 1, and 2, {0 -> 1, 0 -> 2} is considered the same as {0 -> 1, 0 <- 2} or {0 <- 1, 0 -> 2} or {0 <-1, 0 <- 2}.

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

    L24: gets the ASCIIarrow-up-right 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: String.format()arrow-up-right is a static method in String.

  • Arrays.copyOf()arrow-up-right
    InvalidParameterExceptionarrow-up-right
    switcharrow-up-right
    charAt()arrow-up-right
    substring()arrow-up-right
    Math.max()arrow-up-right
    System.arraycopy()arrow-up-right
    sort()arrow-up-right
    Comparator.naturalOrder()arrow-up-right
    sort()arrow-up-right
    Comparator.reverseOrder()arrow-up-right
    javadocarrow-up-right
    O(n)O(n)O(n)
    O(n+n)=O(n)O(n+n) = O(n)O(n+n)=O(n)
    O(n)O(n)O(n)
    O(1)O(1)O(1)
    naturalOrder()arrow-up-right
    reverseOrder()arrow-up-right
    Collections.max()arrow-up-right
    Collections.binarySearch()arrow-up-right
    →\rightarrow→
    →\rightarrow→
    Merge Sortarrow-up-right
    Tim Sortarrow-up-right
    Quick Sortarrow-up-right
    Intro Sortarrow-up-right
    Bucket Sortarrow-up-right
    Radix Sortarrow-up-right
    Collectorarrow-up-right
    toList()arrow-up-right
    Red-Black Treesarrow-up-right
    Cycle Detection
    Topological Sorting
    Quiz
    Cycle Detectionarrow-up-right
    Topological Sortingarrow-up-right
    Prim's Algorithm
    Kruskal's Algorithm
    Chu-Liu-Edmonds' Algorithm
    Quiz
    Homework
    Prim's Algorithmarrow-up-right
    Kruskal's Algorithmarrow-up-right
    Chu-Liu-Edmonds' Algorithmarrow-up-right
    : swaps the maximum key with the beginning key in the range
    .
  • L10: sinks to heapify →O(log⁡n)\rightarrow O(\log n)→O(logn).

  • →O(n)\rightarrow O(n)→O(n)
    →O(1)\rightarrow O(1)→O(1)
    →O(log⁡n)\rightarrow O(\log n)→O(logn)
    →O(nlog⁡n)\rightarrow O(n \log n)→O(nlogn)
    →O(n)\rightarrow O(n)→O(n)
    n/2k⇒{500,250,125,…}n / 2^k \Rightarrow \{500, 250, 125, \ldots\}n/2k⇒{500,250,125,…}
    n=1000n = 1000n=1000
    O(n)O(n)O(n)
    O(n2)O(n^2)O(n2)
    O(n)O(n)O(n)
    O(log⁡n)O(\log n)O(logn)
    O(nlog⁡n)O(n\log n)O(nlogn)
    O(nlog⁡n)O(n \log n)O(nlogn)
    O(n2)O(n^2)O(n2)
    O(n2)O(n^2)O(n2)
    O(n1.5)O(n^{1.5})O(n1.5)
    O(n1.5)O(n^{1.5})O(n1.5)
    →O(1)\rightarrow O(1)→O(1)
    : breaks if the parent has a higher priority than the child.
  • L14: swaps if the child has a higher priority than the parent.

  • Collections.swap()arrow-up-right
    : creates a stream specified by the supplier.
  • limit()arrow-up-right: creates a stream with a specific size.

  • toArray()arrow-up-right: returns an array of the specific type.

  • : exercise.
    currentTimeMillis()arrow-up-right
    Supplierarrow-up-right
    randomarrow-up-right
    StringJoinerarrow-up-right
    generate()arrow-up-right
    : the case of right linear.
    L12: iterates through all the outgoing edges of the source vertex:
    • L17: removes the outgoing edge from the graph.

    • L20-21: adds the target vertex if it has no incoming edge left to local.

  • L25-27: adds all vertices in local to global.

  • L6-7
    : returns true if the source vertex of this edge has seen before.
  • L9-10: returns true if the recursive call finds a cycle.

  • : contains the total weight of this spanning tree.
    SpanningTreearrow-up-right
    graph.spanarrow-up-right
    Comparablearrow-up-right
    MSTarrow-up-right
    hashtag
    Ford-Fulkerson

    Ford-Fulkerson algorithm finds the maximum flow from a flow network as follows:

    Let us create the FordFulkersonarrow-up-right class:

    • L2: indicates one source and one target vertices.

    • L6: iterates as long as it can find an augmenting path

      • L7: finds the edge with the minimum capacity in the augmenting path.

      • L8: updates the edges in the path with the flow.

    Let us define the getMin() method:

    Finally, let us define the getAugmentingPath() method:

    • L2: once the source reaches the target, it found an augmenting path.

    • L6: adding the source vertex would cause a cycle.

    • L7: cannot push the flow when there is no residual left.

    • L10: recursively finds the augmenting path by switching the target.

    hashtag
    Backward Pushing

    Let us consider the following graph:

    As shown, our implementation of Ford-Fulkerson Algorithm does not always guarantee to find the maximum flow correctly. To fix this issue, we need to implement backward pushing:

    The backward pushing can be performed after the applying the flow to all edges as in the implementation above (see the code in the "With Backward Pushing" tab).

    Finally, the updateBackward() method can be implemented as follows:

    Subgrapharrow-up-right

    L11: iterates through all incoming edges:

    • L14: if the visited set contains the source vertex, adding the edge would create a cycle.

    • L16: if the tree contains v-1 number of edges, a spanning tree is found.

    • L17: repeats the process by adding the source of the edge.

    The add() method can be defined as follows:

    • L4-7: adds all incoming edges that have not been visited to the queue.

    What is the worst-case complexity of Prim's Algorithm?

    MSTPrimarrow-up-right
    If there is no cycle, go to #5.
  • If there is a cycle,

    1. Merge subtrees with the cycle into one and update scores for all incoming edges to this merged subtree, and goto #2.

    2. For each vertex in the subtree, add the weight of its outgoing edge chain to its incoming edges not in the subtree.

  • Break all cycles by removing edges that cause multiple parents.

  • The following demonstrates how Chu-Liu-Edmonds' Algorithm find a minimum spanning tree:

    What is the logic behind updating the edge weights related to the cycles?

    The following explains the weight updates in details:

    L5: creates a forest where each vertex is considered a separate tree.

  • L8: iterates through all edges:

    • L11: if the target and source vertices in the edge are not in the same tree.

    • L13: if the tree contains v-1 number of edges, a spanning tree is found.

    • L14: merges the target and source vertices into the same tree.

  • MSTKruskalarrow-up-right
    Ford-Fulkerson Algorithm
  • Simplex Algorithm

  • Quiz

  • hashtag
    References

    • Network Flowarrow-up-right

    • Flow Networkarrow-up-right

    • Maximum Flowarrow-up-right

    Flow Networks
    allMatch()arrow-up-right
    topological_sort()arrow-up-right

    9.3. Quiz

    This section provides exercises for better understanding in network flow

    hashtag
    Augmenting Paths

    An augmenting path is a directed path between the source and target vertices; no need to worry about residuals for this quiz.

    • Create the NetworkFlowQuizarrow-up-right class under the package.

    • Update the getAugmentingPaths() method that returns all augmenting paths between the specific source and target vertices using depth-first traverse.

    hashtag
    Extra Credit

    • Create the NetworkFlowQuizExtra class and update the getAugmentingPaths() method such that it uses breadth-first traverse instead of depth-first traverse.

    hashtag
    Simplex Algorithm

    Given the following graph where there are two source vertices, and , and two target vertices, and , define the objective functions and their constraints to find the (1) max-flow and (2) min-cut using the simplex algorithm:

    hashtag
    Report

    Write quiz8 that includes:

    • The worst-case complexity of your getAugmentingPaths() method.

    • An explanation of when the depth-first traverse is preferred over the breadth-first traverse and vice versa to find augmenting paths.

    • An objective function and is constraints to find max-flow.

    10.4. Quiz

    This section provides exercises for better understanding in dynamic programming.

    hashtag
    Tower of Hanoi

    Write a report quiz9.pdf that includes answers to the followings.

    • As n increases from 1 to 10, how many times does the auxiliary solve() method get called recursively in and ?

    • Is there clear patterns between n and the number of the method calls made by these classes? Explain the patterns if exist.

    hashtag
    Longest Common Subsequence

    Include answers to the followings in quiz9.pdf:

    • Explain what the values of the dynamic table mean in the class.

    • LCSDynamic pre-populates the dynamic table before making any recursive calls. Is it possible to find a LCS with dynamic programming by populating the dynamic table while making recursive class.

    circle-info

    You may need a different type of a dynamic table to populate it while making recursive calls.

    hashtag
    Extra Credit

    • Create the class under the package.

    • Update the solveAll() that returns all longest common subsequences between two strings.

    10.1. Fibonacci Sequence

    This section discusses recursive, iterative, and dynamic programming ways of generating the Fibonacci sequence.

    The Fibonacci sequencearrow-up-right is a sequence of numbers generated as follows:

    • F0=0,F1=1F_0 = 0, F_1 = 1F0​=0,F1​=1

    • Fn=Fn−1+Fn−2F_n = F_{n-1} + F_{n-2}Fn​=Fn−1​+Fn−2​ where n≥2n \geq 2n≥2

    hashtag
    Abstraction

    Let us create the interface that contains one abstract method:

    • L2: returns the 'th Fibonacci number.

    hashtag
    Recursion

    The followings demonstrate how to generate the Fibonacci sequence using recursion:

    Let us create the class inheriting Fibonacci:

    What is the worst-case complexity of this recursive algorithm?

    hashtag
    Dynamic Programming

    The followings demonstrate how to generate the Fibonacci sequence using dynamic programming:

    Let us create the FibonacciDynamic class inheriting Fibonacci:

    • L7: creates a dynamic table represented by an array where the 'th dimension is to be filled with the 'th Fibonacci number.

    • L16: returns the value in the dynamic table if exists.

    What is the worst-case complexity of this dynamic programming algorithm?

    hashtag
    Iteration

    For this particular task, the efficiency can be further enhanced using iteration. Let us create the class inheriting Fibonacci:

    hashtag
    Benchmark

    The followings show performance comparisons:

    9.3. Simplex Algorithm

    hashtag
    Max-Flow Min-Cut Theorem

    Let SSS - TTT cut be a set of edges whose removal disconnects any path between SSS and TTT:

    What is the relationship between max-flow and min-cut?

    hashtag
    Simplex: Prime

    hashtag
    Simplex: Dual

    9.1. Flow Network

    This section describes how to implement a flow network.

    A flow network is a directed graph that has the following properties:

    hashtag
    Max Flow

    Let us define the MaxFlowarrow-up-right class that keeps track of the amount of flows in every edge used to find the maximum flow:

    • L2: consists of (edge, flow) as the key-value pair.

    • L3: stores the total amount of flows to the target vertices.

    • L13-14: initializes all flows to 0.

    Let us define getter methods:

    • L5: returns the remaining residual that can be used to push more flows.

      • L6: the edge weight represents the total capacity.

    Finally, let us define setter methods:

    • L1: takes the path from a source and a target.

      • L2: updates the flow of every edge in the path with the specific flow.

    10. Dynamic Programming

    This chapter discusses dynamic programming algorithms in comparison to recursions.

    hashtag
    Contents

    1. Fibonacci Sequence

    hashtag
    References

    10.2. Tower of Hanoi

    This section discusses recursive and dynamic programming ways of solving the Tower of Hanoi problem.

    The followings demonstrate the Tower of Hanoi problem:

    hashtag
    Abstraction

    Let us create the abstract class Hanoiarrow-up-right:

    • L9: returns a list of movements to solve the given problem.

      • n: the total number of rings.

      • source: the ID of the source tower.

      • intermediate

    • L11: returns the string representation of a movement.

    hashtag
    Recursion

    Let us create the class inheriting Hanoi:

    hashtag
    Dynamic Programming

    Let us create the class inheriting Hanoi:

    hashtag
    Benchmark

    The followings show performance comparison:

    10.3. Longest Common Subsequence

    This section discusses recursive and dynamic programming ways of finding longest common subsequence.

    A subsequence is a sequence that can be derived from another sequence by deleting some or no elements without changing the order of the remaining elements.

    Given a string "ABCDE"

    • Substring: {"A", "BC", "CDE", ...}

    • Subsequence: {all substrings, "AC", "ACE", ...}

    • Not subsequence: {"BA", "DAB", ...}

    Longest common subsequence

    • The longest subsequence commonly shared by multiple strings.

    • e.g., “baal” is a LCS of “bilabial” and “balaclava”.

    Can there be more than one longest common subsequence?

    Application

    • Find the longest common subsequence in DNA (e.g., GAATGTCCTTTCTCTAAGTCCTAAG).

    hashtag
    Abstraction

    Let us create the abstract class :

    hashtag
    Recursion

    Let us create the inheriting LCS:

    • L4: no character is left to compare for either string.

    • L5: when two characters match, move onto c[:i-1] and d[:j-1].

    The followings demonstrate a recursive way of finding a LCS between two strings:

    hashtag
    Dynamic Programming

    Let us create the class inheriting LCS.

    • L4: creates a dynamic table and passes it to the solver.

    • L12: the dynamic table is pre-populated before any recursive calls.

    The following show the dynamic table populated by the previous example:

    Let us define the solve() method:

    • L2: no character is left to compare for either string.

    • L3: when two characters match, move onto c[:i-1] and d[:j-1].

    The followings demonstrate how to find a LCS using the dynamic table:

    private boolean containsCycleAux(int target, Deque<Integer> notVisited, Set<Integer> visited) {
        notVisited.remove(target);
        visited.add(target);
    
        for (Edge edge : getIncomingEdges(target)) {
            if (visited.contains(edge.getSource()))
                return true;
    
            if (containsCycleAux(edge.getSource(), notVisited, new HashSet<>(visited)))
                return true;
        }
    
        return false;
    }
    public class SpanningTree implements Comparable<SpanningTree> {
        private final List<Edge> edges;
        private double total_weight;
    
        public SpanningTree() {
            edges = new ArrayList<>();
        }
    
        public SpanningTree(SpanningTree tree) {
            edges = new ArrayList<>(tree.getEdges());
            total_weight = tree.getTotalWeight();
        }
    }
    public List<Edge> getEdges() { return edges; }
    public double getTotalWeight() { return total_weight; }
    public int size() { return edges.size(); }
    
    public void addEdge(Edge edge) {
        edges.add(edge);
        total_weight += edge.getWeight();
    }
    @Override
    public int compareTo(SpanningTree tree) {
        double diff = total_weight - tree.total_weight;
        if (diff > 0) return 1;
        else if (diff < 0) return -1;
        else return 0;
    }
    public interface MST {
        public SpanningTree getMinimumSpanningTree(Graph graph);
    }
    public class FordFulkerson implements NetworkFlow {
        public MaxFlow getMaximumFlow(Graph graph, int source, int target) {
            MaxFlow mf = new MaxFlow(graph);
            Subgraph sub;
    
            while ((sub = getAugmentingPath(graph, mf, new Subgraph(), source, target)) != null) {
                double min = getMin(mf, sub.getEdges());
                mf.updateFlow(sub.getEdges(), min);
            }
    
            return mf;
        }
    }
    public class FordFulkerson implements NetworkFlow {
        public MaxFlow getMaximumFlow(Graph graph, int source, int target) {
            MaxFlow mf = new MaxFlow(graph);
            Subgraph sub;
    
            while ((sub = getAugmentingPath(graph, mf, new Subgraph(), source, target)) != null) {
                double min = getMin(mf, sub.getEdges());
                mf.updateFlow(sub.getEdges(), min);
                updateBackward(graph, sub, mf, min);
            }
    
            return mf;
        }
    }
    public class Subgraph {
        private final List<Edge> edges;
        private final Set<Integer> vertices;
    
        public Subgraph() {
            edges = new ArrayList<>();
            vertices = new HashSet<>();
        }
    
        public Subgraph(Subgraph graph) {
            edges = new ArrayList<>(graph.getEdges());
            vertices = new HashSet<>(graph.getVertices());
        }
    }
    public List<Edge> getEdges() { return edges; }
    
    public Set<Integer> getVertices() { return vertices; }
    
    public void addEdge(Edge edge) {
        edges.add(edge);
        vertices.add(edge.getSource());
        vertices.add(edge.getTarget());
    }
    
    public boolean contains(int vertex) {
        return vertices.contains(vertex);
    }
    private double getMin(MaxFlow mf, List<Edge> path) {
        double min = mf.getResidual(path.get(0));
    
        for (int i = 1; i < path.size(); i++)
            min = Math.min(min, mf.getResidual(path.get(i)));
    
        return min;
    }
    private Subgraph getAugmentingPath(Graph graph, MaxFlow mf, Subgraph sub, int source, int target) {
        if (source == target) return sub;
        Subgraph tmp;
    
        for (Edge edge : graph.getIncomingEdges(target)) {
            if (sub.contains(edge.getSource())) continue;
            if (mf.getResidual(edge) <= 0) continue;
            tmp = new Subgraph(sub);
            tmp.addEdge(edge);
            tmp = getAugmentingPath(graph, mf, tmp, source, edge.getSource());
            if (tmp != null) return tmp;
        }
    
        return null;
    }
    protected void updateBackward(Graph graph, Subgraph sub, MaxFlow mf, double min) {
        boolean found;
    
        for (Edge edge : sub.getEdges()) {
            found = false;
    
            for (Edge rEdge : graph.getIncomingEdges(edge.getSource())) {
                if (rEdge.getSource() == edge.getTarget()) {
                    mf.updateFlow(rEdge, -min);
                    found = true;
                    break;
                }
            }
    
            if (!found) {
                Edge rEdge = graph.setDirectedEdge(edge.getTarget(), edge.getSource(), 0);
                mf.updateFlow(rEdge, -min);
            }
        }
    }
    private void add(PriorityQueue<Edge> queue, Set<Integer> visited, Graph graph, int target) {
        visited.add(target);
    
        for (Edge edge : graph.getIncomingEdges(target)) {
            if (!visited.contains(edge.getSource()))
                queue.add(edge);
        }
    }
    private void add(PriorityQueue<Edge> queue, Set<Integer> visited, Graph graph, int target) {
        visited.add(target);
        
        graph.getIncomingEdges(target).stream().
            filter(edge -> !visited.contains(edge.getSource())).
            collect(Collectors.toCollection(() -> queue));
    }
    public class MSTPrim implements MST {
        @Override
        public SpanningTree getMinimumSpanningTree(Graph graph) {
            PriorityQueue<Edge> queue = new PriorityQueue<>();
            SpanningTree tree = new SpanningTree();
            Set<Integer> visited = new HashSet<>();
            Edge edge;
    
            add(queue, visited, graph, 0);
    
            while (!queue.isEmpty()) {
                edge = queue.poll();
    
                if (!visited.contains(edge.getSource())) {
                    tree.addEdge(edge);
                    if (tree.size() + 1 == graph.size()) break;
                    add(queue, visited, graph, edge.getSource());
                }
            }
    
            return tree;
        }
    }
    public class MSTKruskal implements MST {
        @Override
        public SpanningTree getMinimumSpanningTree(Graph graph) {
            PriorityQueue<Edge> queue = new PriorityQueue<>(graph.getAllEdges());
            DisjointSet forest = new DisjointSet(graph.size());
            SpanningTree tree = new SpanningTree();
    
            while (!queue.isEmpty()) {
                Edge edge = queue.poll();
    
                if (!forest.inSameSet(edge.getTarget(), edge.getSource())) {
                    tree.addEdge(edge);
                    if (tree.size() + 1 == graph.size()) break;
                    forest.union(edge.getTarget(), edge.getSource());
                }
            }
    
            return tree;
        }
    }
    public class MaxFlow {
        private Map<Edge, Double> flow_map;
        private double maxflow;
    
        public MaxFlow(Graph graph) {
            init(graph);
        }
    
        public void init(Graph graph) {
            flow_map = new HashMap<>();
            maxflow = 0;
    
            for (Edge edge : graph.getAllEdges())
                flow_map.put(edge, 0d);
        }
    }
    public abstract class Hanoi {
        /**
         * @param n            the total number of rings.
         * @param source       the source tower.
         * @param intermediate the intermediate tower.
         * @param destination  the destination tower.
         * @return a list of steps to move all rings in the source tower to the destination tower.
         */
        public abstract List<String> solve(int n, char source, char intermediate, char destination);
    
        protected String getKey(int n, char source, char destination) {
            return n + ":" + source + "->" + destination;
        }
    }
    Ford-Fulkerson Algorithmarrow-up-right
    Edmonds–Karp Algorithmarrow-up-right
    Simplex Online Toolarrow-up-right
    HanoiRecursivearrow-up-right
    HanoiDynamicarrow-up-right
    LCSDynamicarrow-up-right
    LCSQuizarrow-up-right
    dynamic.lcsarrow-up-right
    Tower of Hanoi
    Longest Common Subsequence
    Quiz
    Dynamic Programmingarrow-up-right
    Tower of Hanoiarrow-up-right
    Longest Common Subsequencearrow-up-right
    https://www.slideshare.net/jchoi7s/dsajava-divide-conquer-sorting-algorithms-benchmarksarrow-up-right
    https://speakerdeck.com/emory/dsa-java-integer-bucket-sortarrow-up-right
    https://speakerdeck.com/emory/dsa-java-distribution-based-sort-benchmarksarrow-up-right
    https://speakerdeck.com/emory/dsa-java-lsd-radix-sortarrow-up-right
    https://www.slideshare.net/jchoi7s/trie-demonstrationwww.slideshare.netchevron-right

    An objective function and is constraints to find min-cut.

    S1S_1S1​
    S2S_2S2​
    T1T_1T1​
    T2T_2T2​
    graph.flowarrow-up-right
    L17
    : fills in the dynamic table using recursion.
    kkk
    iii
    iii
    Fibonacciarrow-up-right
    FibonacciRecursivearrow-up-right
    FibonacciIterativearrow-up-right
    L3
    : updates the overall flow.
  • L6: adds the specific flow to the specific edge.

  • : the ID of the intermediate tower.
  • destination: the ID of the destination tower.

  • HanoiRecursivearrow-up-right
    HanoiDynamicarrow-up-right
    L7: gets the longest common subsequence between c[:i-1] and d[:j].
  • L8: gets the longest common subsequence between c[:i] and d[:j-1].

  • L9: returns the longest subsequence.

  • L5: gets the longest common subsequence between c[:i] and d[:j-1].
  • L6: gets the longest common subsequence between c[:i-1] and d[:j].

  • L9: returns the longest subsequence by looking up the values in the dynamic table.

  • LCSarrow-up-right
    LCSRecursivearrow-up-right
    LCSDynamicarrow-up-right
    Maximize p = 0x1 + x2 + 0x3 + x4 subject to
    x1 <= 4
    x2 <= 2
    x3 <= 3
    x4 <= 2
    x2 - x1 <= 0
    x4 - x3 <= 0
    
    Maximize p = 0x1 + 0x2 + 0x3 + 0x4 + 0x5 + 0x6 + x7 + x8  subject to
    x1 <= 4
    x2 <= 2
    x3 <= 3
    x4 <= 2
    x5 <= 3
    x6 <= 1
    x7 <= 2
    x8 <= 4
    x3 - x1 <= 0
    x4 + x5 - x2 - x6 <= 0
    x6 + x7 - x3 - x4 <= 0
    x8 - x5 <= 0
    https://www.slideshare.net/jchoi7s/tries-removewww.slideshare.netchevron-right
    public interface Fibonacci {
        int get(int k);
    }
    public class FibonacciRecursive implements Fibonacci {
        @Override
        public int get(int k) {
            return switch (k) {
                case 0 -> 0;
                case 1 -> 1;
                default -> get(k - 1) + get(k - 2);
            };
        }
    }
    public class FibonacciDynamic implements Fibonacci {
        @Override
        public int get(int k) {
            return getAux(k, createTable(k));
        }
        
        private int[] createTable(int k) {
            int[] table = new int[k + 1];
            table[0] = 0;
            table[1] = 1;
            Arrays.fill(table, 2, k + 1, -1);
            return table;
        }
    
        private int getAux(int k, int[] table) {
            if (table[k] >= 0) return table[k];
            return table[k] = getAux(k - 1, table) + getAux(k - 2, table);
        }
    }
    public class FibonacciIterative implements Fibonacci {
        @Override
        public int get(int k) {
            if (k < 2) return k;
            int f0 = 0, f1 = 1, f2;
    
            for (int i = 2; i < k; i++) {
                f2 = f0 + f1;
                f0 = f1;
                f1 = f2;
            }
    
            return f0 + f1;
        }
    }
    public double getMaxFlow() {
        return maxflow;
    }
    
    public double getResidual(Edge edge) {
        return edge.getWeight() - flow_map.get(edge);
    }
    public void updateFlow(List<Edge> path, double flow) {
        path.forEach(e -> updateFlow(e, flow));
        maxflow += flow;
    }
    
    public void updateFlow(Edge edge, double flow) {
        Double prev = flow_map.getOrDefault(edge, 0d);
        flow_map.put(edge, prev + flow);
    }
    public class HanoiRecursive extends Hanoi {
        @Override
        public List<String> solve(int n, char source, char intermediate, char destination) {
            List<String> list = new ArrayList<>();
            solve(list, n, source, intermediate, destination);
            return list;
        }
    
        private void solve(List<String> list, int n, char source, char intermediate, char destination) {
            if (n == 0) return;
            solve(list, n - 1, source, destination, intermediate);
            list.add(getKey(n, source, destination));
            solve(list, n - 1, intermediate, source, destination);
        }
    }
    public class HanoiDynamic extends Hanoi {
        @Override
        public List<String> solve(int n, char source, char intermediate, char destination) {
            List<String> list = new ArrayList<>();
            solve(list, n, source, intermediate, destination, new HashMap<>());
            return list;
        }
    
        private void solve(List<String> list, int n, char source, char intermediate, char destination, Map<String, int[]> map) {
            if (n == 0) return;
            int fromIndex = list.size();
            int[] sub = map.get(getKey(n - 1, source, intermediate));
    
            if (sub != null) addAll(list, sub[0], sub[1]);
            else solve(list, n - 1, source, destination, intermediate, map);
    
            String key = getKey(n, source, destination);
            list.add(key);
            sub = map.get(getKey(n - 1, intermediate, destination));
    
            if (sub != null) addAll(list, sub[0], sub[1]);
            else solve(list, n - 1, intermediate, source, destination, map);
    
            if (!map.containsKey(key))
                map.put(key, new int[]{fromIndex, list.size()});
        }
    
        private void addAll(List<String> list, int fromIndex, int toIndex) {
            for (int i = fromIndex; i < toIndex; i++)
                list.add(list.get(i));
        }
    }
    public abstract class LCS {
        /**
         * @param a the first string.
         * @param b the second string.
         * @return a longest common sequence of the two specific strings.
         */
        public String solve(String a, String b) {
            return solve(a.toCharArray(), b.toCharArray(), a.length() - 1, b.length() - 1);
        }
    
        /**
         * @param c the first array of characters.
         * @param d the second array of characters.
         * @param i the index of the character in {@code c} to be compared.
         * @param j the index of the character in {@code d} to be compared.
         * @return a longest common sequence of the two specific strings.
         */
        protected abstract String solve(char[] c, char[] d, int i, int j);
    }
    public class LCSRecursive extends LCS {
        @Override
        protected String solve(char[] c, char[] d, int i, int j) {
            if (i < 0 || j < 0) return "";
            if (c[i] == d[j]) return solve(c, d, i - 1, j - 1) + c[i];
    
            String c1 = solve(c, d, i - 1, j);
            String d1 = solve(c, d, i, j - 1);
            return (c1.length() > d1.length()) ? c1 : d1;
        }
    }
    public class LCSDynamic extends LCS {
        @Override
        protected String solve(char[] c, char[] d, int i, int j) {
            return solve(c, d, i, j, createTable(c, d));
        }
    
        /**
         * @param c the first string.
         * @param d the second string.
         * @return the dynamic table populated by estimating the # of LCSs in the grid of the two specific strings.
         */
        protected int[][] createTable(char[] c, char[] d) {
            final int N = c.length, M = d.length;
            int[][] table = new int[N][M];
    
            for (int i = 1; i < N; i++)
                for (int j = 1; j < M; j++)
                    table[i][j] = (c[i] == d[j]) ? table[i - 1][j - 1] + 1 : Math.max(table[i - 1][j], table[i][j - 1]);
    
            return table;
        }
    }
    protected String solve(char[] c, char[] d, int i, int j, int[][] table) {
        if (i < 0 || j < 0) return "";
        if (c[i] == d[j])  return solve(c, d, i - 1, j - 1, table) + c[i];
        
        if (i == 0) return solve(c, d, i, j - 1, table);
        if (j == 0) return solve(c, d, i - 1, j, table);
        return (table[i - 1][j] > table[i][j - 1]) ? solve(c, d, i - 1, j, table) : solve(c, d, i, j - 1, table);
    }
    Minimize p = 4x1 + 2x2 + 3x3 + 2x4 + 0y1 + 0y3  subject to
    x1 + y1 >= 1
    x3 + y3 >= 1
    x2 - y1 >= 0
    x4 - y3 >= 0
    
    Minimize p = 4x1 + 2x2 + 3x3 + 2x4 +
3x5 + x6 + 2x7 + 4x8 + 0y1 + 0y2 + 0y3 + 0y4  subject to
    x1 + y1 >= 1
    x2 + y2 >= 1
    x3 - y1 + y3 >= 0
    x4 - y2 + y3 >= 0
    x5 - y2 + y4 >= 0
    x6 - y3 + y2 >= 0
    x7 - y3 >= 0
    x8 - y4 >= 0
    https://www.slideshare.net/jchoi7s/balanced-bst-rotate-left/jchoi7s/balanced-bst-rotate-leftwww.slideshare.netchevron-right
    https://www.slideshare.net/jchoi7s/topological-sorting-by-incoming-scoreswww.slideshare.netchevron-right
    https://www.slideshare.net/jchoi7s/dsajava-binary-heap-remove-238005081arrow-up-right
    https://www.slideshare.net/jchoi7s/dsajava-shell-sortarrow-up-right
    https://www.slideshare.net/jchoi7s/dsajava-heap-sortwww.slideshare.netchevron-right
    https://www.slideshare.net/jchoi7s/dsajava-heap-sortarrow-up-right
    https://www.slideshare.net/jchoi7s/dsajava-binary-heap-benchmarks-238005098arrow-up-right
    https://www.slideshare.net/jchoi7s/dsajava-comparisonbased-sort-benchmarksarrow-up-right
    https://www.slideshare.net/jchoi7s/dsajava-binary-heap-add-238005046arrow-up-right
    https://www.slideshare.net/jchoi7s/kruskals-algorithm-238997335www.slideshare.netchevron-right