diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..560aa4f2b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 000000000..ffd13a1b5 --- /dev/null +++ b/.github/workflows/gradle.yml @@ -0,0 +1,64 @@ +# This workflow will build a Java project with Gradle +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle + +name: Java CI with Gradle + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +permissions: + checks: write + +jobs: + test: + name: Test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: 'temurin' + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v3 + - name: Setup and execute tests via Gradle + uses: gradle/gradle-build-action@v3 + with: + gradle-version: wrapper + arguments: test + - name: Publish Test Results + if: ${{ always() }} + uses: mikepenz/action-junit-report@v3 + with: + report_paths: "**/TEST-*.xml" + exclude_sources: build/ + annotate_only: ${{ github.event_name == 'pull_request' }} + + style-check: + name: Code Formatting Check + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: 'temurin' + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v3 + - name: Verify all Java files are formatted correctly according to the Google Java Style Guide using Gradle + uses: gradle/gradle-build-action@v3 + id: verifygooglejavaformat + with: + gradle-version: wrapper + arguments: spotlessJavaCheck + - name: Create summary if format check failed + if: ${{ steps.verifygooglejavaformat.outcome == 'failure' }} + run: | + echo "Run the command `./gradlew spotlessApply` to fix Java formatting." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/readme-url-checker.yml b/.github/workflows/readme-url-checker.yml new file mode 100644 index 000000000..548349949 --- /dev/null +++ b/.github/workflows/readme-url-checker.yml @@ -0,0 +1,23 @@ +# Use awesome bot to verify that all URLs in the README are valid. +name: README URL Checker +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Ruby 2.6 + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.0 + + - name: Install Awesome Bot + run: gem install awesome_bot + - name: Run Awesome Bot + run: | + awesome_bot README.md --allow-dupe --allow-redirect + echo "If this workflow fails, it usually means there's a broken link in the README that needs fixing. This workflow has been flaky in the past, so verify the reported broken links, but don't sweat it if this workflow is broken." diff --git a/.gitignore b/.gitignore index c5b2dba1f..68c3443af 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,16 @@ gradle-app.setting .gradletasknamecache dependencies +bin/ + +# Eclipse IDE metadata +.classpath +.project +.settings/ + +# Mac files +.DS_Store +.idea/ + +# Kotlin folder? +*/META-INF/* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 01dab87cc..000000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -# Travis Continuous Integration script for travis-ci.org - -dist: trusty - -# Runs all unit tests and check for broken links -language: java - -jdk: - - oraclejdk8 - -# Install ruby to get gem command -before_install: - - sudo apt-add-repository -y ppa:brightbox/ruby-ng - - sudo apt-get -y update - - sudo apt-get -y install ruby-full - -# Install awesome_bot for README.md broken link checking -before_script: - - gem install awesome_bot - -# Run maven tests and broken link check -script: - - awesome_bot README.md --allow-dupe --allow-redirect - - gradle test diff --git a/README.md b/README.md index d26721b13..a42be2f99 100644 --- a/README.md +++ b/README.md @@ -1,213 +1,321 @@ - -[![Travis](https://img.shields.io/travis/williamfiset/Algorithms.svg)](https://travis-ci.org/williamfiset/Algorithms) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +![Java CI with Gradle](https://github.com/williamfiset/Algorithms/workflows/Java%20CI%20with%20Gradle/badge.svg) +![README Checker](https://github.com/williamfiset/Algorithms/workflows/README%20URL%20Checker/badge.svg) +[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/donate?hosted_button_id=JUP2HZ6JUPB5C) # Algorithms & data structures project -Algorithms and data structures are amongst the most fundamental ingredients in the recipe for efficient code and good software design; knowledge of how to create and design excellent algorithms is an essential skill required in becoming an exemplary programmer. The goal of this repository is to demonstrate how to correctly implement the most common data structures and algorithms in the simplest and most elegant ways. +Algorithms and data structures are fundamental to efficient code and good software design. Creating and designing excellent algorithms is required for being an exemplary programmer. This repository's goal is to demonstrate how to correctly implement common data structures and algorithms in the simplest and most elegant ways. # Contributing -This repository is contribution friendly :smiley:. If you're an algorithms enthusiast and want to add or improve an algorithm your contribution is welcome! Please be sure to checkout the [Wiki](https://github.com/williamfiset/Algorithms/wiki) for instructions. +This repository is contribution friendly :smiley:. If you'd like to add or improve an algorithm, your contribution is welcome! Please be sure to check out the [Wiki](https://github.com/williamfiset/Algorithms/wiki) for instructions. + +### Other programming languages? + +This repository provides algorithm implementations in Java, however, there are other forks that provide implementations in other languages, most notably: + +* **C++/Python**: https://github.com/akzare/Algorithms +* **Rust**: https://github.com/TianyiShi2001/Algorithms + +# Running an algorithm implementation + +To compile and run any of the algorithms here, you need at least JDK version 8. Gradle can make things more convenient for you, but it is not required. + +## Running with Gradle (recommended) + +This project supports the [Gradle Wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html). The Gradle wrapper automatically downloads Gradle the first time it runs, so expect a delay when running the first command below. + +If you are on Windows, use `gradlew.bat` instead of `./gradlew` below. + +Run a single algorithm like this: + +``` +./gradlew run -Palgorithm=. +``` + +Alternatively, you can run a single algorithm specifying the full class name + +``` +./gradlew run -Pmain= + +``` + +For instance: + +``` +./gradlew run -Palgorithm=search.BinarySearch +``` + +or + +``` +./gradlew run -Pmain=com.williamfiset.algorithms.search.BinarySearch +``` + +## Compiling and running with only a JDK + +### Create a classes folder + +``` +cd Algorithms +mkdir classes +``` + +### Compile the algorithm + +``` +javac -sourcepath src/main/java -d classes src/main/java/ +``` + +### Run the algorithm +``` +java -cp classes +``` + +### Example + +``` +$ javac -d classes -sourcepath src/main/java src/main/java/com/williamfiset/algorithms/search/BinarySearch.java +$ java -cp classes com.williamfiset.algorithms.search.BinarySearch +``` # Data Structures -* [:movie_camera:](https://www.youtube.com/watch?v=q4fnJZr8ztY)[Balanced Trees](https://github.com/williamfiset/algorithms/tree/master/com/williamfiset/algorithms/datastructures/balancedtree) - * [Avl Tree (recursive)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeRecursive.java) - * [Avl Tree (recursive, mildly optimized)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeRecursiveOptimized.java) - * [Red Black Tree (recursive)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/balancedtree/RedBlackTree.java) -* [:movie_camera:](https://www.youtube.com/watch?v=JfSdGQdAzq8)[Binary Search Tree](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/binarysearchtree/BinarySearchTree.java) -* [Splay Tree](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTree.java) -* [Bloom Filter](https://github.com/williamfiset/algorithms/tree/master/com/williamfiset/algorithms/datastructures/bloomfilter) -* [:movie_camera:](https://www.youtube.com/watch?v=PEnFFiQe1pM)[Dynamic Array](https://github.com/williamfiset/algorithms/tree/master/com/williamfiset/algorithms/datastructures/dynamicarray) - * [Dynamic array (integer only, fast)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/dynamicarray/IntArray.java) - * [Dynamic array (generic)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/dynamicarray/DynamicArray.java) -* [:movie_camera:](https://www.youtube.com/watch?v=RgITNht_f4Q)[Fenwick Tree](https://github.com/williamfiset/algorithms/tree/master/com/williamfiset/algorithms/datastructures/fenwicktree) - * [Fenwick Tree (range query, point updates)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeQueryPointUpdate.java) - * [Fenwick Tree (range update, point query)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeUpdatePointQuery.java) -* [Set](https://github.com/williamfiset/algorithms/tree/master/com/williamfiset/algorithms/datastructures/set) -* [:movie_camera:](https://www.youtube.com/watch?v=2E54GqF0H4s)[Hashtable](https://github.com/williamfiset/algorithms/tree/master/com/williamfiset/algorithms/datastructures/hashtable) - * [Hashtable (double hashing)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/hashtable/HashTableDoubleHashing.java) - * [Hashtable (linear probing)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/hashtable/HashTableLinearProbing.java) - * [Hashtable (quadratic probing)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/hashtable/HashTableQuadraticProbing.java) - * [Hashtable (separate chaining)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/hashtable/HashTableSeperateChaining.java) -* [:movie_camera:](https://www.youtube.com/watch?v=-Yn5DU0_-lw)[Linked List](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/linkedlist/DoublyLinkedList.java) -* [:movie_camera:](https://www.youtube.com/watch?v=wptevk0bshY)[Priority Queue](https://github.com/williamfiset/algorithms/tree/master/com/williamfiset/algorithms/datastructures/priorityqueue) - * [Min Binary Heap](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeap.java) - * [Min Indexed Binary Heap (sorted key-value pairs, similar to hash-table)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedBinaryHeap.java) - * [Min D-Heap](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/priorityqueue/MinDHeap.java) - * [:movie_camera:](https://www.youtube.com/watch?v=DT8xZ0Uf8wo)[Min Indexed D-Heap (sorted key-value pairs, similar to hash-table)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedDHeap.java) -* [Quad Tree [WIP]](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/quadtree/QuadTree.java) -* [:movie_camera:](https://www.youtube.com/watch?v=KxzhEQ-zpDc)[Queue](https://github.com/williamfiset/algorithms/tree/master/com/williamfiset/algorithms/datastructures/queue) - * [Queue (integer only, fixed size, fast)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/queue/IntQueue.java) - * [Queue (linked list, generic)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/queue/Queue.java) -* [Segment Tree](https://github.com/williamfiset/algorithms/tree/master/com/williamfiset/algorithms/datastructures/segmenttree) - * [Segment tree (array based, compact)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/segmenttree/CompactSegmentTree.java) - * [Segment tree (pointer implementation)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/segmenttree/Node.java) -* [Skip List [UNTESTED]](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/skiplist/SkipList.java) -* [:movie_camera:](https://www.youtube.com/watch?v=L3ud3rXpIxA)[Stack](https://github.com/williamfiset/algorithms/tree/master/com/williamfiset/algorithms/datastructures/stack) - * [Stack (integer only, fixed size, fast)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/stack/IntStack.java) - * [Stack (linked list, generic)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/stack/Stack.java) -* [:movie_camera:](https://www.youtube.com/watch?v=zqKlL3ZpTqs)[Suffix Array](https://github.com/williamfiset/algorithms/tree/master/com/williamfiset/algorithms/datastructures/suffixarray) - * [Suffix Array (O(n²logn) construction)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArraySlow.java) - * [Suffix Array (O(nlog²(n)) construction)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayMed.java) - * [Suffix Array (O(nlog(n)) construction)](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayFast.java) -* [Trie](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/trie/Trie.java) -* [:movie_camera:](https://www.youtube.com/watch?v=ibjEGG7ylHk)[Union Find](https://github.com/williamfiset/algorithms/blob/master/com/williamfiset/algorithms/datastructures/unionfind/UnionFind.java) + +- [:movie_camera:](https://www.youtube.com/watch?v=q4fnJZr8ztY) [Balanced Trees](src/main/java/com/williamfiset/algorithms/datastructures/balancedtree) + - [AVL Tree (recursive)](src/main/java/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeRecursive.java) + - [Red Black Tree (recursive)](src/main/java/com/williamfiset/algorithms/datastructures/balancedtree/RedBlackTree.java) +- [:movie_camera:](https://www.youtube.com/watch?v=JfSdGQdAzq8) [Binary Search Tree](src/main/java/com/williamfiset/algorithms/datastructures/binarysearchtree/BinarySearchTree.java) +- [Splay Tree](src/main/java/com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTree.java) +- [:movie_camera:](https://www.youtube.com/watch?v=PEnFFiQe1pM) [Dynamic Array](src/main/java/com/williamfiset/algorithms/datastructures/dynamicarray) + - [Dynamic array (integer only, fast)](src/main/java/com/williamfiset/algorithms/datastructures/dynamicarray/IntArray.java) +- [:movie_camera:](https://www.youtube.com/watch?v=RgITNht_f4Q) [Fenwick Tree](src/main/java/com/williamfiset/algorithms/datastructures/fenwicktree) + - [Fenwick Tree (range query, point updates)](src/main/java/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeQueryPointUpdate.java) + - [Fenwick Tree (range update, point query)](src/main/java/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeUpdatePointQuery.java) +- [Fibonacci Heap](src/main/java/com/williamfiset/algorithms/datastructures/fibonacciheap) +- [:movie_camera:](https://www.youtube.com/watch?v=2E54GqF0H4s) [Hashtable](src/main/java/com/williamfiset/algorithms/datastructures/hashtable) + - [Hashtable (double hashing)](src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableDoubleHashing.java) + - [Hashtable (linear probing)](src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableLinearProbing.java) + - [Hashtable (quadratic probing)](src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableQuadraticProbing.java) + - [Hashtable (separate chaining)](src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableSeparateChaining.java) +- [:movie_camera:](https://www.youtube.com/watch?v=-Yn5DU0_-lw) [Linked List](src/main/java/com/williamfiset/algorithms/datastructures/linkedlist/DoublyLinkedList.java) +- [:movie_camera:](https://www.youtube.com/watch?v=wptevk0bshY) [Priority Queue](src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue) + - [Min Binary Heap](src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeap.java) + - [Min Indexed Binary Heap (sorted key-value pairs, similar to hash-table)](src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedBinaryHeap.java) + - [Min D-Heap](src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinDHeap.java) + - [:movie_camera:](https://www.youtube.com/watch?v=DT8xZ0Uf8wo) [Min Indexed D-Heap (sorted key-value pairs, similar to hash-table)](src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedDHeap.java) +- [:movie_camera:](https://www.youtube.com/watch?v=KxzhEQ-zpDc) [Queue](src/main/java/com/williamfiset/algorithms/datastructures/queue) + - [Queue (integer only, fixed size, fast)](src/main/java/com/williamfiset/algorithms/datastructures/queue/IntQueue.java) + - [Queue (linked list, generic)](src/main/java/com/williamfiset/algorithms/datastructures/queue/Queue.java) +- [Segment Tree](src/main/java/com/williamfiset/algorithms/datastructures/segmenttree) + - [Segment tree (array based, compact)](src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/CompactSegmentTree.java) + - [Segment tree (pointer implementation)](src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/Node.java) +- [:movie_camera:](https://youtu.be/uUatD9AudXo) [Sparse Table](src/main/java/com/williamfiset/algorithms/datastructures/sparsetable/SparseTable.java) +- [:movie_camera:](https://www.youtube.com/watch?v=L3ud3rXpIxA) [Stack](src/main/java/com/williamfiset/algorithms/datastructures/stack) + - [Stack (integer only, fixed size, fast)](src/main/java/com/williamfiset/algorithms/datastructures/stack/IntStack.java) + - [Stack (linked list, generic)](src/main/java/com/williamfiset/algorithms/datastructures/stack/ListStack.java) + - [Stack (array, generic)](src/main/java/com/williamfiset/algorithms/datastructures/stack/ArrayStack.java) +- [:movie_camera:](https://www.youtube.com/watch?v=zqKlL3ZpTqs) [Suffix Array](src/main/java/com/williamfiset/algorithms/datastructures/suffixarray) + - [Suffix Array (O(n²logn) construction)](src/main/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArraySlow.java) + - [Suffix Array (O(nlog²(n)) construction)](src/main/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayMed.java) + - [Suffix Array (O(nlog(n)) construction)](src/main/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayFast.java) +- [Trie](src/main/java/com/williamfiset/algorithms/datastructures/trie/Trie.java) +- [:movie_camera:](https://www.youtube.com/watch?v=ibjEGG7ylHk) [Union Find](src/main/java/com/williamfiset/algorithms/datastructures/unionfind/UnionFind.java) # Dynamic Programming -* [Coin change problem](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/dp/CoinChange.java) **- O(nW)** -* [Edit distance](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/dp/EditDistance.java) **- O(nm)** -* [:movie_camera:](https://www.youtube.com/watch?v=cJ21moQpofY)[Knapsack 0/1](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/dp/Knapsack_01.java) **- O(nW)** -* [Knapsack unbounded (0/∞)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/dp/KnapsackUnbounded.java) **- O(nW)** -* [Maximum contiguous subarray](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/dp/MaximumSubarray.java) **- O(n)** -* [Longest Common Subsequence (LCS)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/dp/LongestCommonSubsequence.java) **- O(nm)** -* [Longest Increasing Subsequence (LIS)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/dp/LongestIncreasingSubsequence.java) **- O(n2)** -* [Longest Palindrome Subsequence (LPS)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/dp/LongestPalindromeSubsequence.java) **- O(n2)** -* [:movie_camera:](https://www.youtube.com/watch?v=cY4HiiFHO1o)[Traveling Salesman Problem (dynamic programming, iterative)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingIterative.java) **- O(n22n)** -* [Traveling Salesman Problem (dynamic programming, recursive)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingRecursive.java) **- O(n22n)** -* [Minimum Weight Perfect Matching (iterative, complete graph)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/dp/MinimumWeightPerfectMatching.java) **- O(n22n)** + +## Dynamic Programming Classics + +- [Coin change problem](src/main/java/com/williamfiset/algorithms/dp/CoinChange.java) **- O(nW)** +- [Edit distance (iterative)](src/main/java/com/williamfiset/algorithms/dp/EditDistanceIterative.java) **- O(nm)** +- [Edit distance (recursive)](src/main/java/com/williamfiset/algorithms/dp/EditDistanceRecursive.java) **- O(nm)** +- [:movie_camera:](https://www.youtube.com/watch?v=cJ21moQpofY) [Knapsack 0/1](src/main/java/com/williamfiset/algorithms/dp/Knapsack_01.java) **- O(nW)** +- [Knapsack unbounded (0/∞)](src/main/java/com/williamfiset/algorithms/dp/KnapsackUnbounded.java) **- O(nW)** +- [Maximum contiguous subarray](src/main/java/com/williamfiset/algorithms/dp/MaximumSubarray.java) **- O(n)** +- [Longest Common Subsequence (LCS)](src/main/java/com/williamfiset/algorithms/dp/LongestCommonSubsequence.java) **- O(nm)** +- [Longest Increasing Subsequence (LIS)](src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequence.java) **- O(n2)** +- [Longest Palindrome Subsequence (LPS)](src/main/java/com/williamfiset/algorithms/dp/LongestPalindromeSubsequence.java) **- O(n2)** +- [:movie_camera:](https://www.youtube.com/watch?v=cY4HiiFHO1o) [Traveling Salesman Problem (dynamic programming, iterative)](src/main/java/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingIterative.java) **- O(n22n)** +- [Traveling Salesman Problem (dynamic programming, recursive)](src/main/java/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingRecursive.java) **- O(n22n)** +- [Minimum Weight Perfect Matching (iterative, complete graph)](src/main/java/com/williamfiset/algorithms/dp/MinimumWeightPerfectMatching.java) **- O(n22n)** + +## Dynamic Programming Problem Examples + +### Adhoc + +- [:movie_camera:](https://www.youtube.com/watch?v=_tur2nPkIKo) [Magic Cows](https://github.com/williamfiset/Algorithms/blob/master/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/MagicalCows.java) +- [:movie_camera:](https://youtu.be/oQQO_n57SB0) [Narrow Art Gallery](https://github.com/williamfiset/Algorithms/blob/master/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/NarrowArtGalleryRecursive.java) + +### Tiling problems + +- [:movie_camera:](https://youtu.be/yn2jnmlepY8) [Tiling Dominoes](https://github.com/williamfiset/Algorithms/blob/master/src/main/java/com/williamfiset/algorithms/dp/examples/tilingdominoes/TilingDominoes.java) +- [:movie_camera:](https://www.youtube.com/watch?v=CecjOo4Zo-g) [Tiling Dominoes and Trominoes](src/main/java/com/williamfiset/algorithms/dp/examples/domino-and-tromino-tiling) +- [:movie_camera:](https://youtu.be/pPgBZqY_Xh0) [Mountain Scenes](https://github.com/williamfiset/Algorithms/blob/master/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/Scenes.java) # Geometry -* [Angle between 2D vectors](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/AngleBetweenVectors2D.java) **- O(1)** -* [Angle between 3D vectors](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/AngleBetweenVectors3D.java) **- O(1)** -* [Circle-circle intersection point(s)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/CircleCircleIntersectionPoints.js) **- O(1)** -* [Circle-line intersection point(s)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/LineCircleIntersection.js) **- O(1)** -* [Circle-line segment intersection point(s)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/LineSegmentCircleIntersection.js) **- O(1)** -* [Circle-point tangent line(s)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/PointCircleTangent.java) **- O(1)** -* [Closest pair of points (line sweeping algorithm)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/ClosestPairOfPoints.java) **- O(nlog(n))** -* [Collinear points test (are three 2D points on the same line)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/CollinearPoints.java) **- O(1)** -* [Convex hull (Graham Scan algorithm)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/ConvexHullGrahamScan.java) **- O(nlog(n))** -* [Convex hull (Monotone chain algorithm)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/ConvexHullMonotoneChainsAlgorithm.java) **- O(nlog(n))** -* [Convex polygon area](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/ConvexPolygonArea.java) **- O(n)** -* [Convex polygon cut](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/ConvexPolygonCutWithLineSegment.java) **- O(n)** -* [Convex polygon contains points](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/ConvexPolygonContainsPoint.java) **- O(log(n))** -* [Coplanar points test (are four 3D points on the same plane)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/CoplanarPoints.java) **- O(1)** -* [Line class (handy infinite line class)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/Line.java) **- O(1)** -* [Line-circle intersection point(s)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/LineCircleIntersection.js) **- O(1)** -* [Line segment-circle intersection point(s)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/LineSegmentCircleIntersection.js) **- O(1)** -* [Line segment to general form (ax + by = c)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/LineSegmentToGeneralForm.java) **- O(1)** -* [Line segment-line segment intersection](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/LineSegmentLineSegmentIntersection.java) **- O(1)** -* [Longitude-Latitude geographic distance](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/LongitudeLatitudeGeographicDistance.java) **- O(1)** -* [Point is inside triangle check](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/PointInsideTriangle.java) **- O(1)** -* [Point rotation about point](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/PointRotation.java) **- O(1)** -* [Triangle area algorithms](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/TriangleArea.java) **- O(1)** -* [[UNTESTED] Circle-circle intersection area](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/CircleCircleIntersectionArea.java) **- O(1)** -* [[UNTESTED] Circular segment area](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/geometry/CircularSegmentArea.java) **- O(1)** + +- [Angle between 2D vectors](src/main/java/com/williamfiset/algorithms/geometry/AngleBetweenVectors2D.java) **- O(1)** +- [Angle between 3D vectors](src/main/java/com/williamfiset/algorithms/geometry/AngleBetweenVectors3D.java) **- O(1)** +- [Circle-circle intersection point(s)](src/main/java/com/williamfiset/algorithms/geometry/CircleCircleIntersectionPoints.js) **- O(1)** +- [Circle-line intersection point(s)](src/main/java/com/williamfiset/algorithms/geometry/LineCircleIntersection.js) **- O(1)** +- [Circle-line segment intersection point(s)](src/main/java/com/williamfiset/algorithms/geometry/LineSegmentCircleIntersection.js) **- O(1)** +- [Circle-point tangent line(s)](src/main/java/com/williamfiset/algorithms/geometry/PointCircleTangent.java) **- O(1)** +- [Closest pair of points (line sweeping algorithm)](src/main/java/com/williamfiset/algorithms/geometry/ClosestPairOfPoints.java) **- O(nlog(n))** +- [Collinear points test (are three 2D points on the same line)](src/main/java/com/williamfiset/algorithms/geometry/CollinearPoints.java) **- O(1)** +- [Convex hull (Graham Scan algorithm)](src/main/java/com/williamfiset/algorithms/geometry/ConvexHullGrahamScan.java) **- O(nlog(n))** +- [Convex hull (Monotone chain algorithm)](src/main/java/com/williamfiset/algorithms/geometry/ConvexHullMonotoneChainsAlgorithm.java) **- O(nlog(n))** +- [Convex polygon area](src/main/java/com/williamfiset/algorithms/geometry/ConvexPolygonArea.java) **- O(n)** +- [Convex polygon cut](src/main/java/com/williamfiset/algorithms/geometry/ConvexPolygonCutWithLineSegment.java) **- O(n)** +- [Convex polygon contains points](src/main/java/com/williamfiset/algorithms/geometry/ConvexPolygonContainsPoint.java) **- O(log(n))** +- [Coplanar points test (are four 3D points on the same plane)](src/main/java/com/williamfiset/algorithms/geometry/CoplanarPoints.java) **- O(1)** +- [Line class (handy infinite line class)](src/main/java/com/williamfiset/algorithms/geometry/Line.java) **- O(1)** +- [Line-circle intersection point(s)](src/main/java/com/williamfiset/algorithms/geometry/LineCircleIntersection.js) **- O(1)** +- [Line segment-circle intersection point(s)](src/main/java/com/williamfiset/algorithms/geometry/LineSegmentCircleIntersection.js) **- O(1)** +- [Line segment to general form (ax + by = c)](src/main/java/com/williamfiset/algorithms/geometry/LineSegmentToGeneralForm.java) **- O(1)** +- [Line segment-line segment intersection](src/main/java/com/williamfiset/algorithms/geometry/LineSegmentLineSegmentIntersection.java) **- O(1)** +- [Longitude-Latitude geographic distance](src/main/java/com/williamfiset/algorithms/geometry/LongitudeLatitudeGeographicDistance.java) **- O(1)** +- [Point is inside triangle check](src/main/java/com/williamfiset/algorithms/geometry/PointInsideTriangle.java) **- O(1)** +- [Point rotation about point](src/main/java/com/williamfiset/algorithms/geometry/PointRotation.java) **- O(1)** +- [Triangle area algorithms](src/main/java/com/williamfiset/algorithms/geometry/TriangleArea.java) **- O(1)** +- [[UNTESTED] Circle-circle intersection area](src/main/java/com/williamfiset/algorithms/geometry/CircleCircleIntersectionArea.java) **- O(1)** +- [[UNTESTED] Circular segment area](src/main/java/com/williamfiset/algorithms/geometry/CircularSegmentArea.java) **- O(1)** # Graph theory ### Tree algorithms -* [Tree canonical form (tree isomorphism, tree encoding)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCanonicalFormAdjacencyList.java) **- O(Elog(E))** -* [Tree center(s)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenter.java) **- O(V+E)** -* [Tree diameter](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeDiameter.java) **- O(V+E)** + +- [:movie_camera:](https://www.youtube.com/watch?v=2FFq2_je7Lg) [Rooting an undirected tree](src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/RootingTree.java) **- O(V+E)** +- [:movie_camera:](https://www.youtube.com/watch?v=OCKvEMF0Xac) [Identifying isomorphic trees](src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphism.java) **- O(?)** +- [:movie_camera:](https://www.youtube.com/watch?v=nzF_9bjDzdc) [Tree center(s)](src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenter.java) **- O(V+E)** +- [Tree diameter](src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeDiameter.java) **- O(V+E)** +- [:movie_camera:](https://www.youtube.com/watch?v=sD1IoalFomA) [Lowest Common Ancestor (LCA, Euler tour)](src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorEulerTour.java) **- O(1) queries, O(nlogn) preprocessing** ### Network flow -* [Bipartite graph verification (adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyList.java) **- O(V+E)** -* [:movie_camera:](https://www.youtube.com/watch?v=LdOnanfc5TM)[Max flow & Min cut (Ford-Fulkerson with DFS, adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDfsSolverAdjacencyList.java) **- O(fE)** -* [Max flow & Min cut (Ford-Fulkerson with DFS, adjacency matrix)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDFSAdjacencyMatrix.java) **- O(fV2)** -* [:movie_camera:](https://www.youtube.com/watch?v=RppuJYwlcI8)[Max flow & Min cut (Edmonds-Karp, adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/networkflow/EdmondsKarpAdjacencyList.java) **- O(VE2)** -* [:movie_camera:](https://youtu.be/1ewLrXUz4kk)[Max flow & Min cut (Capacity scaling, adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/networkflow/CapacityScalingSolverAdjacencyList.java) **- O(E2log2(U))** -* [:movie_camera:](https://youtu.be/M6cm8UeeziI)[Max flow & Min cut (Dinic's, adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/networkflow/Dinics.java) **- O(EV2) or O(E√V) for bipartite graphs** -* [Maximum Cardinality Bipartite Matching (augmenting path algorithm, adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/networkflow/MaximumCardinalityBipartiteMatchingAugmentingPathAdjacencyList.java) **- O(VE)** -* [Min Cost Max Flow (Bellman-Ford, adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowWithBellmanFord.java) **- O(E2V2)** -* [Min Cost Max Flow (Johnson's algorithm, adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowJohnsons.java) **- O(E2Vlog(V))** - -### Other graph theory -* [:movie_camera:](https://www.youtube.com/watch?v=aZXi1unBdJA)[Articulation points/cut vertices (adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyList.java) **- O(V+E)** -* [Bellman-Ford (edge list, negative cycles, fast & optimized)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/BellmanFordEdgeList.java) **- O(VE)** -* [:movie_camera:](https://www.youtube.com/watch?v=lyw4FaxrwHg)[Bellman-Ford (adjacency list, negative cycles)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyList.java) **- O(VE)** -* [Bellman-Ford (adjacency matrix, negative cycles)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyMatrix.java) **- O(V3)** -* [:movie_camera:](https://www.youtube.com/watch?v=oDqjPvD54Ss)[Breadth first search (adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java) **- O(V+E)** -* [Breadth first search (adjacency list, fast queue)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java) **- O(V+E)** -* [:movie_camera:](https://www.youtube.com/watch?v=aZXi1unBdJA)[Bridges/cut edges (adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyList.java) **- O(V+E)** -* [Find connected components (adjacency list, union find)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/ConnectedComponentsAdjacencyList.java) **- O(Elog(E))** -* [Find connected components (adjacency list, DFS)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/ConnectedComponentsDfsSolverAdjacencyList.java) **- O(V+E)** -* [Depth first search (adjacency list, iterative)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterative.java) **- O(V+E)** -* [Depth first search (adjacency list, iterative, fast stack)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java) **- O(V+E)** -* [:movie_camera:](https://www.youtube.com/watch?v=7fujbpJ0LB4)[Depth first search (adjacency list, recursive)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListRecursive.java) **- O(V+E)** -* [:movie_camera:](https://www.youtube.com/watch?v=pSqmAO-m7Lk)[Dijkstra's shortest path (adjacency list, lazy implementation)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java) **- O(Elog(V))** -* [:movie_camera:](https://www.youtube.com/watch?v=pSqmAO-m7Lk)[Dijkstra's shortest path (adjacency list, eager implementation + D-ary heap)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyListWithDHeap.java) **- O(ElogE/V(V))** -* [:movie_camera:](https://www.youtube.com/watch?v=8MpoO2zA2l4)[Eulerian Path (directed edges)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/EulerianPathDirectedEdgesAdjacencyList.java) **- O(E+V)** -* [:movie_camera:](https://www.youtube.com/watch?v=4NQ3HnhyNfQ)[Floyd Warshall algorithm (adjacency matrix, negative cycle check)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/FloydWarshallSolver.java) **- O(V3)** -* [Graph diameter (adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/GraphDiameter.java) **- O(VE)** -* [Kruskal's min spanning tree algorithm (edge list, union find)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/KruskalsEdgeList.java) **- O(Elog(E))** -* [:movie_camera:](https://www.youtube.com/watch?v=JZBQLXgSGfs)[Kruskal's min spanning tree algorithm (edge list, union find, lazy sorting)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/KruskalsEdgeListPartialSortSolver.java) **- O(Elog(E))** -* [:movie_camera:](https://www.youtube.com/watch?v=jsmMtJpPnhU)[Prim's min spanning tree algorithm (lazy version, adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyList.java) **- O(Elog(E))** -* [Prim's min spanning tree algorithm (lazy version, adjacency matrix)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyMatrix.java) **- O(V2)** -* [:movie_camera:](https://www.youtube.com/watch?v=xq3ABa-px_g)[Prim's min spanning tree algorithm (eager version, adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java) **- O(Elog(V))** -* [Steiner tree (minimum spanning tree generalization)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/SteinerTree.java) **- O(V3 + V2 * 2T + V * 3T)** -* [:movie_camera:](https://www.youtube.com/watch?v=TyWtx7q2D7Y)[Tarjan's strongly connected components algorithm (adjacency list) ](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyList.java) **- O(V+E)** -* [Tarjan's strongly connected components algorithm (adjacency matrix) ](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/TarjanAdjacencyMatrix.java) **- O(V2)** -* [:movie_camera:](https://www.youtube.com/watch?v=eL-KzMXSXXI)[Topological sort (acyclic graph, adjacency list)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyList.java) **- O(V+E)** -* [Topological sort (acyclic graph, adjacency matrix)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyMatrix.java) **- O(V2)** -* [Traveling Salesman Problem (brute force)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/TspBruteForce.java) **- O(n!)** -* [:movie_camera:](https://www.youtube.com/watch?v=cY4HiiFHO1o)[Traveling Salesman Problem (dynamic programming, iterative)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingIterative.java) **- O(n22n)** -* [Traveling Salesman Problem (dynamic programming, recursive)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingRecursive.java) **- O(n22n)** + +- [Bipartite graph verification (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyList.java) **- O(V+E)** +- [:movie_camera:](https://www.youtube.com/watch?v=LdOnanfc5TM) [Max flow & Min cut (Ford-Fulkerson with DFS, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDfsSolverAdjacencyList.java) **- O(fE)** +- [Max flow & Min cut (Ford-Fulkerson with DFS, adjacency matrix)](src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDFSAdjacencyMatrix.java) **- O(fV2)** +- [:movie_camera:](https://www.youtube.com/watch?v=RppuJYwlcI8) [Max flow & Min cut (Edmonds-Karp, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/EdmondsKarpAdjacencyList.java) **- O(VE2)** +- [:movie_camera:](https://youtu.be/1ewLrXUz4kk) [Max flow & Min cut (Capacity scaling, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/CapacityScalingSolverAdjacencyList.java) **- O(E2log2(U))** +- [:movie_camera:](https://youtu.be/M6cm8UeeziI) [Max flow & Min cut (Dinic's, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/Dinics.java) **- O(EV2) or O(E√V) for bipartite graphs** +- [Maximum Cardinality Bipartite Matching (augmenting path algorithm, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MaximumCardinalityBipartiteMatchingAugmentingPathAdjacencyList.java) **- O(VE)** +- [Min Cost Max Flow (Bellman-Ford, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowWithBellmanFord.java) **- O(E2V2)** +- [Min Cost Max Flow (Johnson's algorithm, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowJohnsons.java) **- O(E2Vlog(V))** + +### Main graph theory algorithms + +- [Articulation points/cut vertices (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyList.java) **- O(V+E)** +- [Bellman-Ford (edge list, negative cycles, fast & optimized)](src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordEdgeList.java) **- O(VE)** +- [:movie_camera:](https://www.youtube.com/watch?v=lyw4FaxrwHg) [Bellman-Ford (adjacency list, negative cycles)](src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyList.java) **- O(VE)** +- [Bellman-Ford (adjacency matrix, negative cycles)](src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyMatrix.java) **- O(V3)** +- [:movie_camera:](https://www.youtube.com/watch?v=oDqjPvD54Ss) [Breadth first search (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java) **- O(V+E)** +- [Breadth first search (adjacency list, fast queue)](src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java) **- O(V+E)** +- [Bridges/cut edges (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyList.java) **- O(V+E)** +- [Find connected components (adjacency list, union find)](src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsAdjacencyList.java) **- O(Elog(E))** +- [Find connected components (adjacency list, DFS)](src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsDfsSolverAdjacencyList.java) **- O(V+E)** +- [Depth first search (adjacency list, iterative)](src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterative.java) **- O(V+E)** +- [Depth first search (adjacency list, iterative, fast stack)](src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java) **- O(V+E)** +- [:movie_camera:](https://www.youtube.com/watch?v=7fujbpJ0LB4) [Depth first search (adjacency list, recursive)](src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListRecursive.java) **- O(V+E)** +- [:movie_camera:](https://www.youtube.com/watch?v=pSqmAO-m7Lk) [Dijkstra's shortest path (adjacency list, lazy implementation)](src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java) **- O(Elog(V))** +- [:movie_camera:](https://www.youtube.com/watch?v=pSqmAO-m7Lk) [Dijkstra's shortest path (adjacency list, eager implementation + D-ary heap)](src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyListWithDHeap.java) **- O(ElogE/V(V))** +- [:movie_camera:](https://www.youtube.com/watch?v=8MpoO2zA2l4) [Eulerian Path (directed edges)](src/main/java/com/williamfiset/algorithms/graphtheory/EulerianPathDirectedEdgesAdjacencyList.java) **- O(E+V)** +- [:movie_camera:](https://www.youtube.com/watch?v=4NQ3HnhyNfQ) [Floyd Warshall algorithm (adjacency matrix, negative cycle check)](src/main/java/com/williamfiset/algorithms/graphtheory/FloydWarshallSolver.java) **- O(V3)** +- [Graph diameter (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/GraphDiameter.java) **- O(VE)** +- [:movie_camera:](https://www.youtube.com/watch?v=cIBFEhD77b4) [Kahn's algorithm (topological sort, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/Kahns.java) **- O(E+V)** +- [Kruskal's min spanning tree algorithm (edge list, union find)](src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeList.java) **- O(Elog(E))** +- [:movie_camera:](https://www.youtube.com/watch?v=JZBQLXgSGfs) [Kruskal's min spanning tree algorithm (edge list, union find, lazy sorting)](src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeListPartialSortSolver.java) **- O(Elog(E))** +- [Kosaraju's strongly connected components algorithm (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/Kosaraju.java) **- O(V+E)** +- [:movie_camera:](https://www.youtube.com/watch?v=jsmMtJpPnhU) [Prim's min spanning tree algorithm (lazy version, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyList.java) **- O(Elog(E))** +- [Prim's min spanning tree algorithm (lazy version, adjacency matrix)](src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyMatrix.java) **- O(V2)** +- [:movie_camera:](https://www.youtube.com/watch?v=xq3ABa-px_g) [Prim's min spanning tree algorithm (eager version, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java) **- O(Elog(V))** +- [Steiner tree (minimum spanning tree generalization)](src/main/java/com/williamfiset/algorithms/graphtheory/SteinerTree.java) **- O(V3 + V2 _ 2T + V _ 3T)** +- [:movie_camera:](https://www.youtube.com/watch?v=wUgWX0nc4NY) [Tarjan's strongly connected components algorithm (adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyList.java) **- O(V+E)** +- [:movie_camera:](https://www.youtube.com/watch?v=eL-KzMXSXXI) [Topological sort (acyclic graph, adjacency list)](src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyList.java) **- O(V+E)** +- [Topological sort (acyclic graph, adjacency matrix)](src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyMatrix.java) **- O(V2)** +- [Traveling Salesman Problem (brute force)](src/main/java/com/williamfiset/algorithms/graphtheory/TspBruteForce.java) **- O(n!)** +- [:movie_camera:](https://www.youtube.com/watch?v=cY4HiiFHO1o) [Traveling Salesman Problem (dynamic programming, iterative)](src/main/java/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingIterative.java) **- O(n22n)** +- [Traveling Salesman Problem (dynamic programming, recursive)](src/main/java/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingRecursive.java) **- O(n22n)** # Linear algebra -* [Freivald's algorithm (matrix multiplication verification)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/linearalgebra/FreivaldsAlgorithm.java) **- O(kn2)** -* [Gaussian elimination (solve system of linear equations)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/linearalgebra/GaussianElimination.java) **- O(cr2)** -* [Gaussian elimination (modular version, prime finite field)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/linearalgebra/ModularLinearAlgebra.java) **- O(cr2)** -* [Linear recurrence solver (finds nth term in a recurrence relation)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/linearalgebra/LinearRecurrenceSolver.java) **- O(m3log(n))** -* [Matrix determinant (Laplace/cofactor expansion)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/linearalgebra/MatrixDeterminantLaplaceExpansion.java) **- O((n+2)!)** -* [Matrix inverse](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/linearalgebra/MatrixInverse.java) **- O(n3)** -* [Matrix multiplication](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/linearalgebra/MatrixMultiplication.java) **- O(n3)** -* [Matrix power](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/linearalgebra/MatrixPower.java) **- O(n3log(p))** -* [Square matrix rotation](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/linearalgebra/RotateSquareMatrixInplace.java) **- O(n2)** + +- [Freivald's algorithm (matrix multiplication verification)](src/main/java/com/williamfiset/algorithms/linearalgebra/FreivaldsAlgorithm.java) **- O(kn2)** +- [Gaussian elimination (solve system of linear equations)](src/main/java/com/williamfiset/algorithms/linearalgebra/GaussianElimination.java) **- O(cr2)** +- [Gaussian elimination (modular version, prime finite field)](src/main/java/com/williamfiset/algorithms/linearalgebra/ModularLinearAlgebra.java) **- O(cr2)** +- [Linear recurrence solver (finds nth term in a recurrence relation)](src/main/java/com/williamfiset/algorithms/linearalgebra/LinearRecurrenceSolver.java) **- O(m3log(n))** +- [Matrix determinant (Laplace/cofactor expansion)](src/main/java/com/williamfiset/algorithms/linearalgebra/MatrixDeterminantLaplaceExpansion.java) **- O((n+2)!)** +- [Matrix inverse](src/main/java/com/williamfiset/algorithms/linearalgebra/MatrixInverse.java) **- O(n3)** +- [Matrix multiplication](src/main/java/com/williamfiset/algorithms/linearalgebra/MatrixMultiplication.java) **- O(n3)** +- [Matrix power](src/main/java/com/williamfiset/algorithms/linearalgebra/MatrixPower.java) **- O(n3log(p))** +- [Square matrix rotation](src/main/java/com/williamfiset/algorithms/linearalgebra/RotateSquareMatrixInplace.java) **- O(n2)** # Mathematics -* [[UNTESTED] Chinese remainder theorem](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/ChineseRemainderTheorem.java) -* [Prime number sieve (sieve of Eratosthenes)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/SieveOfEratosthenes.java) **- O(nlog(log(n)))** -* [Prime number sieve (sieve of Eratosthenes, compressed)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/CompressedPrimeSieve.java) **- O(nlog(log(n)))** -* [Totient function (phi function, relatively prime number count)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/EulerTotientFunction.java) **- O(n1/4)** -* [Totient function using sieve (phi function, relatively prime number count)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/EulerTotientFunctionWithSieve.java) **- O(nlog(log(n)))** -* [Extended euclidean algorithm](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/ExtendedEuclideanAlgorithm.java) **- ~O(log(a + b))** -* [Greatest Common Divisor (GCD)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/GCD.java) **- ~O(log(a + b))** -* [Fast Fourier transform (quick polynomial multiplication)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/FastFourierTransform.java) **- O(nlog(n))** -* [Fast Fourier transform (quick polynomial multiplication, complex numbers)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/FastFourierTransformComplexNumbers.java) **- O(nlog(n))** -* [Primality check](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/IsPrime.java) **- O(√n)** -* [Primality check (Rabin-Miller)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/RabinMillerPrimalityTest.py) **- O(k)** -* [Least Common Multiple (LCM)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/LCM.java) **- ~O(log(a + b))** -* [Modular inverse](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/ModularInverse.java) **- ~O(log(a + b))** -* [Prime factorization (pollard rho)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/PrimeFactorization.java) **- O(n1/4)** -* [Relatively prime check (coprimality check)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/math/RelativelyPrime.java) **- ~O(log(a + b))** + +- [[UNTESTED] Chinese remainder theorem](src/main/java/com/williamfiset/algorithms/math/ChineseRemainderTheorem.java) +- [Prime number sieve (sieve of Eratosthenes)](src/main/java/com/williamfiset/algorithms/math/SieveOfEratosthenes.java) **- O(nlog(log(n)))** +- [Prime number sieve (sieve of Eratosthenes, compressed)](src/main/java/com/williamfiset/algorithms/math/CompressedPrimeSieve.java) **- O(nlog(log(n)))** +- [Totient function (phi function, relatively prime number count)](src/main/java/com/williamfiset/algorithms/math/EulerTotientFunction.java) **- O(n1/4)** +- [Totient function using sieve (phi function, relatively prime number count)](src/main/java/com/williamfiset/algorithms/math/EulerTotientFunctionWithSieve.java) **- O(nlog(log(n)))** +- [Extended euclidean algorithm](src/main/java/com/williamfiset/algorithms/math/ExtendedEuclideanAlgorithm.java) **- ~O(log(a + b))** +- [Greatest Common Divisor (GCD)](src/main/java/com/williamfiset/algorithms/math/GCD.java) **- ~O(log(a + b))** +- [Fast Fourier transform (quick polynomial multiplication)](src/main/java/com/williamfiset/algorithms/math/FastFourierTransform.java) **- O(nlog(n))** +- [Fast Fourier transform (quick polynomial multiplication, complex numbers)](src/main/java/com/williamfiset/algorithms/math/FastFourierTransformComplexNumbers.java) **- O(nlog(n))** +- [Primality check](src/main/java/com/williamfiset/algorithms/math/IsPrime.java) **- O(√n)** +- [Primality check (Rabin-Miller)](src/main/java/com/williamfiset/algorithms/math/RabinMillerPrimalityTest.py) **- O(k)** +- [Least Common Multiple (LCM)](src/main/java/com/williamfiset/algorithms/math/LCM.java) **- ~O(log(a + b))** +- [Modular inverse](src/main/java/com/williamfiset/algorithms/math/ModularInverse.java) **- ~O(log(a + b))** +- [Prime factorization (pollard rho)](src/main/java/com/williamfiset/algorithms/math/PrimeFactorization.java) **- O(n1/4)** +- [Relatively prime check (coprimality check)](src/main/java/com/williamfiset/algorithms/math/RelativelyPrime.java) **- ~O(log(a + b))** # Other -* [Bit manipulations](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/other/BitManipulations.java) **- O(1)** -* [List permutations](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/other/Permutations.java) **- O(n!)** -* [:movie_camera:](https://www.youtube.com/watch?v=RnlHPR0lyOE)[Power set (set of all subsets)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/other/PowerSet.java) **- O(2n)** -* [Set combinations](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/other/Combinations.java) **- O(n choose r)** -* [Set combinations with repetition](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/other/CombinationsWithRepetition.java) **- O((n+r-1) choose r)** -* [Sliding Window Minimum/Maximum](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/other/SlidingWindowMaximum.java) **- O(1)** -* [Square Root Decomposition](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/other/SquareRootDecomposition.java) **- O(1) point updates, O(√n) range queries** -* [Unique set combinations](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/other/UniqueCombinations.java) **- O(n choose r)** + +- [Bit manipulations](src/main/java/com/williamfiset/algorithms/other/BitManipulations.java) **- O(1)** +- [List permutations](src/main/java/com/williamfiset/algorithms/other/Permutations.java) **- O(n!)** +- [:movie_camera:](https://www.youtube.com/watch?v=RnlHPR0lyOE) [Power set (set of all subsets)](src/main/java/com/williamfiset/algorithms/other/PowerSet.java) **- O(2n)** +- [Set combinations](src/main/java/com/williamfiset/algorithms/other/Combinations.java) **- O(n choose r)** +- [Set combinations with repetition](src/main/java/com/williamfiset/algorithms/other/CombinationsWithRepetition.java) **- O((n+r-1) choose r)** +- [Sliding Window Minimum/Maximum](src/main/java/com/williamfiset/algorithms/other/SlidingWindowMaximum.java) **- O(1)** +- [Square Root Decomposition](src/main/java/com/williamfiset/algorithms/other/SquareRootDecomposition.java) **- O(1) point updates, O(√n) range queries** +- [Unique set combinations](src/main/java/com/williamfiset/algorithms/other/UniqueCombinations.java) **- O(n choose r)** +- [Lazy Range Adder](src/main/java/com/williamfiset/algorithms/other/LazyRangeAdder.java) **- O(1) range updates, O(n) to finalize all updates** # Search algorithms -* [Binary search (real numbers)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/search/BinarySearch.java) **- O(log(n))** -* [Interpolation search (discrete discrete)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/search/InterpolationSearch.java) **- O(n) or O(log(log(n))) with uniform input** -* [Ternary search (real numbers)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/search/TernarySearch.java) **- O(log(n))** -* [Ternary search (discrete numbers)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/search/TernarySearchDiscrete.java) **- O(log(n))** + +- [Binary search (real numbers)](src/main/java/com/williamfiset/algorithms/search/BinarySearch.java) **- O(log(n))** +- [Interpolation search (discrete discrete)](src/main/java/com/williamfiset/algorithms/search/InterpolationSearch.java) **- O(n) or O(log(log(n))) with uniform input** +- [Ternary search (real numbers)](src/main/java/com/williamfiset/algorithms/search/TernarySearch.java) **- O(log(n))** +- [Ternary search (discrete numbers)](src/main/java/com/williamfiset/algorithms/search/TernarySearchDiscrete.java) **- O(log(n))** # Sorting algorithms -* [Bubble sort](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/sorting/BubbleSort.java) **- O(n2)** -* [Bucket sort](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/sorting/BucketSort.java) **- Θ(n + k)** -* [Counting sort](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/sorting/CountingSort.java) **- O(n + k)** -* [Heapsort](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/sorting/Heapsort.java) **- O(nlog(n))** -* [Insertion sort](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/sorting/InsertionSort.java) **- O(n2)** -* [Mergesort](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/sorting/Mergesort.java) **- O(nlog(n))** -* [Quicksort (in-place, Hoare partitioning)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/sorting/Quicksort.java) **- Θ(nlog(n))** -* [Selection sort](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/sorting/SelectionSort.java) **- O(n2)** + +- [Bubble sort](src/main/java/com/williamfiset/algorithms/sorting/BubbleSort.java) **- O(n2)** +- [Bucket sort](src/main/java/com/williamfiset/algorithms/sorting/BucketSort.java) **- Θ(n + k)** +- [Counting sort](src/main/java/com/williamfiset/algorithms/sorting/CountingSort.java) **- O(n + k)** +- [Heapsort](src/main/java/com/williamfiset/algorithms/sorting/Heapsort.java) **- O(nlog(n))** +- [Insertion sort](src/main/java/com/williamfiset/algorithms/sorting/InsertionSort.java) **- O(n2)** +- [:movie_camera:](https://www.youtube.com/watch?v=-3u1C1URNZY) [Mergesort](src/main/java/com/williamfiset/algorithms/sorting/MergeSort.java) **- O(nlog(n))** +- [Quicksort (in-place, Hoare partitioning)](src/main/java/com/williamfiset/algorithms/sorting/QuickSort.java) **- Θ(nlog(n))** +- [Quicksort3 (Dutch National Flag algorithm)](src/main/java/com/williamfiset/algorithms/sorting/QuickSort3.java) **- Θ(nlog(n))** +- [Selection sort](src/main/java/com/williamfiset/algorithms/sorting/SelectionSort.java) **- O(n2)** +- [Radix sort](src/main/java/com/williamfiset/algorithms/sorting/RadixSort.java) **- O(n\*w)** # String algorithms -* [Booth's algorithm (finds lexicographically smallest string rotation)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/strings/BoothsAlgorithm.java) **- O(n)** -* [Knuth-Morris-Pratt algorithm (finds pattern matches in text)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/strings/KMP.java) **- O(n+m)** -* [Longest Common Prefix (LCP) array](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/strings/LongestCommonPrefixArray.java) **- O(nlog(n)) bounded by SA construction, otherwise O(n)** -* [:movie_camera:](https://www.youtube.com/watch?v=Ic80xQFWevc)[Longest Common Substring (LCS)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/strings/LongestCommonSubstring.java) **- O(nlog(n)) bounded by SA construction, otherwise O(n)** -* [:movie_camera:](https://www.youtube.com/watch?v=OptoHwC3D-Y)[Longest Repeated Substring (LRS)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/strings/LongestRepeatedSubstring.java) **- O(nlog(n))** -* [Manacher's algorithm (finds all palindromes in text)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/strings/ManachersAlgorithm.java) **- O(n)** -* [Rabin-Karp algorithm (finds pattern matches in text)](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/strings/RabinKarp.java) **- O(n+m)** -* [Substring verification with suffix array](https://github.com/williamfiset/Algorithms/blob/master/com/williamfiset/algorithms/strings/SubstringVerificationSuffixArray.java) **- O(nlog(n)) SA construction and O(mlog(n)) per query** + +- [Booth's algorithm (finds lexicographically smallest string rotation)](src/main/java/com/williamfiset/algorithms/strings/BoothsAlgorithm.java) **- O(n)** +- [Knuth-Morris-Pratt algorithm (finds pattern matches in text)](src/main/java/com/williamfiset/algorithms/strings/KMP.java) **- O(n+m)** +- [Longest Common Prefix (LCP) array](src/main/java/com/williamfiset/algorithms/strings/LongestCommonPrefixArray.java) **- O(nlog(n)) bounded by SA construction, otherwise O(n)** +- [:movie_camera:](https://www.youtube.com/watch?v=Ic80xQFWevc) [Longest Common Substring (LCS)](src/main/java/com/williamfiset/algorithms/strings/LongestCommonSubstring.java) **- O(nlog(n)) bounded by SA construction, otherwise O(n)** +- [:movie_camera:](https://www.youtube.com/watch?v=OptoHwC3D-Y) [Longest Repeated Substring (LRS)](src/main/java/com/williamfiset/algorithms/strings/LongestRepeatedSubstring.java) **- O(nlog(n))** +- [Manacher's algorithm (finds all palindromes in text)](src/main/java/com/williamfiset/algorithms/strings/ManachersAlgorithm.java) **- O(n)** +- [Rabin-Karp algorithm (finds pattern match positions in text)](src/main/java/com/williamfiset/algorithms/strings/RabinKarp.java) **- O(n+m)** +- [Substring verification with suffix array](src/main/java/com/williamfiset/algorithms/strings/SubstringVerificationSuffixArray.java) **- O(nlog(n)) SA construction and O(mlog(n)) per query** # License This repository is released under the [MIT license](https://opensource.org/licenses/MIT). In short, this means you are free to use this software in any personal, open-source or commercial projects. Attribution is optional but appreciated. + +# Donate + +Consider donating to support my creation of educational content: + +[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/donate?hosted_button_id=JUP2HZ6JUPB5C) diff --git a/build.gradle b/build.gradle index 5c00148ec..2c69fb357 100644 --- a/build.gradle +++ b/build.gradle @@ -1,48 +1,51 @@ -apply plugin: 'java' -apply plugin: "com.github.sherter.google-java-format" - -// https://github.com/sherter/google-java-format-gradle-plugin buildscript { repositories { maven { url "https://plugins.gradle.org/m2/" } } - dependencies { - classpath "gradle.plugin.com.github.sherter.google-java-format:google-java-format-gradle-plugin:0.8" - } } -// Assume Java 8 -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +plugins { + // https://github.com/diffplug/spotless + id("com.diffplug.spotless") version "6.25.0" +} + +apply plugin: 'java' +apply plugin: 'application' + +mainClassName = findProperty("main") ?: "com.williamfiset.algorithms.${findProperty("algorithm") ?: 'missingPackage.missingClass'}" repositories { mavenLocal() mavenCentral() } +// Describe all project deps. Use 'testCompile' for test dependencies and +// 'compile' for project dependencies. dependencies { - - // JUnit framework - testCompile 'junit:junit:4.+' - compile 'junit:junit:4.+' + // Apache commons lang + testImplementation 'org.apache.commons:commons-lang3:3.14.0' // JUnit 5 / Jupiter - testImplementation('org.junit.jupiter:junit-jupiter-api:5.4.2') - testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.2') - - // Test mocking framework - testCompile "org.mockito:mockito-core:1.+" + testImplementation('org.junit.jupiter:junit-jupiter:5.10.0') + testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.10.0') // Google Guava lib - compile group: 'com.google.guava', name: 'guava', version: '22.0' + testImplementation group: 'com.google.guava', name: 'guava', version: '33.3.1-jre' - // Google truth API - compile "com.google.truth:truth:0.36" + // Google Truth test framework + // https://mvnrepository.com/artifact/com.google.truth/truth + testImplementation group: 'com.google.truth', name: 'truth', version: '1.1.5' - // Apache commons lang - compile 'org.apache.commons:commons-lang3:3.6' + // Test mocking framework + testImplementation "org.mockito:mockito-core:5.+" +} + +spotless { + java { + googleJavaFormat() + } } test { @@ -50,50 +53,28 @@ test { testLogging.showStandardStreams = true } -String javaAlgorithmsPackage = "com/williamfiset/algorithms"; -String javatestsAlgorithmsPackage = "javatests/com/williamfiset/algorithms"; +tasks.named('test', Test) { + useJUnitPlatform() +} -sourceSets { - main { - java { - srcDirs = [ - javaAlgorithmsPackage + '/ai', - javaAlgorithmsPackage + '/dp', - javaAlgorithmsPackage + '/datastructures', - javaAlgorithmsPackage + '/geometry', - javaAlgorithmsPackage + '/graphtheory', - javaAlgorithmsPackage + '/linearalgebra', - javaAlgorithmsPackage + '/math', - javaAlgorithmsPackage + '/other', - javaAlgorithmsPackage + '/search', - javaAlgorithmsPackage + '/sorting', - javaAlgorithmsPackage + '/strings', - javaAlgorithmsPackage + '/utils' - ] - } - } - test { - java { - srcDirs = [ - javatestsAlgorithmsPackage + '/ai', - javatestsAlgorithmsPackage + '/datastructures', - javatestsAlgorithmsPackage + '/dp', - javatestsAlgorithmsPackage + '/geometry', - javatestsAlgorithmsPackage + '/graphtheory', - javatestsAlgorithmsPackage + '/linearalgebra', - javatestsAlgorithmsPackage + '/math', - javatestsAlgorithmsPackage + '/other', - javatestsAlgorithmsPackage + '/search', - javatestsAlgorithmsPackage + '/sorting', - javatestsAlgorithmsPackage + '/strings', - javatestsAlgorithmsPackage + '/utils' - ] - } - } +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' + options.compilerArgs += [ + '-Xlint:unchecked', + '-Xlint:deprecation' + ] } +// Options when compiling tests +compileTestJava { + options.encoding = 'UTF-8' + options.compilerArgs += [ + '-Xlint:unchecked', + '-Xlint:deprecation' + ] +} task buildDependenciesFolder(type: Copy) { - from configurations.compile + from configurations.implementation into './dependencies' } diff --git a/com/williamfiset/algorithms/datastructures/balancedtree/RedBlackTree.java b/com/williamfiset/algorithms/datastructures/balancedtree/RedBlackTree.java deleted file mode 100644 index 96aa92a6c..000000000 --- a/com/williamfiset/algorithms/datastructures/balancedtree/RedBlackTree.java +++ /dev/null @@ -1,319 +0,0 @@ -/** - * This file contains an implementation of a Red-Black tree. A RB tree is a special type of binary - * tree which self balances itself to keep operations logarithmic. - * - *

Great visualization tool: https://www.cs.usfca.edu/~galles/visualization/RedBlack.html - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.datastructures.balancedtree; - -public class RedBlackTree> implements Iterable { - - public static final boolean RED = true; - public static final boolean BLACK = false; - - public class Node { - - // The color of this node. By default all nodes start red. - public boolean color = RED; - - // The value/data contained within the node. - public T value; - - // The left, right and parent references of this node. - public Node left, right, parent; - - public Node(T value, Node parent) { - this.value = value; - this.parent = parent; - } - } - - // The root node of the RB tree. - public Node root; - - // Tracks the number of nodes inside the tree. - private int nodeCount = 0; - - // Returns the number of nodes in the tree. - public int size() { - return nodeCount; - } - - // Returns whether or not the tree is empty. - public boolean isEmpty() { - return size() == 0; - } - - public boolean contains(T value) { - - Node node = root; - - if (node == null || value == null) return false; - - while (node != null) { - - // Compare current value to the value in the node. - int cmp = value.compareTo(node.value); - - // Dig into left subtree. - if (cmp < 0) node = node.left; - - // Dig into right subtree. - else if (cmp > 0) node = node.right; - - // Found value in tree. - else return true; - } - - return false; - } - - public boolean insert(T value) { - - if (value == null) throw new IllegalArgumentException(); - - // No root node. - if (root == null) { - root = new Node(value, null); - insertionRelabel(root); - nodeCount++; - return true; - } - - for (Node node = root; ; ) { - - int cmp = value.compareTo(node.value); - - // Left subtree. - if (cmp < 0) { - if (node.left == null) { - node.left = new Node(value, node); - insertionRelabel(node.left); - nodeCount++; - return true; - } - node = node.left; - - // Right subtree. - } else if (cmp > 0) { - if (node.right == null) { - node.right = new Node(value, node); - insertionRelabel(node.right); - nodeCount++; - return true; - } - node = node.right; - - // The value we're trying to insert already exists in the tree. - } else return false; - } - } - - private void insertionRelabel(Node node) { - - Node parent = node.parent; - - // Root node case. - if (parent == null) { - node.color = BLACK; - root = node; - return; - } - - Node grandParent = parent.parent; - if (grandParent == null) return; - - // The red-black tree invariant is already satisfied. - if (parent.color == BLACK || node.color == BLACK) return; - - boolean nodeIsLeftChild = (parent.left == node); - boolean parentIsLeftChild = (parent == grandParent.left); - Node uncle = parentIsLeftChild ? grandParent.right : grandParent.left; - boolean uncleIsRedNode = (uncle == null) ? BLACK : uncle.color; - - if (uncleIsRedNode) { - - parent.color = BLACK; - grandParent.color = RED; - uncle.color = BLACK; - - // At this point the parent node is red and so is the new child node. - // We need to re-balance somehow because no two red nodes can be - // adjacent to one another. - } else { - - // Parent node is a left child. - if (parentIsLeftChild) { - - // Left-left case. - if (nodeIsLeftChild) { - grandParent = leftLeftCase(grandParent); - - // Left-right case. - } else { - grandParent = leftRightCase(grandParent); - } - - // Parent node is a right child. - } else { - - // Right-left case. - if (nodeIsLeftChild) { - grandParent = rightLeftCase(grandParent); - - // Right-right case. - } else { - grandParent = rightRightCase(grandParent); - } - } - } - - insertionRelabel(grandParent); - } - - private void swapColors(Node a, Node b) { - boolean tmpColor = a.color; - a.color = b.color; - b.color = tmpColor; - } - - private Node leftLeftCase(Node node) { - node = rightRotate(node); - swapColors(node, node.right); - return node; - } - - private Node leftRightCase(Node node) { - node.left = leftRotate(node.left); - return leftLeftCase(node); - } - - private Node rightRightCase(Node node) { - node = leftRotate(node); - swapColors(node, node.left); - return node; - } - - private Node rightLeftCase(Node node) { - node.right = rightRotate(node.right); - return rightRightCase(node); - } - - private Node rightRotate(Node parent) { - - Node grandParent = parent.parent; - Node child = parent.left; - - parent.left = child.right; - if (child.right != null) child.right.parent = parent; - - child.right = parent; - parent.parent = child; - - child.parent = grandParent; - updateParentChildLink(grandParent, parent, child); - - return child; - } - - private Node leftRotate(Node parent) { - - Node grandParent = parent.parent; - Node child = parent.right; - - parent.right = child.left; - if (child.left != null) child.left.parent = parent; - - child.left = parent; - parent.parent = child; - - child.parent = grandParent; - updateParentChildLink(grandParent, parent, child); - - return child; - } - - // Sometimes the left or right child node of a parent changes and the - // parent's reference needs to be updated to point to the new child. - // This is a helper method to do just that. - private void updateParentChildLink(Node parent, Node oldChild, Node newChild) { - if (parent != null) { - if (parent.left == oldChild) { - parent.left = newChild; - } else { - parent.right = newChild; - } - } - } - - // Helper method to find the leftmost node (which has the smallest value) - private Node findMin(Node node) { - while (node.left != null) node = node.left; - return node; - } - - // Helper method to find the rightmost node (which has the largest value) - private Node findMax(Node node) { - while (node.right != null) node = node.right; - return node; - } - - // Returns as iterator to traverse the tree in order. - @Override - public java.util.Iterator iterator() { - - final int expectedNodeCount = nodeCount; - final java.util.Stack stack = new java.util.Stack<>(); - stack.push(root); - - return new java.util.Iterator() { - Node trav = root; - - @Override - public boolean hasNext() { - if (expectedNodeCount != nodeCount) throw new java.util.ConcurrentModificationException(); - return root != null && !stack.isEmpty(); - } - - @Override - public T next() { - - if (expectedNodeCount != nodeCount) throw new java.util.ConcurrentModificationException(); - - while (trav != null && trav.left != null) { - stack.push(trav.left); - trav = trav.left; - } - - Node node = stack.pop(); - - if (node.right != null) { - stack.push(node.right); - trav = node.right; - } - - return node.value; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - // Example usage of RB tree: - public static void main(String[] args) { - - int[] values = {5, 8, 1, -4, 6, -2, 0, 7}; - RedBlackTree rbTree = new RedBlackTree<>(); - for (int v : values) rbTree.insert(v); - - System.out.printf("RB tree contains %d: %s\n", 6, rbTree.contains(6)); - System.out.printf("RB tree contains %d: %s\n", -5, rbTree.contains(-5)); - System.out.printf("RB tree contains %d: %s\n", 1, rbTree.contains(1)); - System.out.printf("RB tree contains %d: %s\n", 99, rbTree.contains(99)); - } -} diff --git a/com/williamfiset/algorithms/datastructures/dynamicarray/DynamicArray.java b/com/williamfiset/algorithms/datastructures/dynamicarray/DynamicArray.java deleted file mode 100644 index 3637ffe26..000000000 --- a/com/williamfiset/algorithms/datastructures/dynamicarray/DynamicArray.java +++ /dev/null @@ -1,127 +0,0 @@ -/** - * A generic dynamic array implementation - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.datastructures.dynamicarray; - -@SuppressWarnings("unchecked") -public class DynamicArray implements Iterable { - - private T[] arr; - private int len = 0; // length user thinks array is - private int capacity = 0; // Actual array size - - public DynamicArray() { - this(16); - } - - public DynamicArray(int capacity) { - if (capacity < 0) throw new IllegalArgumentException("Illegal Capacity: " + capacity); - this.capacity = capacity; - arr = (T[]) new Object[capacity]; - } - - public int size() { - return len; - } - - public boolean isEmpty() { - return size() == 0; - } - - public T get(int index) { - return arr[index]; - } - - public void set(int index, T elem) { - arr[index] = elem; - } - - public void clear() { - for (int i = 0; i < len; i++) arr[i] = null; - len = 0; - } - - public void add(T elem) { - - // Time to resize! - if (len + 1 >= capacity) { - if (capacity == 0) capacity = 1; - else capacity *= 2; // double the size - T[] new_arr = (T[]) new Object[capacity]; - for (int i = 0; i < len; i++) new_arr[i] = arr[i]; - arr = new_arr; // arr has extra nulls padded - } - - arr[len++] = elem; - } - - // Removes an element at the specified index in this array. - public T removeAt(int rm_index) { - if (rm_index >= len || rm_index < 0) throw new IndexOutOfBoundsException(); - T data = arr[rm_index]; - T[] new_arr = (T[]) new Object[len - 1]; - for (int i = 0, j = 0; i < len; i++, j++) - if (i == rm_index) j--; // Skip over rm_index by fixing j temporarily - else new_arr[j] = arr[i]; - arr = new_arr; - capacity = --len; - return data; - } - - public boolean remove(Object obj) { - int index = indexOf(obj); - if (index == -1) return false; - removeAt(index); - return true; - } - - public int indexOf(Object obj) { - for (int i = 0; i < len; i++) { - if (obj == null) { - if (arr[i] == null) return i; - } else { - if (obj.equals(arr[i])) return i; - } - } - return -1; - } - - public boolean contains(Object obj) { - return indexOf(obj) != -1; - } - - // Iterator is still fast but not as fast as iterative for loop - @Override - public java.util.Iterator iterator() { - return new java.util.Iterator() { - int index = 0; - - @Override - public boolean hasNext() { - return index < len; - } - - @Override - public T next() { - return arr[index++]; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - @Override - public String toString() { - if (len == 0) return "[]"; - else { - StringBuilder sb = new StringBuilder(len).append("["); - for (int i = 0; i < len - 1; i++) sb.append(arr[i] + ", "); - return sb.append(arr[len - 1] + "]").toString(); - } - } -} diff --git a/com/williamfiset/algorithms/datastructures/segmenttree/RangeQueryPointUpdateSegmentTree.java b/com/williamfiset/algorithms/datastructures/segmenttree/RangeQueryPointUpdateSegmentTree.java deleted file mode 100644 index 54ebe65e1..000000000 --- a/com/williamfiset/algorithms/datastructures/segmenttree/RangeQueryPointUpdateSegmentTree.java +++ /dev/null @@ -1,53 +0,0 @@ -/** NOTE: This file is a current WIP */ -public class RangeQueryPointUpdateSegmentTree { - - // Tree values - private int[] t; - - private int n; - - public RangeQueryPointUpdateSegmentTree(int[] values) { - if (values == null) { - throw new NullPointerException("Segment tree values cannot be null."); - } - n = values.length; - t = new int[2 * n]; - - // buildTree(0, 0, n-1); - } - - // Builds tree bottom up starting with leaf nodes and combining - // values on callback. - // Range is inclusive: [l, r] - private int buildTree(int i, int l, int r, int[] values) { - if (l == r) { - return 0; - } - int leftChild = (i * 2); - int rightChild = (i * 2) + 1; - int mid = (l + r) / 2; - // TODO(will): herm... doesn't look quite righT? - // t[i] = buildTree(leftChild, l, mid, values) + buildTree(rightChild, mid, r, values); - - return 0; - } - - public int query(int l, int r) { - return 0; - } - - public void set(int i, int value) { - // update(i, 0, n-1, value); - } - - private void update(int at, int to, int l, int r, int value) { - if (l == r) { - return; - } else { - int lv = t[at * 2]; - int rv = t[at * 2 + 1]; - int m = (l + r) >>> 1; - if (l <= r) {} - } - } -} diff --git a/com/williamfiset/algorithms/datastructures/skiplist/SkipList.java b/com/williamfiset/algorithms/datastructures/skiplist/SkipList.java deleted file mode 100644 index 76c873eff..000000000 --- a/com/williamfiset/algorithms/datastructures/skiplist/SkipList.java +++ /dev/null @@ -1,165 +0,0 @@ -/** - * SkipList is a data structure that is useful for dealing with dynamic sorted data. In particular - * it gives O(log(n)) average complexity of insertion, removal, and find operations. This - * implementation has been augmented with a method for determining the rank of an element in the - * SkipList. Finding the rank of an element is also O(log(n)) on average. The complexities are - * average complexities since this algorithm is dependent on randomisation to achieve nice balanced - * properties. To make this efficient, instantiate the SkipList with a height equal to, or just - * greater than, log(n) where n is the number of elements that will be in the list. On average, this - * data structure uses O(n) space. Worst case space is O(nlog(n)), and worst case for all other - * operations is O(n) - * - * @author Finn Lidbetter - */ -package com.williamfiset.algorithms.datastructures.skiplist; - -import java.util.Random; - -class SkipList { - Random rand = new Random(0); - int height; - Node head; - Node tail; - // Initialise with the height of the SkipList and Keys smaller and larger - // than all other keys that will be inserted in the SkipList - public SkipList(int height, Key minKey, Key maxKey, int h) { - this.height = height; - head = new Node(minKey); - tail = new Node(maxKey); - Node currLeft = head; - Node currRight = tail; - for (int i = 0; i <= h; i++) { - currLeft.right = currRight; - currRight.left = currLeft; - currLeft.down = new Node(currLeft.k); - currLeft.down.up = currLeft; - currRight.down = new Node(currRight.k); - currRight.down.up = currRight; - currLeft.leftDist = 0; - currRight.leftDist = 1; - currLeft.height = height - i; - currRight.height = height - i; - currLeft = currLeft.down; - currRight = currRight.down; - } - currLeft.up.down = null; - currRight.up.down = null; - } - - public void insert(Node n2) { - int r = rand.nextInt(); - if (r < 0) r *= -1; - r %= (1 << (height - 1)); - int nodeHeight = Integer.numberOfLeadingZeros(r) - (32 - (height - 1)); - head.find(n2).insert(n2, null, nodeHeight, 1); - } - - public void remove(Node n2) { - head.find(n2).remove(n2); - } - - public int getRank(Node n2) { - Node curr = head.find(n2); - if (curr.compareTo(n2) != 0) return -1; - int distSum = 0; - while (curr.left != null) { - while (curr.up != null) { - curr = curr.up; - } - distSum += curr.leftDist; - curr = curr.left; - } - return distSum; - } - - class Node implements Comparable { - Node left; - Node right; - Node up; - Node down; - int height; - int leftDist; - Key k; - - public Node(Key kk) { - k = kk; - } - - public int compareTo(Node n2) { - return k.compareTo(n2.k); - } - - public Node find(Node f) { - if (f.compareTo(right) >= 0) { - return right.find(f); - } else if (down != null) { - return down.find(f); - } - return this; - } - - public void insert(Node n2, Node lower, int insertHeight, int distance) { - if (height <= insertHeight) { - n2.left = this; - n2.right = right; - n2.down = lower; - right.left = n2; - right = n2; - if (lower != null) lower.up = n2; - n2.height = height; - n2.leftDist = distance; - n2.right.leftDist -= n2.leftDist - 1; - Node curr = this; - while (curr.up == null) { - distance += curr.leftDist; - curr = curr.left; - } - curr = curr.up; - curr.insert(new Node(n2.k), n2, insertHeight, distance); - } else { - Node curr = this; - curr.right.leftDist++; - while (curr.left != null || curr.up != null) { - while (curr.up == null) { - curr = curr.left; - } - curr = curr.up; - curr.right.leftDist++; - } - } - } - - public void remove(Node n2) { - Node curr = this; - if (curr.compareTo(n2) != 0) return; - while (curr.up != null) { - curr.left.right = curr.right; - curr.right.left = curr.left; - curr.right.leftDist += curr.leftDist - 1; - curr = curr.up; - } - curr.left.right = curr.right; - curr.right.left = curr.left; - curr.right.leftDist += curr.leftDist - 1; - while (curr.left != null || curr.up != null) { - while (curr.up == null) { - curr = curr.left; - } - curr = curr.up; - curr.right.leftDist--; - } - } - } - - class Key implements Comparable { - int k; - - public Key(int kk) { - k = kk; - } - - public int compareTo(Key k2) { - return k - k2.k; - } - } -} diff --git a/com/williamfiset/algorithms/dp/CoinChange.java b/com/williamfiset/algorithms/dp/CoinChange.java deleted file mode 100644 index 1bbf8493e..000000000 --- a/com/williamfiset/algorithms/dp/CoinChange.java +++ /dev/null @@ -1,108 +0,0 @@ -/** - * The coin change problem is an unbounded knapsack problem variant. The problem asks you to find - * the minimum number of coins required for a certain amount of change given the coin denominations. - * You may use each coin denomination as many times as you please. - * - *

Tested against: https://leetcode.com/problems/coin-change/ - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.dp; - -public class CoinChange { - - private static final int INF = 987654321; - - public static int coinChange(int[] coins, int amount) { - - if (coins == null) throw new IllegalArgumentException("Coins array is null"); - if (coins.length == 0) throw new IllegalArgumentException("No coin values :/"); - - final int N = coins.length; - // Initialize table and set first row to be infinity - int[][] DP = new int[N + 1][amount + 1]; - java.util.Arrays.fill(DP[0], INF); - DP[1][0] = 0; - - // Iterate through all the coins - for (int i = 1; i <= N; i++) { - - int coinValue = coins[i - 1]; - for (int j = 1; j <= amount; j++) { - - // Consider not selecting this coin - DP[i][j] = DP[i - 1][j]; - - // Try selecting this coin if it's better - if (j - coinValue >= 0 && DP[i][j - coinValue] + 1 < DP[i][j]) - DP[i][j] = DP[i][j - coinValue] + 1; - } - } - - // The amount we wanted to make cannot be made :/ - if (DP[N][amount] == INF) return -1; - - // Return the minimum number of coins needed - return DP[N][amount]; - } - - public static int coinChangeSpaceEfficient(int[] coins, int amount) { - - if (coins == null) throw new IllegalArgumentException("Coins array is null"); - - // Initialize table and set everything to infinity except first cell - int[] DP = new int[amount + 1]; - java.util.Arrays.fill(DP, INF); - DP[0] = 0; - - for (int i = 1; i <= amount; i++) - for (int coinValue : coins) - if (i - coinValue >= 0 && DP[i - coinValue] + 1 < DP[i]) DP[i] = DP[i - coinValue] + 1; - - // The amount we wanted to make cannot be made :/ - if (DP[amount] == INF) return -1; - - // Return the minimum number of coins needed - return DP[amount]; - } - - // The recursive approach has the advantage that it does not have to visit - // all possible states like the tabular approach does. This can speedup - // things especially if the coin denominations are large. - public static int coinChangeRecursive(int[] coins, int amount) { - - if (coins == null) throw new IllegalArgumentException("Coins array is null"); - if (amount < 0) return -1; - - int[] DP = new int[amount + 1]; - return coinChangeRecursive(amount, coins, DP); - } - - // Private helper method to actually go the recursion - private static int coinChangeRecursive(int amount, int[] coins, int[] DP) { - - // Base cases. - if (amount < 0) return -1; - if (amount == 0) return 0; - if (DP[amount] != 0) return DP[amount]; - - int minCoins = INF; - for (int coinValue : coins) { - - int newAmount = amount - coinValue; - int value = coinChangeRecursive(newAmount, coins, DP); - if (value != -1 && value < minCoins) minCoins = value + 1; - } - - // If we weren't able to find some coins to make our - // amount then cache -1 as the answer. - return DP[amount] = (minCoins == INF) ? -1 : minCoins; - } - - public static void main(String[] args) { - int[] coins = {2, 6, 1}; - System.out.println(coinChange(coins, 17)); - System.out.println(coinChangeSpaceEfficient(coins, 17)); - System.out.println(coinChangeRecursive(coins, 17)); - } -} diff --git a/com/williamfiset/algorithms/dp/examples/NarrowArtGalleryRecursive.java b/com/williamfiset/algorithms/dp/examples/NarrowArtGalleryRecursive.java deleted file mode 100644 index 1f4f7f388..000000000 --- a/com/williamfiset/algorithms/dp/examples/NarrowArtGalleryRecursive.java +++ /dev/null @@ -1,82 +0,0 @@ -import java.util.Scanner; - -public class NarrowArtGalleryRecursive { - - static final int INF = 10000; - - static int N, K, sum; - static int[][] gallery; - static Integer[][][] dp; - - static final int LEFT = 0; - static final int RIGHT = 1; - static final int NEITHER = 2; - - static int min(int... values) { - int m = Integer.MAX_VALUE; - for (int v : values) if (v < m) m = v; - return m; - } - - static int f(int k, int n) { - return sum - min(f(k, n, LEFT), f(k, n, RIGHT), f(k, n, NEITHER)); - } - - // k = num rooms to close - // n = the row index - // s = the side, either LEFT, RIGHT or NEITHER. - static int f(int k, int n, int s) { - if (n < 0) return INF; - if (k <= 0) return 0; - // if (s == NEITHER) return min(f(k-1, n-1, LEFT), f(k-1, n-1, RIGHT)); - - if (dp[k][n][s] != null) return dp[k][n][s]; - - dp[k][n][LEFT] = - min( - // f(k, n, NEITHER) + g(n-1, LEFT), - f(k - 1, n - 1, LEFT) + g(n - 2, LEFT), f(k, n - 1, LEFT)); - - dp[k][n][RIGHT] = - min( - // f(k, n, NEITHER) + g(n-1, RIGHT), - f(k - 1, n - 1, RIGHT) + g(n - 2, RIGHT), f(k, n - 1, RIGHT)); - dp[k][n][NEITHER] = - min(f(k - 1, n - 1, LEFT), f(k - 1, n - 1, RIGHT), f(k - 1, n - 1, NEITHER)); - - switch (s) { - case LEFT: - return dp[k][n][LEFT]; - case RIGHT: - return dp[k][n][RIGHT]; - default: - return dp[k][n][NEITHER]; - } - } - - static int g(int n, int s) { - if (n >= N) return INF; - return gallery[n][s]; - } - - public static void main(String[] Fiset) { - Scanner sc = new Scanner(System.in); - while (true) { - N = sc.nextInt(); - K = sc.nextInt(); - - if (N == 0 && K == 0) break; - - gallery = new int[N][2]; - dp = new Integer[K][N][3]; - - for (int i = 0; i < N; i++) { - gallery[i][LEFT] = sc.nextInt(); - gallery[i][RIGHT] = sc.nextInt(); - sum += gallery[i][LEFT] + gallery[i][RIGHT]; - } - - System.out.println(f(K, N)); - } - } -} diff --git a/com/williamfiset/algorithms/dp/examples/narrowartgallery.zip b/com/williamfiset/algorithms/dp/examples/narrowartgallery.zip deleted file mode 100644 index 10c927710..000000000 Binary files a/com/williamfiset/algorithms/dp/examples/narrowartgallery.zip and /dev/null differ diff --git a/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismHash.java b/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismHash.java deleted file mode 100644 index 682dca248..000000000 --- a/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismHash.java +++ /dev/null @@ -1,98 +0,0 @@ -/** Determines if two rooted trees are isomorphic with high probability. */ - -package com.williamfiset.algorithms.graphtheory.treealgorithms; - -import java.util.*; - -public class TreeIsomorphismHash { - public static class TreeNode { - private int id; - private List children; - - public TreeNode(int id) { - this.id = id; - children = new LinkedList<>(); - } - - public void addChildren(TreeNode... nodes) { - for (TreeNode node : nodes) { - children.add(node); - } - } - - public boolean isLeaf() { - return children.size() == 0; - } - - public List children() { - return children; - } - - @Override - public String toString() { - return String.valueOf(id); - } - } - - private static int MOD = 1_000_000_007; - private static int PRIME = 131; - private static int LEAF_VALUE = 37; - - public static boolean areIsomorphic(TreeNode root1, TreeNode root2) { - return treeHash(root1) == treeHash(root2); - } - - private static int treeHash(TreeNode node) { - if (node == null || node.isLeaf()) { - return LEAF_VALUE; - } - List hashes = new LinkedList<>(); - for (TreeNode child : node.children()) { - hashes.add(treeHash(child)); - } - int hash = 1; - Collections.sort(hashes); - for (int h : hashes) { - hash = ((PRIME * hash) ^ h) % MOD; - } - return hash; - } - - public static void main(String[] args) { - TreeNode root1 = new TreeNode(0); - TreeNode n1 = new TreeNode(1); - TreeNode n2 = new TreeNode(2); - TreeNode n3 = new TreeNode(3); - TreeNode n4 = new TreeNode(4); - TreeNode n5 = new TreeNode(5); - TreeNode n6 = new TreeNode(6); - TreeNode n7 = new TreeNode(7); - TreeNode n8 = new TreeNode(8); - TreeNode n9 = new TreeNode(9); - TreeNode n10 = new TreeNode(10); - root1.addChildren(n1, n4, n8); - n1.addChildren(n2, n3); - n4.addChildren(n5, n6, n7); - n6.addChildren(n10); - n8.addChildren(n9); - - TreeNode root2 = new TreeNode(0); - TreeNode x1 = new TreeNode(1); - TreeNode x2 = new TreeNode(2); - TreeNode x3 = new TreeNode(3); - TreeNode x4 = new TreeNode(4); - TreeNode x5 = new TreeNode(5); - TreeNode x6 = new TreeNode(6); - TreeNode x7 = new TreeNode(7); - TreeNode x8 = new TreeNode(8); - TreeNode x9 = new TreeNode(9); - TreeNode x10 = new TreeNode(10); - root2.addChildren(x4, x8, x1); - x4.addChildren(x6, x5, x7); - x7.addChildren(x10); - x8.addChildren(x9); - x1.addChildren(x3, x2); - - System.out.println(areIsomorphic(root1, root2)); - } -} diff --git a/com/williamfiset/algorithms/linearalgebra/MatrixDeterminantLaplaceExpansion.java b/com/williamfiset/algorithms/linearalgebra/MatrixDeterminantLaplaceExpansion.java deleted file mode 100644 index fef8a8505..000000000 --- a/com/williamfiset/algorithms/linearalgebra/MatrixDeterminantLaplaceExpansion.java +++ /dev/null @@ -1,144 +0,0 @@ -/** - * This is an implementation of finding the determinant of an nxn matrix using Laplace/cofactor - * expansion. Although this method is mathematically beautiful it is computationally intensive and - * not practical for matrices beyond the size of 7-8. - * - *

Time Complexity: ~O((n+2)!) - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.linearalgebra; - -public class MatrixDeterminantLaplaceExpansion { - - // Define a small value of epsilon to compare double values - static final double EPS = 0.00000001; - - public static void main(String[] args) { - - double[][] m = {{6}}; - System.out.println(determinant(m)); // 6 - - m = - new double[][] { - {1, 2}, - {3, 4} - }; - System.out.println(determinant(m)); // -2 - - m = - new double[][] { - {1, -2, 3}, - {4, -5, 6}, - {7, -8, 10} - }; - System.out.println(determinant(m)); // 3 - - m = - new double[][] { - {1, -2, 3, 7}, - {4, -5, 6, 2}, - {7, -8, 10, 3}, - {-8, 10, 3, 2} - }; - System.out.println(determinant(m)); // -252 - - m = - new double[][] { - {1, -2, 3, 7}, - {4, -5, 6, 2}, - {7, -8, 10, 3}, - {-8, 10, 3, 2} - }; - System.out.println(determinant(m)); // -252 - - m = - new double[][] { - {1, -2, 3, 7, 12}, - {4, -5, 6, 2, 4}, - {7, -8, 10, 3, 1}, - {-8, 10, 8, 3, 2}, - {5, 5, 5, 5, 5} - }; - System.out.println(determinant(m)); // -27435 - - System.out.println(); - - for (int n = 1; ; n++) { - m = new double[n][n]; - for (int i = 0; i < n; i++) - for (int j = 0; j < n; j++) m[i][j] = Math.floor(Math.random() * 10); - System.out.printf("Found determinant of %dx%d matrix to be: %.4f\n", n, n, determinant(m)); - } - } - - // Given an n*n matrix this method finds the determinant - // using Laplace/cofactor expansion. - // Time Complexity: ~O((n+2)!) - public static double determinant(double[][] matrix) { - - final int n = matrix.length; - - // Use closed form for 1x1 determinant - if (n == 1) return matrix[0][0]; - - // Use closed form for 2x2 determinant - if (n == 2) return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]; - - // For 3x3 matrices and up use Laplace/cofactor expansion - return laplace(matrix); - } - - // This method uses cofactor expansion to compute the determinant - // of a matrix. Unfortunately, this method is very slow and uses - // A LOT of memory hence it is not too practical for large matrices. - private static double laplace(double[][] m) { - - final int n = m.length; - - // Base case is 3x3 determinant - if (n == 3) { - double a = m[0][0], b = m[0][1], c = m[0][2]; - double d = m[1][0], e = m[1][1], f = m[1][2]; - double g = m[2][0], h = m[2][1], i = m[2][2]; - return a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g); - } - - int det = 0; - - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - - double c = m[i][j]; - if (Math.abs(c) > EPS) { - double[][] newMatrix = constructMatrix(m, j); - double parity = ((j & 1) == 0) ? +1 : -1; - det += parity * c * laplace(newMatrix); - } - } - } - - return det; - } - - // Constructs a matrix one dimension smaller than the last by - // excluding always the top row and some selected column. This - // method uses a lot of space we called recursively multiple times. - private static double[][] constructMatrix(double[][] m, int skipColumn) { - - int n = m.length; - double[][] newMatrix = new double[n - 1][n - 1]; - - int ii = 0; - for (int i = 1; i < n; i++, ii++) { - int jj = 0; - for (int j = 0; j < n; j++) { - if (j == skipColumn) continue; - double v = m[i][j]; - newMatrix[ii][jj++] = v; - } - } - - return newMatrix; - } -} diff --git a/com/williamfiset/algorithms/sorting/BubbleSort.java b/com/williamfiset/algorithms/sorting/BubbleSort.java deleted file mode 100644 index 8278e2857..000000000 --- a/com/williamfiset/algorithms/sorting/BubbleSort.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Bubble sort implementation - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.sorting; - -import java.util.Random; - -public class BubbleSort { - - // Sort the array using bubble sort. The idea behind - // bubble sort is to look for adjacent indexes which - // are out of place and interchange their elements - // until the entire array is sorted. - public static void bubbleSort(int[] ar) { - if (ar == null) return; - - final int N = ar.length; - boolean sorted; - - do { - - sorted = true; - - for (int i = 1; i < N; i++) { - if (ar[i] < ar[i - 1]) { - swap(ar, i - 1, i); - sorted = false; - } - } - - } while (!sorted); - } - - private static void swap(int[] ar, int i, int j) { - int tmp = ar[i]; - ar[i] = ar[j]; - ar[j] = tmp; - } - - public static void main(String[] args) { - - int[] array = {10, 4, 6, 8, 13, 2, 3}; - bubbleSort(array); - System.out.println(java.util.Arrays.toString(array)); - - // TODO(williamfiset): move to javatests/... - runTests(); - } - - static Random RANDOM = new Random(); - - public static void runTests() { - final int NUM_TESTS = 1000; - for (int i = 1; i <= NUM_TESTS; i++) { - - int[] array = new int[i]; - for (int j = 0; j < i; j++) array[j] = randInt(-1000000, +1000000); - int[] arrayCopy = array.clone(); - - bubbleSort(array); - java.util.Arrays.sort(arrayCopy); - - if (!java.util.Arrays.equals(array, arrayCopy)) System.out.println("ERROR"); - } - } - - static int randInt(int min, int max) { - return RANDOM.nextInt((max - min) + 1) + min; - } -} diff --git a/com/williamfiset/algorithms/sorting/BucketSort.java b/com/williamfiset/algorithms/sorting/BucketSort.java deleted file mode 100644 index 139801806..000000000 --- a/com/williamfiset/algorithms/sorting/BucketSort.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Bucket sort implementation - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.sorting; - -import java.util.*; - -public class BucketSort { - - // Performs a bucket sort of an array in which all the elements are - // bounded in the range [minVal, maxVal]. For bucket sort to give linear - // performance the elements need to be uniformly distributed - public static void bucketSort(int[] ar, final int minVal, final int maxVal) { - - if (ar == null || ar.length == 0 || minVal == maxVal) return; - - // N is number elements and M is the range of values - final int N = ar.length, M = maxVal - minVal, NUM_BUCKETS = M / N + 1; - List> buckets = new ArrayList<>(NUM_BUCKETS); - for (int i = 0; i < NUM_BUCKETS; i++) buckets.add(new ArrayList<>()); - - // Place each element in a bucket - for (int i = 0; i < N; i++) { - int bi = (ar[i] - minVal) / M; - List bucket = buckets.get(bi); - bucket.add(ar[i]); - } - - // Sort buckets and stitch together answer - for (int bi = 0, j = 0; bi < NUM_BUCKETS; bi++) { - List bucket = buckets.get(bi); - if (bucket != null) { - Collections.sort(bucket); - for (int k = 0; k < bucket.size(); k++) { - ar[j++] = bucket.get(k); - } - } - } - } - - public static void main(String[] args) { - - int[] array = {10, 4, 6, 8, 13, 2, 3}; - bucketSort(array, 2, 13); - System.out.println(java.util.Arrays.toString(array)); - - array = new int[] {10, 10, 10, 10, 10}; - bucketSort(array, 10, 10); - System.out.println(java.util.Arrays.toString(array)); - - // TODO(williamfiset): move to javatests/... - runTests(); - } - - static Random RANDOM = new Random(); - - public static void runTests() { - final int NUM_TESTS = 1000; - for (int i = 1; i <= NUM_TESTS; i++) { - - int[] array = new int[i]; - int maxVal = Integer.MIN_VALUE, minVal = Integer.MAX_VALUE; - for (int j = 0; j < i; j++) { - array[j] = randInt(-1000000, +1000000); - maxVal = Math.max(maxVal, array[j]); - minVal = Math.min(minVal, array[j]); - } - int[] arrayCopy = array.clone(); - - bucketSort(array, minVal, maxVal); - Arrays.sort(arrayCopy); - - if (!Arrays.equals(array, arrayCopy)) System.out.println("ERROR"); - } - } - - static int randInt(int min, int max) { - return RANDOM.nextInt((max - min) + 1) + min; - } -} diff --git a/com/williamfiset/algorithms/sorting/CountingSort.java b/com/williamfiset/algorithms/sorting/CountingSort.java deleted file mode 100644 index b865184f6..000000000 --- a/com/williamfiset/algorithms/sorting/CountingSort.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * An implementation of counting sort! - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.sorting; - -public class CountingSort { - - // Sorts values in the range of [minVal, maxVal] in O(n+maxVal-maxVal) - public static void countingSort(int[] ar, int minVal, int maxVal) { - int sz = maxVal - minVal + 1; - int[] B = new int[sz]; - for (int i = 0; i < ar.length; i++) B[ar[i] - minVal]++; - for (int i = 0, k = 0; i < sz; i++) while (B[i]-- > 0) ar[k++] = i + minVal; - } - - public static void main(String[] args) { - - // The maximum and minimum values on the numbers we are sorting. - // You need to know ahead of time the upper and lower bounds on - // the numbers you are sorting for counting sort to work. - final int MIN_VAL = -10; - final int MAX_VAL = +10; - - int[] nums = {+4, -10, +0, +6, +1, -5, -5, +1, +1, -2, 0, +6, +8, -7, +10}; - countingSort(nums, MIN_VAL, MAX_VAL); - - // prints [-10, -7, -5, -5, -2, 0, 0, 1, 1, 1, 4, 6, 6, 8, 10] - System.out.println(java.util.Arrays.toString(nums)); - } -} diff --git a/com/williamfiset/algorithms/sorting/InsertionSort.java b/com/williamfiset/algorithms/sorting/InsertionSort.java deleted file mode 100644 index 2881772da..000000000 --- a/com/williamfiset/algorithms/sorting/InsertionSort.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Insertion sort implementation - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.sorting; - -import java.util.Random; - -public class InsertionSort { - - // Sort the given array using insertion sort. The idea behind - // insertion sort is that at the array is already sorted from - // [0, i] and you want to add the element at position i+1, so - // you 'insert' it at the appropriate location. - public static void insertionSort(int[] ar) { - - if (ar == null) return; - - final int N = ar.length; - - for (int i = 1; i < N; i++) for (int j = i; j > 0 && ar[j] < ar[j - 1]; j--) swap(ar, j - 1, j); - } - - private static void swap(int[] ar, int i, int j) { - int tmp = ar[i]; - ar[i] = ar[j]; - ar[j] = tmp; - } - - public static void main(String[] args) { - - int[] array = {10, 4, 6, 8, 13, 2, 3}; - insertionSort(array); - System.out.println(java.util.Arrays.toString(array)); - - // TODO(williamfiset): move to javatests/... - runTests(); - } - - static Random RANDOM = new Random(); - - public static void runTests() { - final int NUM_TESTS = 1000; - for (int i = 1; i <= NUM_TESTS; i++) { - - int[] array = new int[i]; - for (int j = 0; j < i; j++) array[j] = randInt(-1000000, +1000000); - int[] arrayCopy = array.clone(); - - insertionSort(array); - java.util.Arrays.sort(arrayCopy); - - if (!java.util.Arrays.equals(array, arrayCopy)) System.out.println("ERROR"); - } - } - - static int randInt(int min, int max) { - return RANDOM.nextInt((max - min) + 1) + min; - } -} diff --git a/com/williamfiset/algorithms/sorting/RadixSort.java b/com/williamfiset/algorithms/sorting/RadixSort.java deleted file mode 100644 index 0095eb4dc..000000000 --- a/com/williamfiset/algorithms/sorting/RadixSort.java +++ /dev/null @@ -1,8 +0,0 @@ -/** NOTE: This file is still under development */ -package com.williamfiset.algorithms.sorting; - -public class RadixSort { - public static void radixSort(int[] ar) { - // TODO: https://github.com/williamfiset/Algorithms/issues/5 - } -} diff --git a/com/williamfiset/algorithms/sorting/SelectionSort.java b/com/williamfiset/algorithms/sorting/SelectionSort.java deleted file mode 100644 index 33577b2fd..000000000 --- a/com/williamfiset/algorithms/sorting/SelectionSort.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Selection sort implementation - * - * @author William Fiset, william.alexandre.fiset@gmail.com - */ -package com.williamfiset.algorithms.sorting; - -import java.util.Random; - -public class SelectionSort { - - public static void selectionSort(int[] array) { - if (array == null) return; - final int N = array.length; - - for (int i = 0; i < N; i++) { - - // Find the index beyond i with a lower value than i - int swapIndex = i; - for (int j = i + 1; j < N; j++) if (array[j] < array[swapIndex]) swapIndex = j; - - swap(array, i, swapIndex); - } - } - - private static void swap(int[] ar, int i, int j) { - int tmp = ar[i]; - ar[i] = ar[j]; - ar[j] = tmp; - } - - public static void main(String[] args) { - - int[] array = {10, 4, 6, 8, 13, 2, 3}; - selectionSort(array); - System.out.println(java.util.Arrays.toString(array)); - - runTests(); - } - - // TODO(williamfiset): move this to a test file. - - static Random RANDOM = new Random(); - - public static void runTests() { - final int NUM_TESTS = 1000; - for (int i = 1; i <= NUM_TESTS; i++) { - - int[] array = new int[i]; - for (int j = 0; j < i; j++) array[j] = randInt(-1000000, +1000000); - int[] arrayCopy = array.clone(); - - selectionSort(array); - java.util.Arrays.sort(arrayCopy); - - if (!java.util.Arrays.equals(array, arrayCopy)) System.out.println("ERROR"); - } - } - - static int randInt(int min, int max) { - return RANDOM.nextInt((max - min) + 1) + min; - } -} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..a69886cba --- /dev/null +++ b/gradle.properties @@ -0,0 +1,5 @@ +# So 'gradle run' does not produce additional output +org.gradle.logging.level=warn + +# So warnings are not produce by default (override with --warning-mode all) +org.gradle.warning.mode=none \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..f3d88b1c2 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..e9795fa9d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Aug 14 11:49:58 CEST 2022 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..2fe81a7d9 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..24467a141 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/javatests/com/williamfiset/algorithms/datastructures/balancedtree/RedBlackTreeTest.java b/javatests/com/williamfiset/algorithms/datastructures/balancedtree/RedBlackTreeTest.java deleted file mode 100644 index a9389007f..000000000 --- a/javatests/com/williamfiset/algorithms/datastructures/balancedtree/RedBlackTreeTest.java +++ /dev/null @@ -1,341 +0,0 @@ -package javatests.com.williamfiset.algorithms.datastructures.balancedtree; - -import static org.junit.Assert.*; - -import com.williamfiset.algorithms.datastructures.balancedtree.RedBlackTree; -import java.util.*; -import org.junit.Before; -import org.junit.Test; - -public class RedBlackTreeTest { - - static final int MAX_RAND_NUM = +100000; - static final int MIN_RAND_NUM = -100000; - - static final int TEST_SZ = 9000; - - private RedBlackTree tree; - - @Before - public void setup() { - tree = new RedBlackTree<>(); - } - - @Test(expected = IllegalArgumentException.class) - public void testNullInsertion() { - tree.insert(null); - } - - @Test - public void testTreeContainsNull() { - assertFalse(tree.contains(null)); - } - - @Test - public void testLeftLeftRotation() { - - tree.insert(3); - tree.insert(2); - tree.insert(1); - - assertEquals(2, tree.root.value.intValue()); - assertEquals(1, tree.root.left.value.intValue()); - assertEquals(3, tree.root.right.value.intValue()); - - assertEquals(RedBlackTree.BLACK, tree.root.color); - assertEquals(RedBlackTree.RED, tree.root.left.color); - assertEquals(RedBlackTree.RED, tree.root.right.color); - - assertEquals(tree.root, tree.root.left.parent); - assertEquals(tree.root, tree.root.right.parent); - - assertNullChildren(tree.root.left, tree.root.right); - assertCorrectParentLinks(tree.root, null); - } - - @Test - public void testLeftRightRotation() { - - tree.insert(3); - tree.insert(1); - tree.insert(2); - - assertEquals(2, tree.root.value.intValue()); - assertEquals(1, tree.root.left.value.intValue()); - assertEquals(3, tree.root.right.value.intValue()); - - assertEquals(RedBlackTree.BLACK, tree.root.color); - assertEquals(RedBlackTree.RED, tree.root.left.color); - assertEquals(RedBlackTree.RED, tree.root.right.color); - - assertEquals(tree.root, tree.root.left.parent); - assertEquals(tree.root, tree.root.right.parent); - - assertNullChildren(tree.root.left, tree.root.right); - assertCorrectParentLinks(tree.root, null); - } - - @Test - public void testRightLeftRotation() { - - tree.insert(1); - tree.insert(3); - tree.insert(2); - - assertEquals(2, tree.root.value.intValue()); - assertEquals(1, tree.root.left.value.intValue()); - assertEquals(3, tree.root.right.value.intValue()); - - assertEquals(RedBlackTree.BLACK, tree.root.color); - assertEquals(RedBlackTree.RED, tree.root.left.color); - assertEquals(RedBlackTree.RED, tree.root.right.color); - - assertEquals(tree.root, tree.root.left.parent); - assertEquals(tree.root, tree.root.right.parent); - - assertNullChildren(tree.root.left, tree.root.right); - assertCorrectParentLinks(tree.root, null); - } - - @Test - public void testRightRightRotation() { - - tree.insert(1); - tree.insert(2); - tree.insert(3); - - assertEquals(2, tree.root.value.intValue()); - assertEquals(1, tree.root.left.value.intValue()); - assertEquals(3, tree.root.right.value.intValue()); - - assertEquals(RedBlackTree.BLACK, tree.root.color); - assertEquals(RedBlackTree.RED, tree.root.left.color); - assertEquals(RedBlackTree.RED, tree.root.right.color); - - assertEquals(tree.root, tree.root.left.parent); - assertEquals(tree.root, tree.root.right.parent); - - assertNullChildren(tree.root.left, tree.root.right); - assertCorrectParentLinks(tree.root, null); - } - - @Test - public void testLeftUncleCase() { - - /* Red left uncle case. */ - - tree.insert(1); - tree.insert(2); - tree.insert(3); - tree.insert(4); - - assertEquals(2, tree.root.value.intValue()); - assertEquals(1, tree.root.left.value.intValue()); - assertEquals(3, tree.root.right.value.intValue()); - assertEquals(4, tree.root.right.right.value.intValue()); - - assertEquals(RedBlackTree.BLACK, tree.root.color); - assertEquals(RedBlackTree.BLACK, tree.root.left.color); - assertEquals(RedBlackTree.BLACK, tree.root.right.color); - assertEquals(RedBlackTree.RED, tree.root.right.right.color); - - assertNull(tree.root.right.left); - assertNullChildren(tree.root.left, tree.root.right.right); - assertCorrectParentLinks(tree.root, null); - - /* Black left uncle case. */ - - tree.insert(5); - - assertEquals(2, tree.root.value.intValue()); - assertEquals(1, tree.root.left.value.intValue()); - assertEquals(4, tree.root.right.value.intValue()); - assertEquals(3, tree.root.right.left.value.intValue()); - assertEquals(5, tree.root.right.right.value.intValue()); - - assertEquals(RedBlackTree.BLACK, tree.root.color); - assertEquals(RedBlackTree.BLACK, tree.root.left.color); - assertEquals(RedBlackTree.BLACK, tree.root.right.color); - assertEquals(RedBlackTree.RED, tree.root.right.left.color); - assertEquals(RedBlackTree.RED, tree.root.right.right.color); - assertCorrectParentLinks(tree.root, null); - } - - @Test - public void testRightUncleCase() { - - /* Red right uncle case. */ - - tree.insert(2); - tree.insert(3); - tree.insert(4); - tree.insert(1); - - assertEquals(3, tree.root.value.intValue()); - assertEquals(2, tree.root.left.value.intValue()); - assertEquals(4, tree.root.right.value.intValue()); - assertEquals(1, tree.root.left.left.value.intValue()); - - assertEquals(RedBlackTree.BLACK, tree.root.color); - assertEquals(RedBlackTree.BLACK, tree.root.left.color); - assertEquals(RedBlackTree.BLACK, tree.root.right.color); - assertEquals(RedBlackTree.RED, tree.root.left.left.color); - - assertNull(tree.root.left.right); - assertNullChildren(tree.root.right, tree.root.left.left); - assertCorrectParentLinks(tree.root, null); - - /* Black right uncle case. */ - - tree.insert(0); - - assertEquals(3, tree.root.value.intValue()); - assertEquals(1, tree.root.left.value.intValue()); - assertEquals(4, tree.root.right.value.intValue()); - assertEquals(0, tree.root.left.left.value.intValue()); - assertEquals(2, tree.root.left.right.value.intValue()); - - assertEquals(RedBlackTree.BLACK, tree.root.color); - assertEquals(RedBlackTree.BLACK, tree.root.left.color); - assertEquals(RedBlackTree.BLACK, tree.root.right.color); - assertEquals(RedBlackTree.RED, tree.root.left.left.color); - assertEquals(RedBlackTree.RED, tree.root.left.right.color); - assertCorrectParentLinks(tree.root, null); - } - - @Test - public void interestingCase1() { - - int[] values = {41, 44, 95, 83, 72, 66, 94, 90, 59}; - for (int v : values) tree.insert(v); - - assertEquals(44, tree.root.value.intValue()); - - assertEquals(41, tree.root.left.value.intValue()); - assertEquals(83, tree.root.right.value.intValue()); - - assertEquals(66, tree.root.right.left.value.intValue()); - assertEquals(94, tree.root.right.right.value.intValue()); - - assertEquals(59, tree.root.right.left.left.value.intValue()); - assertEquals(72, tree.root.right.left.right.value.intValue()); - assertEquals(90, tree.root.right.right.left.value.intValue()); - assertEquals(95, tree.root.right.right.right.value.intValue()); - - assertEquals(RedBlackTree.BLACK, tree.root.color); - assertEquals(RedBlackTree.BLACK, tree.root.left.color); - assertEquals(RedBlackTree.RED, tree.root.right.color); - assertEquals(RedBlackTree.BLACK, tree.root.right.left.color); - assertEquals(RedBlackTree.BLACK, tree.root.right.right.color); - assertEquals(RedBlackTree.RED, tree.root.right.left.left.color); - assertEquals(RedBlackTree.RED, tree.root.right.left.right.color); - assertEquals(RedBlackTree.RED, tree.root.right.right.left.color); - assertEquals(RedBlackTree.RED, tree.root.right.right.right.color); - } - - @Test - public void testRandomizedValueInsertionsAgainstTreeSet() { - - TreeSet set = new TreeSet<>(); - for (int i = 0; i < TEST_SZ; i++) { - int v = randValue(); - assertEquals(set.add(v), tree.insert(v)); - assertEquals(set.size(), tree.size()); - assertTrue(tree.contains(v)); - assertBinarySearchTreeInvariant(tree.root); - // validateRedBlackTreeInvariant - } - } - - /* - @Test - public void randomRemoveTests() { - TreeSet ts = new TreeSet<>(); - for (int i = 0; i < TEST_SZ; i++) { - - int size = i; - List lst = genRandList(size); - for (Integer value : lst) { - tree.insert(value); - ts.add(value); - } - Collections.shuffle(lst); - - // Remove all the elements we just placed in the tree. - for (int j = 0; j < size; j++) { - - Integer value = lst.get(j); - assertEquals(ts.remove(value), tree.remove(value)); - assertFalse(tree.contains(value)); - assertEquals(size - j - 1, tree.size()); - } - assertTrue(tree.isEmpty()); - } - } - - @Test - public void testTreeHeight() { - for (int n = 1; n <= TEST_SZ; n++) { - - tree.insert(randValue()); - int height = tree.height(); - - // Get an upper bound on what the maximum height of - // an AVL tree should be. Values were taken from: - // https://en.wikipedia.org/wiki/AVL_tree#Comparison_to_other_structures - double c = 1.441; - double b = -0.329; - double upperBound = c*(Math.log(n+2.0)/Math.log(2)) + b; - - assertTrue(height < upperBound); - - } - } - */ - - static void assertNullChildren(RedBlackTree.Node... nodes) { - for (RedBlackTree.Node node : nodes) { - assertNull(node.left); - assertNull(node.right); - } - } - - static void assertCorrectParentLinks(RedBlackTree.Node node, RedBlackTree.Node parent) { - if (node == null) return; - assertEquals(node.parent, parent); - assertCorrectParentLinks(node.left, node); - assertCorrectParentLinks(node.right, node); - } - - // Make sure all left child nodes are smaller in value than their parent and - // make sure all right child nodes are greater in value than their parent. - // (Used only for testing) - boolean assertBinarySearchTreeInvariant(RedBlackTree.Node node) { - if (node == null) return true; - boolean isValid = true; - if (node.left != null) isValid = isValid && node.left.value.compareTo(node.value) < 0; - if (node.right != null) isValid = isValid && node.right.value.compareTo(node.value) > 0; - return isValid - && assertBinarySearchTreeInvariant(node.left) - && assertBinarySearchTreeInvariant(node.right); - } - - // Used for testing. - boolean validateParentLinksAreCorrect(RedBlackTree.Node node, RedBlackTree.Node parent) { - if (node == null) return true; - if (node.parent != parent) return false; - return validateParentLinksAreCorrect(node.left, node) - && validateParentLinksAreCorrect(node.right, node); - } - - static List genRandList(int sz) { - List lst = new ArrayList<>(sz); - for (int i = 0; i < sz; i++) lst.add(i); // unique values. - Collections.shuffle(lst); - return lst; - } - - public static int randValue() { - return (int) (Math.random() * MAX_RAND_NUM * 2) + MIN_RAND_NUM; - } -} diff --git a/javatests/com/williamfiset/algorithms/datastructures/dynamicarray/DynamicArrayTest.java b/javatests/com/williamfiset/algorithms/datastructures/dynamicarray/DynamicArrayTest.java deleted file mode 100644 index 7fb397a10..000000000 --- a/javatests/com/williamfiset/algorithms/datastructures/dynamicarray/DynamicArrayTest.java +++ /dev/null @@ -1,182 +0,0 @@ -package javatests.com.williamfiset.algorithms.datastructures.dynamicarray; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import com.williamfiset.algorithms.datastructures.dynamicarray.DynamicArray; -import org.junit.Test; - -public class DynamicArrayTest { - - @Test - public void testEmptyList() { - DynamicArray list = new DynamicArray<>(); - assertTrue(list.isEmpty()); - } - - @Test(expected = Exception.class) - public void testRemovingEmpty() { - DynamicArray list = new DynamicArray<>(); - list.removeAt(0); - } - - @Test(expected = Exception.class) - public void testIndexOutOfBounds() { - DynamicArray list = new DynamicArray<>(); - list.add(-56); - list.add(-53); - list.add(-55); - list.removeAt(3); - } - - @Test(expected = Exception.class) - public void testIndexOutOfBounds2() { - DynamicArray list = new DynamicArray<>(); - for (int i = 0; i < 1000; i++) list.add(789); - list.removeAt(1000); - } - - @Test(expected = Exception.class) - public void testIndexOutOfBounds3() { - DynamicArray list = new DynamicArray<>(); - for (int i = 0; i < 1000; i++) list.add(789); - list.removeAt(-1); - } - - @Test(expected = Exception.class) - public void testIndexOutOfBounds4() { - DynamicArray list = new DynamicArray<>(); - for (int i = 0; i < 15; i++) list.add(123); - list.removeAt(-66); - } - - @Test - public void testRemoving() { - - DynamicArray list = new DynamicArray<>(); - String[] strs = {"a", "b", "c", "d", "e", null, "g", "h"}; - for (String s : strs) list.add(s); - - boolean ret = list.remove("c"); - assertTrue(ret); - - ret = list.remove("c"); - assertFalse(ret); - - ret = list.remove("h"); - assertTrue(ret); - - ret = list.remove(null); - assertTrue(ret); - - ret = list.remove("a"); - assertTrue(ret); - - ret = list.remove("a"); - assertFalse(ret); - - ret = list.remove("h"); - assertFalse(ret); - - ret = list.remove(null); - assertFalse(ret); - } - - @Test - public void testRemoving2() { - - DynamicArray list = new DynamicArray<>(); - String[] strs = {"a", "b", "c", "d"}; - for (String s : strs) list.add(s); - - assertTrue(list.remove("a")); - assertTrue(list.remove("b")); - assertTrue(list.remove("c")); - assertTrue(list.remove("d")); - - assertFalse(list.remove("a")); - assertFalse(list.remove("b")); - assertFalse(list.remove("c")); - assertFalse(list.remove("d")); - } - - @Test - public void testIndexOfNullElement() { - DynamicArray list = new DynamicArray<>(); - String[] strs = {"a", "b", null, "d"}; - for (String s : strs) list.add(s); - assertTrue(list.indexOf(null) == 2); - } - - @Test - public void testAddingElements() { - - DynamicArray list = new DynamicArray<>(); - - int[] elems = {1, 2, 3, 4, 5, 6, 7}; - - for (int i = 0; i < elems.length; i++) list.add(elems[i]); - - for (int i = 0; i < elems.length; i++) assertEquals(list.get(i).intValue(), elems[i]); - } - - @Test - public void testAddAndRemove() { - - DynamicArray list = new DynamicArray<>(0); - - for (int i = 0; i < 55; i++) list.add(44L); - for (int i = 0; i < 55; i++) list.remove(44L); - assertTrue(list.isEmpty()); - - for (int i = 0; i < 55; i++) list.add(44L); - for (int i = 0; i < 55; i++) list.removeAt(0); - assertTrue(list.isEmpty()); - - for (int i = 0; i < 155; i++) list.add(44L); - for (int i = 0; i < 155; i++) list.remove(44L); - assertTrue(list.isEmpty()); - - for (int i = 0; i < 155; i++) list.add(44L); - for (int i = 0; i < 155; i++) list.removeAt(0); - assertTrue(list.isEmpty()); - } - - @Test - public void testAddSetRemove() { - - DynamicArray list = new DynamicArray<>(0); - - for (int i = 0; i < 55; i++) list.add(44L); - for (int i = 0; i < 55; i++) list.set(i, 33L); - for (int i = 0; i < 55; i++) list.remove(33L); - assertTrue(list.isEmpty()); - - for (int i = 0; i < 55; i++) list.add(44L); - for (int i = 0; i < 55; i++) list.set(i, 33L); - for (int i = 0; i < 55; i++) list.removeAt(0); - assertTrue(list.isEmpty()); - - for (int i = 0; i < 155; i++) list.add(44L); - for (int i = 0; i < 155; i++) list.set(i, 33L); - for (int i = 0; i < 155; i++) list.remove(33L); - assertTrue(list.isEmpty()); - - for (int i = 0; i < 155; i++) list.add(44L); - for (int i = 0; i < 155; i++) list.removeAt(0); - assertTrue(list.isEmpty()); - } - - @Test - public void testSize() { - - DynamicArray list = new DynamicArray<>(); - - Integer[] elems = {-76, 45, 66, 3, null, 54, 33}; - for (int i = 0, sz = 1; i < elems.length; i++, sz++) { - list.add(elems[i]); - assertEquals(list.size(), sz); - } - } -} diff --git a/javatests/com/williamfiset/algorithms/datastructures/queue/IntQueueTest.java b/javatests/com/williamfiset/algorithms/datastructures/queue/IntQueueTest.java deleted file mode 100644 index eff4df881..000000000 --- a/javatests/com/williamfiset/algorithms/datastructures/queue/IntQueueTest.java +++ /dev/null @@ -1,141 +0,0 @@ -package javatests.com.williamfiset.algorithms.datastructures.queue; - -import static org.junit.Assert.*; - -import com.williamfiset.algorithms.datastructures.queue.IntQueue; -import java.util.*; -import org.junit.Before; -import org.junit.Test; - -public class IntQueueTest { - - @Before - public void setup() {} - - @Test - public void testEmptyQueue() { - IntQueue queue = new IntQueue(0); - assertTrue(queue.isEmpty()); - assertEquals(queue.size(), 0); - } - - // Doesn't apply to this implementation because of wrap - // @Test(expected=Exception.class) - // public void testPollOnEmpty() { - // IntQueue queue = new IntQueue(0); - // queue.dequeue(); - // } - - // Doesn't apply to this implementation because of wrap - // @Test(expected=Exception.class) - // public void testPeekOnEmpty() { - // IntQueue queue = new IntQueue(0); - // queue.peek(); - // } - - @Test - public void testEnqueueOneElement() { - IntQueue queue = new IntQueue(1); - queue.enqueue(77); - assertEquals(queue.size(), 1); - } - - @Test - public void testAll() { - int n = 5; - IntQueue queue = new IntQueue(10); - assertTrue(queue.isEmpty()); - for (int i = 1; i <= n; i++) { - queue.enqueue(i); - assertFalse(queue.isEmpty()); - } - for (int i = 1; i <= n; i++) { - assertEquals(i, queue.peek()); - assertEquals(i, queue.dequeue()); - assertEquals(queue.size(), n - i); - } - assertTrue(queue.isEmpty()); - n = 8; - for (int i = 1; i <= n; i++) { - queue.enqueue(i); - assertFalse(queue.isEmpty()); - } - for (int i = 1; i <= n; i++) { - assertEquals(i, queue.peek()); - assertEquals(i, queue.dequeue()); - assertEquals(queue.size(), n - i); - } - assertTrue(queue.isEmpty()); - n = 9; - for (int i = 1; i <= n; i++) { - queue.enqueue(i); - assertFalse(queue.isEmpty()); - } - for (int i = 1; i <= n; i++) { - assertEquals(i, queue.peek()); - assertEquals(i, queue.dequeue()); - assertEquals(queue.size(), n - i); - } - assertTrue(queue.isEmpty()); - n = 10; - for (int i = 1; i <= n; i++) { - queue.enqueue(i); - assertFalse(queue.isEmpty()); - } - for (int i = 1; i <= n; i++) { - assertEquals(i, queue.peek()); - assertEquals(i, queue.dequeue()); - assertEquals(queue.size(), n - i); - } - assertTrue(queue.isEmpty()); - } - - @Test - public void testPeekOneElement() { - IntQueue queue = new IntQueue(1); - queue.enqueue(77); - assertTrue(queue.peek() == 77); - assertEquals(queue.size(), 1); - } - - @Test - public void testDequeueOneElement() { - IntQueue queue = new IntQueue(1); - queue.enqueue(77); - assertTrue(queue.dequeue() == 77); - assertEquals(queue.size(), 0); - } - - @Test - public void testRandom() { - - for (int qSize = 1; qSize <= 50; qSize++) { - - IntQueue intQ = new IntQueue(qSize); - ArrayDeque javaQ = new ArrayDeque<>(qSize); - - assertEquals(javaQ.isEmpty(), intQ.isEmpty()); - assertEquals(javaQ.size(), intQ.size()); - - for (int operations = 0; operations < 5000; operations++) { - - double r = Math.random(); - - if (r < 0.60) { - int elem = (int) (1000 * Math.random()); - if (javaQ.size() < qSize) { - javaQ.offer(elem); - intQ.enqueue(elem); - } - } else { - if (!javaQ.isEmpty()) { - assertEquals((int) javaQ.poll(), (int) intQ.dequeue()); - } - } - - assertEquals(javaQ.isEmpty(), intQ.isEmpty()); - assertEquals(javaQ.size(), intQ.size()); - } - } - } -} diff --git a/javatests/com/williamfiset/algorithms/datastructures/queue/QueueTest.java b/javatests/com/williamfiset/algorithms/datastructures/queue/QueueTest.java deleted file mode 100644 index ea77aa868..000000000 --- a/javatests/com/williamfiset/algorithms/datastructures/queue/QueueTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package javatests.com.williamfiset.algorithms.datastructures.queue; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import com.williamfiset.algorithms.datastructures.queue.Queue; -import org.junit.Before; -import org.junit.Test; - -public class QueueTest { - - Queue queue; - - @Before - public void setup() { - queue = new Queue(); - } - - @Test - public void testEmptyQueue() { - assertTrue(queue.isEmpty()); - assertEquals(queue.size(), 0); - } - - @Test(expected = Exception.class) - public void testPollOnEmpty() { - queue.poll(); - } - - @Test(expected = Exception.class) - public void testPeekOnEmpty() { - queue.peek(); - } - - @Test - public void testOffer() { - queue.offer(2); - assertEquals(queue.size(), 1); - } - - @Test - public void testPeek() { - queue.offer(2); - assertTrue(queue.peek() == 2); - assertEquals(queue.size(), 1); - } - - @Test - public void testPoll() { - queue.offer(2); - assertTrue(queue.poll() == 2); - assertEquals(queue.size(), 0); - } - - @Test - public void testExhaustively() { - assertTrue(queue.isEmpty()); - queue.offer(1); - assertTrue(!queue.isEmpty()); - queue.offer(2); - assertEquals(queue.size(), 2); - assertTrue(queue.peek() == 1); - assertEquals(queue.size(), 2); - assertTrue(queue.poll() == 1); - assertEquals(queue.size(), 1); - assertTrue(queue.peek() == 2); - assertEquals(queue.size(), 1); - assertTrue(queue.poll() == 2); - assertEquals(queue.size(), 0); - assertTrue(queue.isEmpty()); - } -} diff --git a/javatests/com/williamfiset/algorithms/datastructures/segmenttree/SegmentTreeWithPointersTest.java b/javatests/com/williamfiset/algorithms/datastructures/segmenttree/SegmentTreeWithPointersTest.java deleted file mode 100644 index efb09ae38..000000000 --- a/javatests/com/williamfiset/algorithms/datastructures/segmenttree/SegmentTreeWithPointersTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package javatests.com.williamfiset.algorithms.datastructures.segmenttree; - -import static org.junit.Assert.*; - -import com.williamfiset.algorithms.datastructures.segmenttree.Node; -import java.util.*; -import org.junit.Before; -import org.junit.Test; - -public class SegmentTreeWithPointersTest { - - static final int LOOPS = 50; - static final int TEST_SZ = 1000; - static final int MIN_RAND_NUM = 0; - static final int MAX_RAND_NUM = +2000; - - @Before - public void setup() {} - - @Test(expected = IllegalArgumentException.class) - public void testIllegalSegmentTreeCreation1() { - Node tree = new Node(null); - } - - @Test(expected = IllegalArgumentException.class) - public void testIllegalSegmentTreeCreation2() { - int size = -10; - Node tree = new Node(size); - } - - @Test - public void testSumQuery() { - - int[] values = {1, 2, 3, 4, 5}; - Node tree = new Node(values); - - assertEquals(1, tree.sum(0, 1)); - assertEquals(2, tree.sum(1, 2)); - assertEquals(3, tree.sum(2, 3)); - assertEquals(4, tree.sum(3, 4)); - assertEquals(5, tree.sum(4, 5)); - } - - // Select a lower bound index for the Fenwick tree - public static int lowBound(int N) { - return (int) (Math.random() * N); - } - - // Select an upper bound index for the Fenwick tree - public static int highBound(int low, int N) { - return Math.min(N, low + (int) (Math.random() * N)); - } - - public static long randValue() { - return (long) (Math.random() * MAX_RAND_NUM * 2) + MIN_RAND_NUM; - } -} diff --git a/javatests/com/williamfiset/algorithms/datastructures/stack/StackTest.java b/javatests/com/williamfiset/algorithms/datastructures/stack/StackTest.java deleted file mode 100644 index 047848dac..000000000 --- a/javatests/com/williamfiset/algorithms/datastructures/stack/StackTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package javatests.com.williamfiset.algorithms.datastructures.stack; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import com.williamfiset.algorithms.datastructures.stack.Stack; -import org.junit.Before; -import org.junit.Test; - -public class StackTest { - - Stack stack; - - @Before - public void setup() { - stack = new Stack(); - } - - @Test - public void testEmptyStack() { - assertTrue(stack.isEmpty()); - assertEquals(stack.size(), 0); - } - - @Test(expected = Exception.class) - public void testPopOnEmpty() { - stack.pop(); - } - - @Test(expected = Exception.class) - public void testPeekOnEmpty() { - stack.peek(); - } - - @Test - public void testPush() { - stack.push(2); - assertEquals(stack.size(), 1); - } - - @Test - public void testPeek() { - stack.push(2); - assertTrue(stack.peek() == 2); - assertEquals(stack.size(), 1); - } - - @Test - public void testPop() { - stack.push(2); - assertTrue(stack.pop() == 2); - assertEquals(stack.size(), 0); - } - - @Test - public void testExhaustively() { - assertTrue(stack.isEmpty()); - stack.push(1); - assertTrue(!stack.isEmpty()); - stack.push(2); - assertEquals(stack.size(), 2); - assertTrue(stack.peek() == 2); - assertEquals(stack.size(), 2); - assertTrue(stack.pop() == 2); - assertEquals(stack.size(), 1); - assertTrue(stack.peek() == 1); - assertEquals(stack.size(), 1); - assertTrue(stack.pop() == 1); - assertEquals(stack.size(), 0); - assertTrue(stack.isEmpty()); - } -} diff --git a/javatests/com/williamfiset/algorithms/datastructures/trie/TrieTest.java b/javatests/com/williamfiset/algorithms/datastructures/trie/TrieTest.java deleted file mode 100644 index f361991c9..000000000 --- a/javatests/com/williamfiset/algorithms/datastructures/trie/TrieTest.java +++ /dev/null @@ -1,405 +0,0 @@ -package javatests.com.williamfiset.algorithms.datastructures.trie; - -import static org.junit.Assert.*; - -import com.williamfiset.algorithms.datastructures.trie.Trie; -import org.junit.*; - -public class TrieTest { - - // @Before public void setup() { } - - @Test(expected = IllegalArgumentException.class) - public void testBadTrieDelete1() { - Trie t = new Trie(); - t.insert("some string"); - t.delete("some string", 0); - } - - @Test(expected = IllegalArgumentException.class) - public void testBadTrieDelete2() { - Trie t = new Trie(); - t.insert("some string"); - t.delete("some string", -1); - } - - @Test(expected = IllegalArgumentException.class) - public void testBadTrieDelete3() { - Trie t = new Trie(); - t.insert("some string"); - t.delete("some string", -345); - } - - @Test(expected = IllegalArgumentException.class) - public void testBadTrieInsert() { - (new Trie()).insert(null); - } - - @Test(expected = IllegalArgumentException.class) - public void testBadTrieCount() { - (new Trie()).count(null); - } - - @Test(expected = IllegalArgumentException.class) - public void testBadTrieContains() { - (new Trie()).contains(null); - } - - @Test - public void testContains() { - - Trie t1 = new Trie(); - - // This implementation doesn't count the empty string as - // a valid string to be inserted into the trie (although it - // would be easy to account for) - t1.insert(""); - assertFalse(t1.contains("")); - t1.insert(""); - assertFalse(t1.contains("")); - t1.insert(""); - assertFalse(t1.contains("")); - - Trie t2 = new Trie(); - t2.insert("aaaaa"); - t2.insert("aaaaa"); - t2.insert("aaaaa"); - t2.insert("aaaaa"); - t2.insert("aaaaa"); - assertTrue(t2.contains("aaaaa")); - assertTrue(t2.contains("aaaa")); - assertTrue(t2.contains("aaa")); - assertTrue(t2.contains("aa")); - assertTrue(t2.contains("a")); - - Trie t3 = new Trie(); - - t3.insert("AE"); - t3.insert("AE"); - t3.insert("AH"); - t3.insert("AH"); - t3.insert("AH7"); - t3.insert("A7"); - t3.insert("7"); - t3.insert("7"); - t3.insert("B"); - t3.insert("B"); - t3.insert("B"); - t3.insert("B"); - - assertTrue(t3.contains("A")); - assertTrue(t3.contains("AH")); - assertTrue(t3.contains("A7")); - assertTrue(t3.contains("AE")); - assertTrue(t3.contains("AH7")); - assertTrue(t3.contains("7")); - assertTrue(t3.contains("B")); - - assertFalse(t3.contains("Ar")); - assertFalse(t3.contains("A8")); - assertFalse(t3.contains("AH6")); - assertFalse(t3.contains("C")); - } - - @Test - public void testCount() { - - Trie t1 = new Trie(); - - // This implementation doesn't count the empty string as - // a valid string to be inserted into the trie (although it - // would be easy to account for) - t1.insert(""); - assertEquals(t1.count(""), 0); - t1.insert(""); - assertEquals(t1.count(""), 0); - t1.insert(""); - assertEquals(t1.count(""), 0); - - Trie t2 = new Trie(); - t2.insert("aaaaa"); - t2.insert("aaaaa"); - t2.insert("aaaaa"); - t2.insert("aaaaa"); - t2.insert("aaaaa"); - assertEquals(t2.count("aaaaa"), 5); - assertEquals(t2.count("aaaa"), 5); - assertEquals(t2.count("aaa"), 5); - assertEquals(t2.count("aa"), 5); - assertEquals(t2.count("a"), 5); - - Trie t3 = new Trie(); - - t3.insert("AE"); - t3.insert("AE"); - t3.insert("AH"); - t3.insert("AH"); - t3.insert("AH7"); - t3.insert("A7"); - - t3.insert("7"); - t3.insert("7"); - - t3.insert("B"); - t3.insert("B"); - t3.insert("B"); - t3.insert("B"); - - assertEquals(t3.count("A"), 6); - assertEquals(t3.count("AH"), 3); - assertEquals(t3.count("A7"), 1); - assertEquals(t3.count("AE"), 2); - assertEquals(t3.count("AH7"), 1); - assertEquals(t3.count("7"), 2); - assertEquals(t3.count("B"), 4); - assertEquals(t3.count("Ar"), 0); - assertEquals(t3.count("A8"), 0); - assertEquals(t3.count("AH6"), 0); - assertEquals(t3.count("C"), 0); - } - - @Test - public void testInsert() { - - Trie t = new Trie(); - assertFalse(t.insert("a")); - assertFalse(t.insert("b")); - assertFalse(t.insert("c")); - assertFalse(t.insert("d")); - assertFalse(t.insert("x")); - - assertTrue(t.insert("ab")); - assertTrue(t.insert("xkcd")); - assertTrue(t.insert("dogs")); - assertTrue(t.insert("bears")); - - assertFalse(t.insert("mo")); - assertTrue(t.insert("mooooose")); - - t.clear(); - - assertFalse(t.insert("aaaa", 4)); - assertEquals(t.count("aaaa"), 4); - - assertTrue(t.insert("aaa", 3)); - assertEquals(t.count("a"), 7); - assertEquals(t.count("aa"), 7); - assertEquals(t.count("aaa"), 7); - assertEquals(t.count("aaaa"), 4); - assertEquals(t.count("aaaaa"), 0); - - assertTrue(t.insert("a", 5)); - assertEquals(t.count("a"), 12); - assertEquals(t.count("aa"), 7); - assertEquals(t.count("aaa"), 7); - assertEquals(t.count("aaaa"), 4); - assertEquals(t.count("aaaaa"), 0); - } - - @Test - public void testClear() { - - Trie t = new Trie(); - - assertFalse(t.insert("a")); - assertFalse(t.insert("b")); - assertFalse(t.insert("c")); - - assertTrue(t.contains("a")); - assertTrue(t.contains("b")); - assertTrue(t.contains("c")); - - t.clear(); - - assertFalse(t.contains("a")); - assertFalse(t.contains("b")); - assertFalse(t.contains("c")); - - t.insert("aaaa"); - t.insert("aaab"); - t.insert("aaab5"); - t.insert("aaac"); - t.insert("aaacb"); - - assertTrue(t.contains("aaa")); - assertTrue(t.contains("aaacb")); - assertTrue(t.contains("aaab5")); - - t.clear(); - - assertFalse(t.contains("aaaa")); - assertFalse(t.contains("aaab")); - assertFalse(t.contains("aaab5")); - assertFalse(t.contains("aaac")); - assertFalse(t.contains("aaacb")); - } - - @Test - public void testDelete() { - - Trie t = new Trie(); - t.insert("AAC"); - t.insert("AA"); - t.insert("A"); - - assertTrue(t.delete("AAC")); - assertFalse(t.contains("AAC")); - assertTrue(t.contains("AA")); - assertTrue(t.contains("A")); - - assertTrue(t.delete("AA")); - assertFalse(t.contains("AAC")); - assertFalse(t.contains("AA")); - assertTrue(t.contains("A")); - - assertTrue(t.delete("A")); - assertFalse(t.contains("AAC")); - assertFalse(t.contains("AA")); - assertFalse(t.contains("A")); - - t.clear(); - - t.insert("AAC"); - t.insert("AA"); - t.insert("A"); - - assertTrue(t.delete("AA")); - assertTrue(t.delete("AA")); - - assertFalse(t.contains("AAC")); - assertFalse(t.contains("AA")); - assertTrue(t.contains("A")); - - t.clear(); - - t.insert("$A"); - t.insert("$B"); - t.insert("$C"); - - assertTrue(t.delete("$", 3)); - - assertFalse(t.delete("$")); - assertFalse(t.contains("$")); - assertFalse(t.contains("$A")); - assertFalse(t.contains("$B")); - assertFalse(t.contains("$C")); - assertFalse(t.delete("$A")); - assertFalse(t.delete("$B")); - assertFalse(t.delete("$C")); - - t.clear(); - - t.insert("$A"); - t.insert("$B"); - t.insert("$C"); - - assertTrue(t.delete("$", 2)); - assertTrue(t.delete("$")); - - assertFalse(t.contains("$")); - assertFalse(t.contains("$A")); - assertFalse(t.contains("$B")); - assertFalse(t.contains("$C")); - assertFalse(t.delete("$A")); - assertFalse(t.delete("$B")); - assertFalse(t.delete("$C")); - - t.clear(); - - t.insert("$A"); - t.insert("$B"); - t.insert("$C"); - - assertTrue(t.delete("$", 2)); - - assertTrue(t.contains("$")); - assertTrue(t.contains("$A")); - assertTrue(t.contains("$B")); - assertTrue(t.contains("$C")); - assertTrue(t.delete("$A")); - assertFalse(t.delete("$B")); - assertFalse(t.delete("$C")); - - t.clear(); - - t.insert("CAT", 3); - t.insert("DOG", 3); - - assertFalse(t.delete("parrot", 50)); - - t.clear(); - - t.insert("1234"); - t.insert("122", 2); - t.insert("123", 3); - - assertTrue(t.delete("12", 6)); - assertFalse(t.delete("12")); - assertFalse(t.delete("1")); - assertFalse(t.contains("1234")); - assertFalse(t.contains("123")); - assertFalse(t.contains("12")); - assertFalse(t.contains("1")); - - t.clear(); - - t.insert("1234"); - t.insert("122", 2); - t.insert("123", 3); - - t.delete("12", 999999); - - assertFalse(t.contains("1234")); - assertFalse(t.contains("123")); - assertFalse(t.contains("12")); - assertFalse(t.contains("1")); - - t.clear(); - - t.insert("1234"); - t.insert("122", 2); - t.insert("123", 3); - - t.delete("12", 999999); - - assertFalse(t.contains("1234")); - assertFalse(t.contains("123")); - assertFalse(t.contains("12")); - assertFalse(t.contains("1")); - - t.clear(); - - t.insert("1234"); - t.insert("122", 2); - t.insert("123", 3); - - assertTrue(t.delete("1234")); - assertTrue(t.delete("123", 4)); - assertTrue(t.delete("122", 2)); - - assertFalse(t.contains("1")); - assertFalse(t.contains("12")); - assertFalse(t.contains("122")); - assertFalse(t.contains("123")); - assertFalse(t.contains("1234")); - } - - @Test - public void testEdgeCases() { - - Trie t = new Trie(); - assertEquals(t.count(""), 0); - assertEquals(t.count("\0"), 0); - assertEquals(t.count("\0\0"), 0); - assertEquals(t.count("\0\0\0"), 0); - - for (char c = 0; c < 128; c++) assertEquals(t.count("" + c), 0); - - assertFalse(t.contains("")); - assertFalse(t.contains("\0")); - assertFalse(t.contains("\0\0")); - assertFalse(t.contains("\0\0\0")); - - for (char c = 0; c < 128; c++) assertFalse(t.contains("" + c)); - } -} diff --git a/javatests/com/williamfiset/algorithms/datastructures/unionfind/UnionFindTest.java b/javatests/com/williamfiset/algorithms/datastructures/unionfind/UnionFindTest.java deleted file mode 100644 index 5246561c0..000000000 --- a/javatests/com/williamfiset/algorithms/datastructures/unionfind/UnionFindTest.java +++ /dev/null @@ -1,235 +0,0 @@ -package javatests.com.williamfiset.algorithms.datastructures.unionfind; - -import static org.junit.Assert.*; - -import com.williamfiset.algorithms.datastructures.unionfind.UnionFind; -import org.junit.*; - -public class UnionFindTest { - - @Test - public void testNumComponents() { - - UnionFind uf = new UnionFind(5); - assertEquals(uf.components(), 5); - - uf.unify(0, 1); - assertEquals(uf.components(), 4); - - uf.unify(1, 0); - assertEquals(uf.components(), 4); - - uf.unify(1, 2); - assertEquals(uf.components(), 3); - - uf.unify(0, 2); - assertEquals(uf.components(), 3); - - uf.unify(2, 1); - assertEquals(uf.components(), 3); - - uf.unify(3, 4); - assertEquals(uf.components(), 2); - - uf.unify(4, 3); - assertEquals(uf.components(), 2); - - uf.unify(1, 3); - assertEquals(uf.components(), 1); - - uf.unify(4, 0); - assertEquals(uf.components(), 1); - } - - @Test - public void testComponentSize() { - - UnionFind uf = new UnionFind(5); - assertEquals(uf.componentSize(0), 1); - assertEquals(uf.componentSize(1), 1); - assertEquals(uf.componentSize(2), 1); - assertEquals(uf.componentSize(3), 1); - assertEquals(uf.componentSize(4), 1); - - uf.unify(0, 1); - assertEquals(uf.componentSize(0), 2); - assertEquals(uf.componentSize(1), 2); - assertEquals(uf.componentSize(2), 1); - assertEquals(uf.componentSize(3), 1); - assertEquals(uf.componentSize(4), 1); - - uf.unify(1, 0); - assertEquals(uf.componentSize(0), 2); - assertEquals(uf.componentSize(1), 2); - assertEquals(uf.componentSize(2), 1); - assertEquals(uf.componentSize(3), 1); - assertEquals(uf.componentSize(4), 1); - - uf.unify(1, 2); - assertEquals(uf.componentSize(0), 3); - assertEquals(uf.componentSize(1), 3); - assertEquals(uf.componentSize(2), 3); - assertEquals(uf.componentSize(3), 1); - assertEquals(uf.componentSize(4), 1); - - uf.unify(0, 2); - assertEquals(uf.componentSize(0), 3); - assertEquals(uf.componentSize(1), 3); - assertEquals(uf.componentSize(2), 3); - assertEquals(uf.componentSize(3), 1); - assertEquals(uf.componentSize(4), 1); - - uf.unify(2, 1); - assertEquals(uf.componentSize(0), 3); - assertEquals(uf.componentSize(1), 3); - assertEquals(uf.componentSize(2), 3); - assertEquals(uf.componentSize(3), 1); - assertEquals(uf.componentSize(4), 1); - - uf.unify(3, 4); - assertEquals(uf.componentSize(0), 3); - assertEquals(uf.componentSize(1), 3); - assertEquals(uf.componentSize(2), 3); - assertEquals(uf.componentSize(3), 2); - assertEquals(uf.componentSize(4), 2); - - uf.unify(4, 3); - assertEquals(uf.componentSize(0), 3); - assertEquals(uf.componentSize(1), 3); - assertEquals(uf.componentSize(2), 3); - assertEquals(uf.componentSize(3), 2); - assertEquals(uf.componentSize(4), 2); - - uf.unify(1, 3); - assertEquals(uf.componentSize(0), 5); - assertEquals(uf.componentSize(1), 5); - assertEquals(uf.componentSize(2), 5); - assertEquals(uf.componentSize(3), 5); - assertEquals(uf.componentSize(4), 5); - - uf.unify(4, 0); - assertEquals(uf.componentSize(0), 5); - assertEquals(uf.componentSize(1), 5); - assertEquals(uf.componentSize(2), 5); - assertEquals(uf.componentSize(3), 5); - assertEquals(uf.componentSize(4), 5); - } - - @Test - public void testConnectivity() { - - int sz = 7; - UnionFind uf = new UnionFind(sz); - - for (int i = 0; i < sz; i++) assertTrue(uf.connected(i, i)); - - uf.unify(0, 2); - - assertTrue(uf.connected(0, 2)); - assertTrue(uf.connected(2, 0)); - - assertFalse(uf.connected(0, 1)); - assertFalse(uf.connected(3, 1)); - assertFalse(uf.connected(6, 4)); - assertFalse(uf.connected(5, 0)); - - for (int i = 0; i < sz; i++) assertTrue(uf.connected(i, i)); - - uf.unify(3, 1); - - assertTrue(uf.connected(0, 2)); - assertTrue(uf.connected(2, 0)); - assertTrue(uf.connected(1, 3)); - assertTrue(uf.connected(3, 1)); - - assertFalse(uf.connected(0, 1)); - assertFalse(uf.connected(1, 2)); - assertFalse(uf.connected(2, 3)); - assertFalse(uf.connected(1, 0)); - assertFalse(uf.connected(2, 1)); - assertFalse(uf.connected(3, 2)); - - assertFalse(uf.connected(1, 4)); - assertFalse(uf.connected(2, 5)); - assertFalse(uf.connected(3, 6)); - - for (int i = 0; i < sz; i++) assertTrue(uf.connected(i, i)); - - uf.unify(2, 5); - assertTrue(uf.connected(0, 2)); - assertTrue(uf.connected(2, 0)); - assertTrue(uf.connected(1, 3)); - assertTrue(uf.connected(3, 1)); - assertTrue(uf.connected(0, 5)); - assertTrue(uf.connected(5, 0)); - assertTrue(uf.connected(5, 2)); - assertTrue(uf.connected(2, 5)); - - assertFalse(uf.connected(0, 1)); - assertFalse(uf.connected(1, 2)); - assertFalse(uf.connected(2, 3)); - assertFalse(uf.connected(1, 0)); - assertFalse(uf.connected(2, 1)); - assertFalse(uf.connected(3, 2)); - - assertFalse(uf.connected(4, 6)); - assertFalse(uf.connected(4, 5)); - assertFalse(uf.connected(1, 6)); - - for (int i = 0; i < sz; i++) assertTrue(uf.connected(i, i)); - - // Connect everything - uf.unify(1, 2); - uf.unify(3, 4); - uf.unify(4, 6); - - for (int i = 0; i < sz; i++) { - for (int j = 0; j < sz; j++) { - assertTrue(uf.connected(i, j)); - } - } - } - - @Test - public void testSize() { - - UnionFind uf = new UnionFind(5); - assertEquals(uf.size(), 5); - uf.unify(0, 1); - uf.find(3); - assertEquals(uf.size(), 5); - uf.unify(1, 2); - assertEquals(uf.size(), 5); - uf.unify(0, 2); - uf.find(1); - assertEquals(uf.size(), 5); - uf.unify(2, 1); - assertEquals(uf.size(), 5); - uf.unify(3, 4); - uf.find(0); - assertEquals(uf.size(), 5); - uf.unify(4, 3); - uf.find(3); - assertEquals(uf.size(), 5); - uf.unify(1, 3); - assertEquals(uf.size(), 5); - uf.find(2); - uf.unify(4, 0); - assertEquals(uf.size(), 5); - } - - @Test(expected = IllegalArgumentException.class) - public void testBadUnionFindCreation1() { - new UnionFind(-1); - } - - @Test(expected = IllegalArgumentException.class) - public void testBadUnionFindCreation2() { - new UnionFind(-3463); - } - - @Test(expected = IllegalArgumentException.class) - public void testBadUnionFindCreation3() { - new UnionFind(0); - } -} diff --git a/javatests/com/williamfiset/algorithms/dp/CoinChangeTest.java b/javatests/com/williamfiset/algorithms/dp/CoinChangeTest.java deleted file mode 100644 index 197ecee91..000000000 --- a/javatests/com/williamfiset/algorithms/dp/CoinChangeTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package javatests.com.williamfiset.algorithms.dp; - -import static com.google.common.truth.Truth.assertThat; - -import com.google.common.primitives.Ints; -import com.williamfiset.algorithms.dp.CoinChange; -import com.williamfiset.algorithms.utils.TestUtils; -import java.util.*; -import org.junit.*; - -public class CoinChangeTest { - - static final int LOOPS = 100; - - @Test - public void testCoinChange() { - for (int i = 1; i < LOOPS; i++) { - List values = TestUtils.randomIntegerList(i, 1, 1000); - int[] coinValues = Ints.toArray(values); - - int amount = TestUtils.randValue(1, 1000); - - int v1 = CoinChange.coinChange(coinValues, amount); - int v2 = CoinChange.coinChangeSpaceEfficient(coinValues, amount); - int v3 = CoinChange.coinChangeRecursive(coinValues, amount); - - assertThat(v1).isEqualTo(v2); - assertThat(v2).isEqualTo(v3); - } - } -} diff --git a/javatests/com/williamfiset/algorithms/dp/MinimumWeightPerfectMatchingTest.java b/javatests/com/williamfiset/algorithms/dp/MinimumWeightPerfectMatchingTest.java deleted file mode 100644 index b438e1aff..000000000 --- a/javatests/com/williamfiset/algorithms/dp/MinimumWeightPerfectMatchingTest.java +++ /dev/null @@ -1,154 +0,0 @@ -package javatests.com.williamfiset.algorithms.dp; - -import static com.google.common.truth.Truth.assertThat; - -import com.williamfiset.algorithms.dp.MinimumWeightPerfectMatching; -import java.util.*; -import org.junit.*; - -public class MinimumWeightPerfectMatchingTest { - - static final int LOOPS = 25; - - static class BruteForceMwpm { - private int n; - private double[][] matrix; - private double minWeightMatching = Double.POSITIVE_INFINITY; - - public BruteForceMwpm(double[][] matrix) { - this.matrix = matrix; - this.n = matrix.length; - } - - public double getMinWeightCost() { - solve(); - return minWeightMatching; - } - - public double computeMatchingCost(int[] p) { - double t = 0; - for (int i = 0; i < n / 2; i++) { - int ii = p[2 * i]; - int jj = p[2 * i + 1]; - t += matrix[ii][jj]; - } - return t; - } - - public void solve() { - int[] permutation = new int[n]; - for (int i = 0; i < n; i++) permutation[i] = i; - - // Try all matchings - do { - double matchingCost = computeMatchingCost(permutation); - if (matchingCost < minWeightMatching) { - minWeightMatching = matchingCost; - } - } while (nextPermutation(permutation)); - } - - // Generates the next ordered permutation in-place (skips repeated permutations). - // Calling this when the array is already at the highest permutation returns false. - // Recommended usage is to start with the smallest permutations and use a do while - // loop to generate each successive permutations (see main for example). - public static boolean nextPermutation(int[] sequence) { - int first = getFirst(sequence); - if (first == -1) return false; - int toSwap = sequence.length - 1; - while (sequence[first] >= sequence[toSwap]) --toSwap; - swap(sequence, first++, toSwap); - toSwap = sequence.length - 1; - while (first < toSwap) swap(sequence, first++, toSwap--); - return true; - } - - private static int getFirst(int[] sequence) { - for (int i = sequence.length - 2; i >= 0; --i) if (sequence[i] < sequence[i + 1]) return i; - return -1; - } - - private static void swap(int[] sequence, int i, int j) { - int tmp = sequence[i]; - sequence[i] = sequence[j]; - sequence[j] = tmp; - } - } - - @Test - public void testMatchingOutputsUniqueNodes() { - for (int loop = 0; loop < LOOPS; loop++) { - int n = Math.max(1, (int) (Math.random() * 11)) * 2; // n is either 2,4,6,8,10,12,14,16,18,20 - double[][] costMatrix = new double[n][n]; - randomFillSymmetricMatrix(costMatrix, 100); - - MinimumWeightPerfectMatching mwpm = new MinimumWeightPerfectMatching(costMatrix); - int[] matching = mwpm.getMinWeightCostMatching(); - Set set = new HashSet<>(); - for (int i = 0; i < matching.length; i++) { - set.add(matching[i]); - } - - assertThat(set.size()).isEqualTo(matching.length); - } - } - - @Test - public void testMatchingAndCostAreConsistent() { - for (int loop = 0; loop < LOOPS; loop++) { - int n = Math.max(1, (int) (Math.random() * 11)) * 2; // n is either 2,4,6,8,10,12,14,16,18,20 - double[][] costMatrix = new double[n][n]; - randomFillSymmetricMatrix(costMatrix, 100); - - MinimumWeightPerfectMatching mwpm = new MinimumWeightPerfectMatching(costMatrix); - int[] matching = mwpm.getMinWeightCostMatching(); - double totalMinCost = 0; - for (int i = 0; i < matching.length / 2; i++) { - int ii = matching[2 * i]; - int jj = matching[2 * i + 1]; - totalMinCost += costMatrix[ii][jj]; - } - assertThat(totalMinCost).isEqualTo(mwpm.getMinWeightCost()); - } - } - - @Test - public void testAgainstBruteForce_largeValues() { - for (int loop = 0; loop < LOOPS; loop++) { - int n = Math.max(1, (int) (Math.random() * 6)) * 2; // n is either 2,4,6,8, or 10 - double[][] costMatrix = new double[n][n]; - randomFillSymmetricMatrix(costMatrix, /*maxValue=*/ 10000); - - MinimumWeightPerfectMatching mwpm = new MinimumWeightPerfectMatching(costMatrix); - BruteForceMwpm bfMwpm = new BruteForceMwpm(costMatrix); - double dpSoln = mwpm.getMinWeightCost(); - double bfSoln = bfMwpm.getMinWeightCost(); - assertThat(dpSoln).isEqualTo(bfSoln); - } - } - - @Test - public void testAgainstBruteForce_smallValues() { - for (int loop = 0; loop < LOOPS; loop++) { - int n = Math.max(1, (int) (Math.random() * 6)) * 2; // n is either 2,4,6,8, or 10 - double[][] costMatrix = new double[n][n]; - randomFillSymmetricMatrix(costMatrix, /*maxValue=*/ 3); - - MinimumWeightPerfectMatching mwpm = new MinimumWeightPerfectMatching(costMatrix); - BruteForceMwpm bfMwpm = new BruteForceMwpm(costMatrix); - double dpSoln = mwpm.getMinWeightCost(); - double bfSoln = bfMwpm.getMinWeightCost(); - - assertThat(dpSoln).isEqualTo(bfSoln); - } - } - - public void randomFillSymmetricMatrix(double[][] dist, int maxValue) { - for (int i = 0; i < dist.length; i++) { - for (int j = i + 1; j < dist.length; j++) { - double val = (int) (Math.random() * maxValue); - dist[i][j] = dist[j][i] = val; - } - } - } -} diff --git a/javatests/com/williamfiset/algorithms/search/InterpolationSearchTest.java b/javatests/com/williamfiset/algorithms/search/InterpolationSearchTest.java deleted file mode 100644 index 0d72a6e2a..000000000 --- a/javatests/com/williamfiset/algorithms/search/InterpolationSearchTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package javatests.com.williamfiset.algorithms.search; - -import static org.junit.Assert.*; - -import com.williamfiset.algorithms.search.InterpolationSearch; -import org.junit.Test; - -public class InterpolationSearchTest { - private static InterpolationSearch search; - - public static void prepareTest() { - search = new InterpolationSearch(); - } - - @Test - public void testCoverage1() { - int[] arr = {0, 1, 2, 3, 4, 5}; - int index = search.interpolationSearch(arr, 2); - assertTrue(index == 2); - } - - @Test - public void testCoverage2() { - int[] arr = {0, 1, 2, 3, 4, 5}; - int index = search.interpolationSearch(arr, 5); - assertTrue(index == 5); - } - - @Test - public void testCoverage3() { - int[] arr = {0, 1, 2, 3, 4, 5}; - int index = search.interpolationSearch(arr, -1); - assertTrue(index == -1); - } - - @Test - public void testCoverage4() { - int[] arr = {0, 1, 2, 3, 4, 5}; - int index = search.interpolationSearch(arr, 8); - assertTrue(index == -1); - } -} diff --git a/misc/images/comment_img.png b/misc/images/comment_img.png new file mode 100644 index 000000000..3bcbfcab1 Binary files /dev/null and b/misc/images/comment_img.png differ diff --git a/references/AhoCorasick.pdf b/references/AhoCorasick.pdf new file mode 100644 index 000000000..d3e5fed50 Binary files /dev/null and b/references/AhoCorasick.pdf differ diff --git a/slides/geometry/convexhull.key b/slides/binary_search.key old mode 100644 new mode 100755 similarity index 56% rename from slides/geometry/convexhull.key rename to slides/binary_search.key index fed8c9296..fd6d0ec37 Binary files a/slides/geometry/convexhull.key and b/slides/binary_search.key differ diff --git a/slides/constructivealgorithms/powerset.key b/slides/constructivealgorithms/powerset.key index a4385b714..be4451ae4 100644 Binary files a/slides/constructivealgorithms/powerset.key and b/slides/constructivealgorithms/powerset.key differ diff --git a/slides/datastructures/avltree/AvlTree.key b/slides/datastructures/avltree/AvlTree.key index 6bfb31a17..98fbac8cf 100644 Binary files a/slides/datastructures/avltree/AvlTree.key and b/slides/datastructures/avltree/AvlTree.key differ diff --git a/slides/datastructures/introduction/ds_intro_slides.key b/slides/datastructures/introduction/ds_intro_slides.key index e035db635..9753e27fd 100644 Binary files a/slides/datastructures/introduction/ds_intro_slides.key and b/slides/datastructures/introduction/ds_intro_slides.key differ diff --git a/slides/datastructures/priorityqueue/PriorityQueue.key b/slides/datastructures/priorityqueue/PriorityQueue.key index 1b48452e7..91c6de712 100644 Binary files a/slides/datastructures/priorityqueue/PriorityQueue.key and b/slides/datastructures/priorityqueue/PriorityQueue.key differ diff --git a/slides/datastructures/priorityqueue/PriorityQueue.pdf b/slides/datastructures/priorityqueue/PriorityQueue.pdf index c0543e4fb..13f6c1cc5 100644 Binary files a/slides/datastructures/priorityqueue/PriorityQueue.pdf and b/slides/datastructures/priorityqueue/PriorityQueue.pdf differ diff --git a/slides/datastructures/priorityqueue/PriorityQueue.pptx b/slides/datastructures/priorityqueue/PriorityQueue.pptx index 9a08c2aa4..62419c64a 100644 Binary files a/slides/datastructures/priorityqueue/PriorityQueue.pptx and b/slides/datastructures/priorityqueue/PriorityQueue.pptx differ diff --git a/slides/datastructures/priorityqueue/indexed_priority_queue.key b/slides/datastructures/priorityqueue/indexed_priority_queue.key index 8974685be..09b9aa049 100644 Binary files a/slides/datastructures/priorityqueue/indexed_priority_queue.key and b/slides/datastructures/priorityqueue/indexed_priority_queue.key differ diff --git a/slides/datastructures/priorityqueue/indexed_priority_queue.pdf b/slides/datastructures/priorityqueue/indexed_priority_queue.pdf new file mode 100644 index 000000000..1881583cf Binary files /dev/null and b/slides/datastructures/priorityqueue/indexed_priority_queue.pdf differ diff --git a/slides/datastructures/segmenttree/segment_trees.key b/slides/datastructures/segmenttree/segment_trees.key index 2e102edb1..cf3f31906 100644 Binary files a/slides/datastructures/segmenttree/segment_trees.key and b/slides/datastructures/segmenttree/segment_trees.key differ diff --git a/slides/datastructures/sparse_table/script.txt b/slides/datastructures/sparse_table/script.txt new file mode 100644 index 000000000..f2eea3512 --- /dev/null +++ b/slides/datastructures/sparse_table/script.txt @@ -0,0 +1,130 @@ + + +Hello everyone, my name is William, today we're taking a look at some source +code on how to implement a SparseTable. In the last video, we looked at what a +sparse table is, how it can be used to do fast range queries and what's involved +in building one. This video is a follow up to that video, so make sure to give +the other video a watch before proceeding, I'll make sure to put a link in the +description below. + +Awesome, so here we are in the source code written in Java. In this header I put +some instructions on how you can download this code and run it yourself. + +This particular implementation I'm about to show you is for a min sparse table +that can do minimum range queries. If you want to do any other type of range +query such as a max range query or a product range query you will need to modify +this code. I also have another more generic sparse table implementation on +github which can support different query operations if that interested you. + +Right here in the main method, I have a few examples of how this script works. +First you start out with an array of values, then you feed that array to the min +sparse table class, and afterwards you can start doing minimum range queries. In the first +example I query the minimum element in the range 1 to 5 inclusive, which prints the +answer negative 3 with an index position of '2'. + + + +Alright, let's dig into the details of what's going on inside the min sparse +table class. The first thing you will notice is that there are a few instance +variables to know about. + +The first is 'n', the number of elements in the input array. + +Then there's capital P, the floor of the base 2 logarithm of n, this is +effectively the number of rows in the sparse table. + +After, there's the log2 array, this array is simply used to do fast lookups for +the floor of the base 2 logarithm so that we don't need to do any calculations +when we need the value. + +Then dp is just the sparse table with P plus 1 rows and n columns. + +Following this is the variable 'it', short for index table. This is the table +that keeps track of the index of the selected minimum element in the +range we're querying. I briefly mentioned this in the last video, but this table +is super handy to have around, and we will hopefully make use of it in a future video ;P + + + +So when you want to create a MinSparseTable, you need to provide the input +values to do range queries on. However, remember that this input array can only +contain immutable values, if the data changes after the sparse table is built +then the queries on the sparse table will be wrong. + +when you pass in the values array to the MinSparseTable constructor, the +constructor actually builds the sparse table. Doing work in the constructor +isn't considered a best practice, but this is just a simple proof of +concept example. + +We start by getting the value of 'n', the number of +elements in the values array. Then we compute capitol P from the value of 'n'. + +After we know those values, we can go ahead and allocate some memory for our +sparse table, and our index table which both have P+1 rows and 'n' columns. + +Then, the next thing we want to do is simply populate the first row of both the +sparse table with the input values, and the index table with the indeces 0 to N + +After that, populate the log2 array with all the values for the floor of the +base 2 logarithm between 1 and N inclusive. + +Next, we build the sparse table and its associated index table. These two for +loops iterate over all the cells of the table while making +sure not to consider intervals which would go outside the bounds of the table. + +Inside the two loops, we can find the values of the left and the right cells +and take the minimum. + +Lastly, we want to save and propagate the index of the smallest element inside +the index table so that we don’t lose track of it. You can think of the index +table as an identical sparse table that tracks of index values instead of +minimum values. + +If we scroll further below , you see that we have two more methods to look at, +these two methods are used to query the minimum value in a given range, and query +the index of the minimum value in a given range. + +This first method 'queryMin' calculates the value of the smallest element in the +range l to r inclusive in constant time. This method works by finding two +overlapping ranges which cover the entire interval between l and r and taking +the minimum of both intervals. + +The first thing we do is calculate the length of the interval which is +subsequently used to find the value of p, the floor of the base 2 logarithm. +Intuitively, you can think of 'p' as the row in the sparse table we want to +query, and k as the largest power of 2 that fits in the interval. + +Then, simply do a lookup to get the left and the right cell values and take the +minimum. The left cell is found at row `p`, column `l`, and the right cell at +row `p` and column `r - k plus 1`. + +The last method 'queryMinIndex' is used to find the index of the minimum element +in the range `l` to `r` inclusive. + +If there are multiple smallest elements, the index of leftmost one is returned. + +To do this, we’re basically going to do the same thing as when we were finding +the minimum value; we’re going to find the values of the left and the +right cell, but rather than returning the minimum of the two, we’re going to +compare them, find the smaller one, and return the index stored in the +Index Table. + +And that's all I have on sparse tables for now, folks thank you very much for +watching and please subscribe for more content. + + +============== + +16 down to 35 +static image @ 35 + +35 -> 53 +static image @ 53 +53 -> bottom + + + + + + + diff --git a/slides/datastructures/sparse_table/sparse_table.key b/slides/datastructures/sparse_table/sparse_table.key new file mode 100755 index 000000000..b16d88dbd Binary files /dev/null and b/slides/datastructures/sparse_table/sparse_table.key differ diff --git a/slides/datastructures/unionfind/UnionFind.key b/slides/datastructures/unionfind/UnionFind.key index 0fc1bf5c9..16a255af7 100644 Binary files a/slides/datastructures/unionfind/UnionFind.key and b/slides/datastructures/unionfind/UnionFind.key differ diff --git a/slides/datastructures/unionfind/UnionFind.pdf b/slides/datastructures/unionfind/UnionFind.pdf index 2f180ef58..a03b1b2b7 100644 Binary files a/slides/datastructures/unionfind/UnionFind.pdf and b/slides/datastructures/unionfind/UnionFind.pdf differ diff --git a/slides/datastructures/unionfind/UnionFind.pptx b/slides/datastructures/unionfind/UnionFind.pptx index 63bfe538b..aa4dca7e6 100644 Binary files a/slides/datastructures/unionfind/UnionFind.pptx and b/slides/datastructures/unionfind/UnionFind.pptx differ diff --git a/slides/dynamicprogramming/coin_change.key b/slides/dynamicprogramming/coin_change.key new file mode 100755 index 000000000..aa7da884e Binary files /dev/null and b/slides/dynamicprogramming/coin_change.key differ diff --git a/slides/dynamicprogramming/dp_overview.key b/slides/dynamicprogramming/dp_overview.key new file mode 100755 index 000000000..defce7f05 Binary files /dev/null and b/slides/dynamicprogramming/dp_overview.key differ diff --git a/slides/dynamicprogramming/dynamicprogramming.key b/slides/dynamicprogramming/dynamicprogramming.key index 7fa1e059f..32842b650 100644 Binary files a/slides/dynamicprogramming/dynamicprogramming.key and b/slides/dynamicprogramming/dynamicprogramming.key differ diff --git a/slides/dynamicprogramming/edit_distance.key b/slides/dynamicprogramming/edit_distance.key new file mode 100755 index 000000000..1fddc1090 Binary files /dev/null and b/slides/dynamicprogramming/edit_distance.key differ diff --git a/slides/dynamicprogramming/knapsack_01.key b/slides/dynamicprogramming/knapsack_01.key index ad633c522..75e66205b 100644 Binary files a/slides/dynamicprogramming/knapsack_01.key and b/slides/dynamicprogramming/knapsack_01.key differ diff --git a/slides/dynamicprogramming/magic_cows.key b/slides/dynamicprogramming/magic_cows.key new file mode 100755 index 000000000..330521693 Binary files /dev/null and b/slides/dynamicprogramming/magic_cows.key differ diff --git a/slides/dynamicprogramming/narrowartgallery.key b/slides/dynamicprogramming/narrowartgallery.key new file mode 100644 index 000000000..496807732 Binary files /dev/null and b/slides/dynamicprogramming/narrowartgallery.key differ diff --git a/slides/dynamicprogramming/template.key b/slides/dynamicprogramming/template.key new file mode 100755 index 000000000..4395412f7 Binary files /dev/null and b/slides/dynamicprogramming/template.key differ diff --git a/slides/dynamicprogramming/tiling_problems.key b/slides/dynamicprogramming/tiling_problems.key new file mode 100755 index 000000000..2c919b3dc Binary files /dev/null and b/slides/dynamicprogramming/tiling_problems.key differ diff --git a/slides/dynamicprogramming/tiling_problems_part1.key b/slides/dynamicprogramming/tiling_problems_part1.key new file mode 100755 index 000000000..83ba2b6c6 Binary files /dev/null and b/slides/dynamicprogramming/tiling_problems_part1.key differ diff --git a/slides/dynamicprogramming/tiling_problems_part2.key b/slides/dynamicprogramming/tiling_problems_part2.key new file mode 100755 index 000000000..5b6a25e40 Binary files /dev/null and b/slides/dynamicprogramming/tiling_problems_part2.key differ diff --git a/slides/dynamicprogramming/tiling_problems_part3.key b/slides/dynamicprogramming/tiling_problems_part3.key new file mode 100755 index 000000000..15ee537e3 Binary files /dev/null and b/slides/dynamicprogramming/tiling_problems_part3.key differ diff --git a/slides/dynamicprogramming/weighted_maximum_cardinality_matching.key b/slides/dynamicprogramming/weighted_maximum_cardinality_matching.key new file mode 100755 index 000000000..98ac83519 Binary files /dev/null and b/slides/dynamicprogramming/weighted_maximum_cardinality_matching.key differ diff --git a/slides/graphtheory/bellman_ford.key b/slides/graphtheory/bellman_ford.key deleted file mode 100644 index 989cae4c7..000000000 Binary files a/slides/graphtheory/bellman_ford.key and /dev/null differ diff --git a/slides/graphtheory/bellman_ford.pdf b/slides/graphtheory/bellman_ford.pdf deleted file mode 100644 index 6c919f593..000000000 Binary files a/slides/graphtheory/bellman_ford.pdf and /dev/null differ diff --git a/slides/graphtheory/bellman_ford.pptx b/slides/graphtheory/bellman_ford.pptx deleted file mode 100644 index 8230a8a35..000000000 Binary files a/slides/graphtheory/bellman_ford.pptx and /dev/null differ diff --git a/slides/graphtheory/bfs_grid_shortest_path.key b/slides/graphtheory/bfs_grid_shortest_path.key deleted file mode 100644 index f665778ea..000000000 Binary files a/slides/graphtheory/bfs_grid_shortest_path.key and /dev/null differ diff --git a/slides/graphtheory/bfs_grid_shortest_path.pdf b/slides/graphtheory/bfs_grid_shortest_path.pdf deleted file mode 100644 index 3ee41b191..000000000 Binary files a/slides/graphtheory/bfs_grid_shortest_path.pdf and /dev/null differ diff --git a/slides/graphtheory/breadth_first_search.key b/slides/graphtheory/breadth_first_search.key deleted file mode 100644 index ddddc57a7..000000000 Binary files a/slides/graphtheory/breadth_first_search.key and /dev/null differ diff --git a/slides/graphtheory/breadth_first_search.pdf b/slides/graphtheory/breadth_first_search.pdf deleted file mode 100644 index 87688b644..000000000 Binary files a/slides/graphtheory/breadth_first_search.pdf and /dev/null differ diff --git a/slides/graphtheory/breadth_first_search.pptx b/slides/graphtheory/breadth_first_search.pptx deleted file mode 100644 index bc8c9f673..000000000 Binary files a/slides/graphtheory/breadth_first_search.pptx and /dev/null differ diff --git a/slides/graphtheory/bridges_and_articulation_points.key b/slides/graphtheory/bridges_and_articulation_points.key deleted file mode 100644 index db5d46f05..000000000 Binary files a/slides/graphtheory/bridges_and_articulation_points.key and /dev/null differ diff --git a/slides/graphtheory/bridges_and_articulation_points.pdf b/slides/graphtheory/bridges_and_articulation_points.pdf deleted file mode 100644 index 11dc2d9de..000000000 Binary files a/slides/graphtheory/bridges_and_articulation_points.pdf and /dev/null differ diff --git a/slides/graphtheory/bridges_and_articulation_points.pptx b/slides/graphtheory/bridges_and_articulation_points.pptx deleted file mode 100644 index 41c03170f..000000000 Binary files a/slides/graphtheory/bridges_and_articulation_points.pptx and /dev/null differ diff --git a/slides/graphtheory/common_graph_theory_problems.key b/slides/graphtheory/common_graph_theory_problems.key deleted file mode 100644 index 6ad28ada9..000000000 Binary files a/slides/graphtheory/common_graph_theory_problems.key and /dev/null differ diff --git a/slides/graphtheory/common_graph_theory_problems.pdf b/slides/graphtheory/common_graph_theory_problems.pdf deleted file mode 100644 index 4bb55b446..000000000 Binary files a/slides/graphtheory/common_graph_theory_problems.pdf and /dev/null differ diff --git a/slides/graphtheory/common_graph_theory_problems.pptx b/slides/graphtheory/common_graph_theory_problems.pptx deleted file mode 100644 index 49954c1c7..000000000 Binary files a/slides/graphtheory/common_graph_theory_problems.pptx and /dev/null differ diff --git a/slides/graphtheory/dag_shortest_and_longest_path.key b/slides/graphtheory/dag_shortest_and_longest_path.key deleted file mode 100644 index 4c3c49810..000000000 Binary files a/slides/graphtheory/dag_shortest_and_longest_path.key and /dev/null differ diff --git a/slides/graphtheory/dag_shortest_and_longest_path.pdf b/slides/graphtheory/dag_shortest_and_longest_path.pdf deleted file mode 100644 index 86fef889e..000000000 Binary files a/slides/graphtheory/dag_shortest_and_longest_path.pdf and /dev/null differ diff --git a/slides/graphtheory/dag_shortest_and_longest_path.pptx b/slides/graphtheory/dag_shortest_and_longest_path.pptx deleted file mode 100644 index 4196cc7be..000000000 Binary files a/slides/graphtheory/dag_shortest_and_longest_path.pptx and /dev/null differ diff --git a/slides/graphtheory/depth_first_search.key b/slides/graphtheory/depth_first_search.key deleted file mode 100644 index 0acd7af47..000000000 Binary files a/slides/graphtheory/depth_first_search.key and /dev/null differ diff --git a/slides/graphtheory/depth_first_search.pdf b/slides/graphtheory/depth_first_search.pdf deleted file mode 100644 index 85c1a33e7..000000000 Binary files a/slides/graphtheory/depth_first_search.pdf and /dev/null differ diff --git a/slides/graphtheory/depth_first_search.pptx b/slides/graphtheory/depth_first_search.pptx deleted file mode 100644 index 8313469ef..000000000 Binary files a/slides/graphtheory/depth_first_search.pptx and /dev/null differ diff --git a/slides/graphtheory/dijkstras_shortest_path.key b/slides/graphtheory/dijkstras_shortest_path.key deleted file mode 100644 index d65fc929b..000000000 Binary files a/slides/graphtheory/dijkstras_shortest_path.key and /dev/null differ diff --git a/slides/graphtheory/dijkstras_shortest_path.pdf b/slides/graphtheory/dijkstras_shortest_path.pdf deleted file mode 100644 index c2bc273c5..000000000 Binary files a/slides/graphtheory/dijkstras_shortest_path.pdf and /dev/null differ diff --git a/slides/graphtheory/eulerian_circuits_and_paths.key b/slides/graphtheory/eulerian_circuits_and_paths.key deleted file mode 100644 index d87c6c6be..000000000 Binary files a/slides/graphtheory/eulerian_circuits_and_paths.key and /dev/null differ diff --git a/slides/graphtheory/eulerian_circuits_and_paths.pdf b/slides/graphtheory/eulerian_circuits_and_paths.pdf deleted file mode 100644 index e27a8d050..000000000 Binary files a/slides/graphtheory/eulerian_circuits_and_paths.pdf and /dev/null differ diff --git a/slides/graphtheory/floyd_warshall.key b/slides/graphtheory/floyd_warshall.key deleted file mode 100644 index 2bffe15bf..000000000 Binary files a/slides/graphtheory/floyd_warshall.key and /dev/null differ diff --git a/slides/graphtheory/floyd_warshall.pdf b/slides/graphtheory/floyd_warshall.pdf deleted file mode 100644 index c51c442e6..000000000 Binary files a/slides/graphtheory/floyd_warshall.pdf and /dev/null differ diff --git a/slides/graphtheory/floyd_warshall.pptx b/slides/graphtheory/floyd_warshall.pptx deleted file mode 100644 index fc70df534..000000000 Binary files a/slides/graphtheory/floyd_warshall.pptx and /dev/null differ diff --git a/slides/graphtheory/graph_theory_algorithms.key b/slides/graphtheory/graph_theory_algorithms.key new file mode 100644 index 000000000..72368fe0e Binary files /dev/null and b/slides/graphtheory/graph_theory_algorithms.key differ diff --git a/slides/graphtheory/graph_theory_algorithms.pdf b/slides/graphtheory/graph_theory_algorithms.pdf new file mode 100644 index 000000000..8fc52422d Binary files /dev/null and b/slides/graphtheory/graph_theory_algorithms.pdf differ diff --git a/slides/graphtheory/graph_thoery_introduction.key b/slides/graphtheory/graph_thoery_introduction.key deleted file mode 100644 index 1c1da124b..000000000 Binary files a/slides/graphtheory/graph_thoery_introduction.key and /dev/null differ diff --git a/slides/graphtheory/graph_thoery_introduction.pdf b/slides/graphtheory/graph_thoery_introduction.pdf deleted file mode 100644 index 393c80a56..000000000 Binary files a/slides/graphtheory/graph_thoery_introduction.pdf and /dev/null differ diff --git a/slides/graphtheory/graph_thoery_introduction.pptx b/slides/graphtheory/graph_thoery_introduction.pptx deleted file mode 100644 index 4764c2f24..000000000 Binary files a/slides/graphtheory/graph_thoery_introduction.pptx and /dev/null differ diff --git a/slides/graphtheory/network_flow.key b/slides/graphtheory/network_flow_algorithms.key similarity index 69% rename from slides/graphtheory/network_flow.key rename to slides/graphtheory/network_flow_algorithms.key index 96623aa59..f31fc5027 100644 Binary files a/slides/graphtheory/network_flow.key and b/slides/graphtheory/network_flow_algorithms.key differ diff --git a/slides/graphtheory/network_flow_algorithms.pdf b/slides/graphtheory/network_flow_algorithms.pdf new file mode 100644 index 000000000..732efba1e Binary files /dev/null and b/slides/graphtheory/network_flow_algorithms.pdf differ diff --git a/slides/graphtheory/heavy_light_decomposition_micah_slides.key b/slides/graphtheory/other/heavy_light_decomposition_micah_slides.key similarity index 100% rename from slides/graphtheory/heavy_light_decomposition_micah_slides.key rename to slides/graphtheory/other/heavy_light_decomposition_micah_slides.key diff --git a/slides/graphtheory/heavy_light_decomposition_micah_slides.pdf b/slides/graphtheory/other/heavy_light_decomposition_micah_slides.pdf similarity index 100% rename from slides/graphtheory/heavy_light_decomposition_micah_slides.pdf rename to slides/graphtheory/other/heavy_light_decomposition_micah_slides.pdf diff --git a/slides/graphtheory/prims_mst.key b/slides/graphtheory/prims_mst.key deleted file mode 100755 index 7612dc4b0..000000000 Binary files a/slides/graphtheory/prims_mst.key and /dev/null differ diff --git a/slides/graphtheory/scripts/bellman_ford.txt b/slides/graphtheory/scripts/bellman_ford.txt new file mode 100644 index 000000000..99f049716 --- /dev/null +++ b/slides/graphtheory/scripts/bellman_ford.txt @@ -0,0 +1,114 @@ + +Of all the shortest path algorithms in graph theory, Bellman-Ford is definitively one of the simplest, yet I really struggled as an undergrad student trying to learn this algorithm, which is part of the reason I am making this video. + +So what is the BF algorithm? In short, it's a Single Source Shortest Path algorithm, this means that it can find the shortest path from a starting node to all other nodes in a graph. As you can imagine this is very useful. However, BF is not ideal for single source short path algorithms because it has a much worst Time complexity then Dykstra's algorithm. In practice, Bellman-ford runs in a time complexity proportional to the product of the number of edges times the number of vertices, while Dijkstra can do much better, at around big O of E plus V log V with a binary heap. + +So, when would we ever use the BF algorithm? The answer is when Dijkstra's fails, and this can happen when a graph has negative edge weights. When a graph has negative edge weights it's possible that a negative cycle can manifest itself, and when it does, it is of critical importance that we are able to detect it. If this happens and we're using Dijkstra's to find the shortest path we'll get stuck in an infinite loop because the algorithm will keep finding a better and better path. +A neat application of Bellman Ford and negative cycles is in finance and economics when performing an arbitrage between two or more markets. I'm not an expert, but this is when prices between different markets are such that you can cycle through each market with a security such as a stock or a currency and end up with more profit than you started with, essentially getting risk free gains. + +So let's look at how negative cycles can arise because that seems important. + +Here is a graph I made with directed edges, some of which are negative. I've labeled our starting node to be node 0 and our goal would be to find the distance from zero to every other node in a SSSP context, but we're only interested in negative cycles for now. I will label blue nodes as regular nodes, red nodes as nodes directly involved in a negative cycle and yellow nodes as those reachable from a negative cycle. + +One way negative cycles can emerge is through negative self loops. What happens is that once we reach a self loop we can stay in that loop for a near infinite amount of time before exiting, as a result everywhere reachable by the cycle has a best cost of negative infinity, which depending on your problem may either be good or bad. + +In this graph, nodes 2,3,4,5 are all reachable by node 1, so they all have a best cost of negative infinity with regards to the single source shortest path problem. + +Let's look at another example.. + +In this graph, a negative cycle manifests itself, but not as the result of a negative self loop, instead through a cycle of nodes whose net gain is less than zero. If you add up the edge values 1, 4 and -6 attached to nodes 1,2 and 3 the net change is -1. + +If we look at where this cycle can reach we see that the entire right side of the graph is affected, so hardly any nodes are safe from this cycle. + +Now let's look at the actual steps involved in the Bellman-Ford algorithm, first we'll need to define a few variables. +Let E be the number of edges in the graph, +Let V be the number of vertices, +Let S be the id of the starting node, S is short for start, +and lastly, let D be an array of size V that tracks the best distance from S to each node. + +The first thing we'll want to do is set every entry in D to positive infinity. This is because the distance to each node is initially infinity because we have no idea how far each node is. Next, we'll want to set the distance to the starting node to be zero because we're already there. +The last part of the algorithm is to relax each edge V-1 times. Relaxing an edge simply means taking an edge and trying to update the value from where the edge starts to where it ends. + +In terms of code, this is all we do. We loop V-1 times, then for each edge we relax the edge. In the relaxing step, what we do is we look at the value of where the edge starts, add the edge cost and see if that's better than where we're trying to go and if so update with the shorter path value. + +To actually detect negative cycles, we don't do anything special, all we do is run the algorithm a second time. What we're doing in this second pass is checking for any nodes that update to a better value than the known best value, and if they do then they're part of a negative cycle and we give that node a value of negative infinity. + +Let's look at a full example, here is a graph I made. Again, we will be start at node 0 and find the shortest path to every other node. On the right, I have illustrated the distance array D, watch the values in this array change as the algorithm executes. Right now, all values in the array are set to positive infinity as per the first step of the algorithm. + +In the second step, we set the starting node's value to 0. Now, the algorithm actually starts and we are on the first iteration where we attempt to relax each edge. Before we continue, I have an important note at the bottom of the screen which says that the edges do not need to be processed in any particular order. I may process the edges with one ordering and you process them with another ordering and we many not end up the same values on each iteration, but we will get the same end result in the distance array. + +I will highlight the edge currently being processed in orange and update the distance array whenever appropriate. Right now, the best value to node 1 is 5 because a distance of 5 is better than a distance of positive infinity. + +Then Node 2 gets its value updated to 25 because node 1 had a value of 5 from the last turn, and the edge from node 1 to node 2 is 20 making for a total of 25. + +Then node 5 has a similar thing happen to it + +and node 6 as well. + +BTW, an edge is dark gray if it has already been processed in this iteration. Next, node 3 gets its value updated from infinity to 35 because the best value in node 2 so far is 25 plus the edge cost of 10 is 35. + +Then, the edge from 2 to 4 updates to a best value of 100. + +Up next is an interesting edge because it was able to update node 2's best value from 25 to 20 by taking the value in node 3 which is currently 35 adding a weight of -15 and giving us a better value of 20. So this is all the algorithm does, it processes each edge performing relaxation operation, I'll let the animation play finishing of the rest this iteration. + +... + +So iteration one is over and there are 8 more to go, but for simplicity i'll just play one more iteration to give you an idea of how the algorithm works. + +We reset all the edges and start processing the edges again. You'll notice that a lot less updating happens in the distance array this round, particularly because I unintentionally selected the edges to be processed in a more or less optimal way. + +... + +So that's the end of the second iteration, if we fast forward to the end here's the resulting distance array. + +However, we're not done, we still need to find the negative cycles. Let's execute the algorithm a second time. + +Same procedure as usual, just relax each edge, but when we are able to relax an edge update that node's value to negative infinity instead. Let's process some edges until something interesting happens. + +... + +So it appears that when we went from node 2 to node 3 we were able to relax the edge and obtain a better value for node 3 then was there previously, so node 3 is part of some negative cycle so I will mark it red. + +Similarly, node 4 is connected to a negative, although indirectly. In the distance table I do not distinguish between nodes which are reachable by a negative cycle and those which are primarily involved in one, so there's no way to tell them apart, feel free to add some logic in the BF algorithm if you need to make the distinction. + +Continuing on, node 2 is also trapped in the cycle. + +And the last node also affected by the cycle is node 9 on the right. Now let's finish up with this iteration by processing the rest of the edges. + +... + +So that's it for the first iteration there are another 8 iterations to perform. In this example we happened to detect all cycles on the first iteration but this was a coincidence, in general you really need another 8 iterations. This is because you want the negative cycle minus infinity values to propagate throughout the graph, the propagation is highly dependent on the order in which the edges are being processed, but having V-1 iterations ensures that this propagation occurs correctly. + + +--------------------------------------------------------- + +if time_status is good: + + Alright time to finally see some source code, + you can find what i'm about to show you on github dot com + slash williamfiset slash algorithms, there should be a + link in the description below. + +else + + Thank you for watching, we'll cover the source code in the next + video. There will be a link to the code in the description for those who are curious. + + + + + + + + + + + + + + + + + + + diff --git a/slides/graphtheory/bfs_grid_shortest_path.txt b/slides/graphtheory/scripts/bfs_grid_shortest_path.txt similarity index 100% rename from slides/graphtheory/bfs_grid_shortest_path.txt rename to slides/graphtheory/scripts/bfs_grid_shortest_path.txt diff --git a/slides/graphtheory/breadth_first_search.txt b/slides/graphtheory/scripts/breadth_first_search.txt similarity index 100% rename from slides/graphtheory/breadth_first_search.txt rename to slides/graphtheory/scripts/breadth_first_search.txt diff --git a/slides/graphtheory/bridges_and_articulation_points.txt b/slides/graphtheory/scripts/bridges_and_articulation_points.txt similarity index 100% rename from slides/graphtheory/bridges_and_articulation_points.txt rename to slides/graphtheory/scripts/bridges_and_articulation_points.txt diff --git a/slides/graphtheory/common_graph_theory_problems.txt b/slides/graphtheory/scripts/common_graph_theory_problems.txt similarity index 100% rename from slides/graphtheory/common_graph_theory_problems.txt rename to slides/graphtheory/scripts/common_graph_theory_problems.txt diff --git a/slides/graphtheory/dag_shortest_and_longest_path.key.txt b/slides/graphtheory/scripts/dag_shortest_and_longest_path.key.txt similarity index 100% rename from slides/graphtheory/dag_shortest_and_longest_path.key.txt rename to slides/graphtheory/scripts/dag_shortest_and_longest_path.key.txt diff --git a/slides/graphtheory/depth_first_search.txt b/slides/graphtheory/scripts/depth_first_search.txt similarity index 100% rename from slides/graphtheory/depth_first_search.txt rename to slides/graphtheory/scripts/depth_first_search.txt diff --git a/slides/graphtheory/dijkstras_shortest_path.txt b/slides/graphtheory/scripts/dijkstras_shortest_path.txt similarity index 100% rename from slides/graphtheory/dijkstras_shortest_path.txt rename to slides/graphtheory/scripts/dijkstras_shortest_path.txt diff --git a/slides/graphtheory/eulerian_circuits_and_paths.txt b/slides/graphtheory/scripts/eulerian_circuits_and_paths.txt similarity index 100% rename from slides/graphtheory/eulerian_circuits_and_paths.txt rename to slides/graphtheory/scripts/eulerian_circuits_and_paths.txt diff --git a/slides/graphtheory/floyd_warshall.txt b/slides/graphtheory/scripts/floyd_warshall.txt similarity index 100% rename from slides/graphtheory/floyd_warshall.txt rename to slides/graphtheory/scripts/floyd_warshall.txt diff --git a/slides/graphtheory/graph_theory_introduction.txt b/slides/graphtheory/scripts/graph_theory_introduction.txt similarity index 100% rename from slides/graphtheory/graph_theory_introduction.txt rename to slides/graphtheory/scripts/graph_theory_introduction.txt diff --git a/slides/graphtheory/scripts/lowest_common_ancestor_code.txt b/slides/graphtheory/scripts/lowest_common_ancestor_code.txt new file mode 100644 index 000000000..1f44b8ee2 --- /dev/null +++ b/slides/graphtheory/scripts/lowest_common_ancestor_code.txt @@ -0,0 +1,105 @@ + +Alright, here we are in the source code for the lowest common ancestor written +in Java. + +Let's begin by going over an example of how this class is intended to be used. + +In the main method I give three examples of how to do lowest common ancestor queries. +The first thing we need to do is actually build a rooted tree, in this example I'm building the first tree from the previous videos slides. + +After you've created the tree you can pass the tree into the lowest common ancestor Euler tour constructor. At the moment, each instance of this class is only meant to handle one tree, so if you want to do lowest common ancestor queries on multiple trees you will need multiple solvers. + +After the solver is created you can use it to do lowest common ancestor queries. In the following example I do three queries to find: the LCA of 13 and 14, the LCA of 9 and 11 and the LCA of 12 and 12. + +Let's scroll down and look at some implementation details more closely. + + +The first class I want to look at is the TreeNode class, you'll notice that this +class is mostly the same as before except that it has a new variable `n` which +tracks the number of nodes in the subtree of this treenode (including the node +itself). A TreeNode also has an index and a list of children. + +Most of the methods in this class are accessor methods, the only thing we need +to look at are some minor changes I made to the buildTree method since the +tree rooting video. + + + +In the buildTree method you'll notice that I am now counting the number of nodes +in the subtree of the current node. For out purposes, this effectively serves +as a nice way to know how many nodes are in the tree from the root node. + + + +Moving on, I want to talk about some of the instance variables in this class. + +The first is `n`, the number of nodes in the tree. + +which is followed by the `tourIndex` variable which tracks the index of where +we are in the Eulerian tour as we're traversing the tree. + +The nodeDepth and nodeOrder arrays are populated during the Eulerian tour, they +track the depth of each node and the pointer to each node for each index in the +Eulerian tour. + +The last map helps keep track of the last occurrence of a node in the +Eulerian tour. + +And finally, is the min sparse table class to do range minimum queries. + + + +When creating an instance of an LCA solver you need to provide the root node of +the tree you're doing LCA queries for. + +From the root node, assign n to be the number of nodes in the tree and call the +setup function. + +The set up function is responsible for allocating memory and constructing with +the Eulerian tour. Begin by allocating memory for our three arrays: `node order`, +`nodedepth` and `last`. + +Then do a depth first search on the tree to construct the Eulerian tour. + +After this, create a sparse table using the `node depth` array that was populated during the depth first search phase. + + + +The depth first search method is the one that actually performs the Eulerian tour. As parameters it tracks the current node and the node depth. + +When the current node becomes null we know that we have reached our base case +and can return. + +Otherwise visit the current node and iterate over it all its children recursively. + +The inner visit function call ensures that after visiting a sub tree, that we revisit the current node. This is essential to get the desired Eulerian tour effect and traverse the tree as expected. + +The visit function itself is responsible for populating the arrays associated with the Eulerian tour. + +In particular, this function will update the `nodes` array to keep track of a pointer to the current node, it will also update the `depth` array to track the current depth and it will also update the inverse mapping to the eulerian tour index. + +Next is the LCA method, which find the lowest common ancestor of two nodes with the indices `index one` and `index two` + +The first thing we want to do is look inside our last map, and find the indices in the Eulerian tour associated with `index1` and `index2`. From these values, we can extract a left and a right endpoint. To make sure that the left endpoint always appears before the right endpoint, take the min and max of the indices. + +After we know the left and the right endpoints, simply do a range minimum query to find the index of the minimum element in the range `l` to `r` inclusive using the sparse table we created in the setup method. + +And lastly, return the tree node object for the lowest common ancestor found in the nodes array. + + + +Below is an implementation of a Min sparse table, but I'm not going to cover that +in this video since I have a dedicated video on sparse tables in my data structure series. I'll make sure to supply a link to that. + + +Awesome, well that's all for this video, I hope you learned something, please like this video, and subscribe for more mathematics and computer science content thank you + + + + + + + + + + diff --git a/slides/graphtheory/network_flow_bipartite_unweighted_matching.txt b/slides/graphtheory/scripts/network_flow_bipartite_unweighted_matching.txt similarity index 100% rename from slides/graphtheory/network_flow_bipartite_unweighted_matching.txt rename to slides/graphtheory/scripts/network_flow_bipartite_unweighted_matching.txt diff --git a/slides/graphtheory/network_flow_capacity_scaling.txt b/slides/graphtheory/scripts/network_flow_capacity_scaling.txt similarity index 100% rename from slides/graphtheory/network_flow_capacity_scaling.txt rename to slides/graphtheory/scripts/network_flow_capacity_scaling.txt diff --git a/slides/graphtheory/network_flow_capacity_scaling_source_code.txt b/slides/graphtheory/scripts/network_flow_capacity_scaling_source_code.txt similarity index 100% rename from slides/graphtheory/network_flow_capacity_scaling_source_code.txt rename to slides/graphtheory/scripts/network_flow_capacity_scaling_source_code.txt diff --git a/slides/graphtheory/network_flow_dinics.txt b/slides/graphtheory/scripts/network_flow_dinics.txt similarity index 100% rename from slides/graphtheory/network_flow_dinics.txt rename to slides/graphtheory/scripts/network_flow_dinics.txt diff --git a/slides/graphtheory/network_flow_dinics_source_code.txt b/slides/graphtheory/scripts/network_flow_dinics_source_code.txt similarity index 100% rename from slides/graphtheory/network_flow_dinics_source_code.txt rename to slides/graphtheory/scripts/network_flow_dinics_source_code.txt diff --git a/slides/graphtheory/network_flow_edmonds_karp.txt b/slides/graphtheory/scripts/network_flow_edmonds_karp.txt similarity index 100% rename from slides/graphtheory/network_flow_edmonds_karp.txt rename to slides/graphtheory/scripts/network_flow_edmonds_karp.txt diff --git a/slides/graphtheory/network_flow_edmonds_karp_source_code_notes.txt b/slides/graphtheory/scripts/network_flow_edmonds_karp_source_code_notes.txt similarity index 100% rename from slides/graphtheory/network_flow_edmonds_karp_source_code_notes.txt rename to slides/graphtheory/scripts/network_flow_edmonds_karp_source_code_notes.txt diff --git a/slides/graphtheory/network_flow_elementary_math.txt b/slides/graphtheory/scripts/network_flow_elementary_math.txt similarity index 100% rename from slides/graphtheory/network_flow_elementary_math.txt rename to slides/graphtheory/scripts/network_flow_elementary_math.txt diff --git a/slides/graphtheory/network_flow_max_flow.txt b/slides/graphtheory/scripts/network_flow_max_flow.txt similarity index 100% rename from slides/graphtheory/network_flow_max_flow.txt rename to slides/graphtheory/scripts/network_flow_max_flow.txt diff --git a/slides/graphtheory/network_flow_mice_and_owls.txt b/slides/graphtheory/scripts/network_flow_mice_and_owls.txt similarity index 100% rename from slides/graphtheory/network_flow_mice_and_owls.txt rename to slides/graphtheory/scripts/network_flow_mice_and_owls.txt diff --git a/slides/graphtheory/prims_mst_script.txt b/slides/graphtheory/scripts/prims_mst_script.txt similarity index 99% rename from slides/graphtheory/prims_mst_script.txt rename to slides/graphtheory/scripts/prims_mst_script.txt index c31bdf417..2aa6ce2ac 100644 --- a/slides/graphtheory/prims_mst_script.txt +++ b/slides/graphtheory/scripts/prims_mst_script.txt @@ -509,7 +509,7 @@ Now let's look at the actual algorithm for eager Prim's. @173. In this first block I define a few more variables we'll need: 'm' the number of expected edges in the MST. -'edgeCount' the number of edges we have currently included in the MST. This +'edgeCount' the number of edges we have currently have included in the MST. This variable is used to make sure the tree spans the whole graph. 'mstCost' tracks the total cost of the MST and finally, 'mstEdges' is an array which holds edges which we have included in @@ -541,7 +541,7 @@ this is the node the edge is pointing at. Next, skip edges which point at already visited nodes. @180. -Now here's the bit where we actually relax the edge. First I check if the IPQ +Now here's the bit where we actually relax the edge. First, check if the IPQ contains the key with the value of the destination node. @181. diff --git a/slides/graphtheory/promo_script.txt b/slides/graphtheory/scripts/promo_script.txt similarity index 100% rename from slides/graphtheory/promo_script.txt rename to slides/graphtheory/scripts/promo_script.txt diff --git a/slides/graphtheory/tarjans_scc.txt b/slides/graphtheory/scripts/tarjans_scc.txt similarity index 73% rename from slides/graphtheory/tarjans_scc.txt rename to slides/graphtheory/scripts/tarjans_scc.txt index b48f3b3a1..b3adec401 100644 --- a/slides/graphtheory/tarjans_scc.txt +++ b/slides/graphtheory/scripts/tarjans_scc.txt @@ -1,27 +1,32 @@ -1) Hello and welcome back, my name is William, today I want to talk about the fascinating topic of Strongly Connected Components and how we're going to use Tarjan's algorithm to find them. +Hello and welcome back, my name is William, today I want to talk about the fascinating topic of Strongly Connected Components, and how we can find them using Tarjan's algorithm. -2) So what are SCCs or Strongly Connected Components? I like to think of them as self contained cycles within a directed graph. Where for every vertex in a given cycle you can reach every other vertex in the same cycle. For example, in the graph below there are four strongly connected components. +So what are SCCs or Strongly Connected Components? I like to think of them as self contained cycles within a directed graph. Where for every vertex in a given cycle you can reach every other vertex in the same cycle. -3) I've outlined them here in different colors. If you inspect each SCC you'll notice that each has it's own self contained cycle and that for each component there's no way to find a path that leaves a component and comes back. Because of that property we can be sure that SCCs are unique within a directed graph. +For example, in the graph below, there are four strongly connected components. I've outlined them here in different colors. If you inspect each SCC you'll notice that each has it's own self contained cycle, and that for each component, there's no way to find a path that leaves a component and comes back. Because of this property, we can be sure that SCCs are unique within a directed graph. -4) To understand Tarjan's SCC algorithm we're going to need to understand the concept of a low-link value. Simply put, a low-link value is the smallest node id reachable from that node including itself. For that to make sense, we're going to need to label each of the nodes in our graph by doing a DFS. +4) To understand Tarjan's SCC algorithm, we're first going to need to understand the concept of a low-link value. Simply put, the low-link value of a particular node is the smallest node id reachable from that node, including the id of the node itself. For that to make sense, I'm going to label each of the nodes in our graph by doing a DFS. -5) Suppose we start at the top left corner and label that node with the id 0. +5) Suppose we start at the top left corner, and label that node with an id of 0. -6) Now we continue exploring our graph until we visit all the edges and labeled all nodes. +6) Now, let's explore the rest of the graph, and assign ids to all our nodes. I will let the animation play, you try and follow along ... -15) [Back to blue graph] Alright now that we're done labeling the nodes inspect the graph and try and determine the low-link value for each node. Again, the low-link value of a node is the smallest [lowest] node id reachable from that node including itself. For example the low-link value of node 1 should be 0 since node 0 is reachable from node 1 via a series of edges. Similarly, node 4's low-link value should be 3 since node 3 is the lowest node that is reachable from node 4. +15) [Back to blue graph] Alright, now that we're done labeling the nodes, inspect the graph and try and determine the low-link value for each node. Again, the low-link value of a node is the smallest node id reachable from that node including itself. -16) So if we assign all the low-link values we get the following setup. From this view you realize that all nodes which have the same low-link value belong to the same strongly connected component. +For example, the low-link value of node 1 is 0, since node 0 is the node with the lowest id reachable from node 1 -17) If I now assign colors to each SCC we can clearly see that for each component all the low-link values are the same. This seems too easy right? Well, you're not wrong, there is a catch. The flaw with this technique is that it is highly dependent on the traversal order of the DFS which is for our purposes is random. +Similarly, the low-link of node 3 is 2, since node 2 is node with the lowest id reachable from node 3 -18) For instance, in this same graph I rearranged the node ids as though the DFS started in the top right corner and made its way down and then across. In such an event, the low-link values will be incorrect. +So if we assign all low-link values to all the nodes, we get the following setup. From this view, you realize that all nodes which have the same low-link value belong to the same strongly connected component. -19) In this specific case all the low-link values are the same but there clearly are multiple SCCs. What's going on? Well what's happening is that the low-link values are highly dependent of the order in which the nodes are explored in our DFS so we might NOT end up with the correct arrangement of node ids for our low-link values to tell us which nodes are in which SCC. -This is where Tarjan's algorithm kicks in which its stack invariant to prevent SCCs from interfering with each others' low-link values. +If I assign colors to each SCC, you can clearly see that for each component, all the low-link values are the same. This seems too easy right? Well, you're not wrong, there is a catch. The flaw with this technique is that it is highly dependent on the traversal order of the "DFS", which is effectively random. Let me show you a counterexample + +Suppose we take the same graph, and rearrange the node ids as though the "DFS" started at 0, went to node 1, got stuck at the component with node 1, continued on node 2, explored 3, got stuck again and resumed at node 4, went to node 5 and finished at 6. + +You'll notice that this time, node 6 in the new graph has a low link value of 0, which should indicate that node 6 is somehow part of node 0's strongly connected component, which we know is not the case. + +However, that's not the only issue, the real issue is that all the low-link values are the same, but there are clearly multiple SCCs in the graph. What's going on? Well, what's happening is that the low-link values are highly dependent on the order in which the nodes are explored during our "DFS", so we might NOT end up with the correct arrangement of node ids for our low-link values to tell us which nodes are in which SCC. This is where Tarjan's algorithm kicks in, Tarjan's maintains an invariant to prevent the low link values of multiple SCCs from interfering with each other. 20) So to cope with the random traversal order of the DFS, Tarjan’s algorithm maintains a set (often as a stack) of valid nodes from which to update low-link values from. How the stack works is that nodes are added to the stack as nodes are explored for the first time; and nodes are removed from the stack each time a SCC is found. diff --git a/slides/graphtheory/scripts/topsort.txt b/slides/graphtheory/scripts/topsort.txt new file mode 100644 index 000000000..b886a90b7 --- /dev/null +++ b/slides/graphtheory/scripts/topsort.txt @@ -0,0 +1,130 @@ + + +*Play 2 sec intro sound* + +Welcome back! Today's topic is topological sort also called topsort for short. We're going to dicuss what is topsort, where it's used and how to find a topological ordering with some animation. + +Let's begin with an example, suppose you're a university student and you really want to take class H. + +Well before you can enroll in class H you must first take classes D and E. + +But before taking class D you must take classes A and B which have no prerequisites. + +So in a sense, there appears to be some sort of ordering on the nodes of the graph. If we needed to take all these classes Topsort is an algorithm which would be capable of telling us the order in which we should enroll in courses such that we never enroll in a course which we do not have the prerequisites for. + +Another canonical example of an application of topsort is for program build dependencies. A program cannot be built unless all its dependencies are first built. + +For example, consider this graph where each node represents a program and the edges represent that one program depends on another to run. Well if we're trying to build program J on the right then we must first build program H and G, but to build those we need E and F, but to build those we need, and so on... So the idea is to first build the programs without any dependencies and move onwards from there. How do we find a valid ordering in which to build the programs? Well this is when topsort comes into play. One possible ordering might be: + +... + +Notice that there are unused dependencies in this case, and that will happen from time to time. + +In conclusion, topsort is an algorithm which will give us a topological ordering on a directed graph. A topological ordering is an ordering of nodes where for each edge from node A to node B, node A appears before node B in the ordering. If it helps, this is a fancy way of saying that we can align all the nodes in line and have all the edges pointing to the right. An important note to make about topological orderings is that they are not unique. As you can imagine there are multiple valid ways to enroll in courses and still graduate, or compile your program and its dependencies in a different order than you previously did. + + +Sadly not every type of graph can have a topological ordering. For example, any graph which contains a directed cycle cannot have a valid ordering. + +Think of why this might be true, there cannot be an order if there is a cyclic dependency since there is no where to start. Every node in the cycle depends on another. So any graph with a directed cycle is forbidden. + +The only graphs that have valid topological orderings are Directed Acyclic Graphs, that is graphs with directed edges and no cycles. + +A natural question to ask is how do I verify that my graph does not contain a directed cycle? One method is to use Tarjan's strongly connected component algorithm which can be used to find these cycles. + +Another neat thing definitely worth mentioning is that every tree has a topological ordering since by definition trees do no contain any cycles! An easy way to find a topological ordering with trees is to iteratively pick off the leaf nodes. It's like you're cherry picking from the bottom, it doesn't matter the order you do it. Once the root of a subtree has all grayed out children it them becomes available. +This procedure continues until no more nodes are left... + +... + +So we know how it works for trees, how about general directed acyclic graphs? Well the algorithm is also simple. Just repeat the following steps: +1) First find an unvisited node, it doesn't matter which. +2) From this node do a Depth First Search exploring only reachable unvisited nodes. +3) On recursive callbacks add the current node to the topological ordering in reverse order. +Let's do an example, things will become much clearer. + +Here's a directed acyclic graph we want to find one of many topological orderings for. As the algorithm executes i'll be keeping track of the call stack on the left hand side in case you're curious as well as the topological ordering at the bottom of the screen, ok so let's get going. The first step is to pick an unvisited node, i'm going to pick node H. + +Now we do a DFS outwards from H in all possible directions exploring what we can. + +I'm going to go towards J + + + +Now from J i'm going to keep exploring + + + +Let's go to M. + + + +Not at M there's no where to go, so we backtrack and add M as the last element in the topological order. + +Still at J we need to go towards L. + + + + + +Backtrack at L, nowhere left to go. + +Also backtrack at J and add it to the ordering. Notice that stack frames are being popped off the call stack when I recurse. + +Now we're back at H and we still need to visit node I. + + + + + +From node I we try and visit L + +but it turns out L is always visited, so we recurse. + +Add I to the ordering + +And finally H. As you saw, selecting a random unvisited node made us visit a subsection of the graph, we continue this process until all nodes are visited. + +The next node I will chose is node E. In the interest of time and simplicity, I will let the animation play and you can follow along. Note that if you try and predict the next few values you may not get the same values as me since the ordering is not unique, but this does not mean you're incorrect! + +... + +The next node I will chose is node C. + +... + +So that's one possible topological ordering, now let's look at some code! + +Here's some pseudo code for topsort. Let's walk through it real quick. + +1) The first thing I do is I get the number of nodes from the graph which I assume is passed into the function as an adjacency list. + +2) Then I declare an array called V, short for visited which tracks whether a node has been visited or not. + +3) The next array called 'ordering' is the result we'll be returning in this function. + +4) Associated with ordering is the index i which tracks the insertion position of the next element in our topological ordering. As you've been seeing in the slides we insert elements backwards which is why i starts at position N - 1. + +5) Next we're ready to enter a for loop to iterate over all the nodes in our graph. The loop variable called 'at' tracks the id of the nodes we're currently processing. + +6) I then check if we're on an unvisited node, because those are the only ones we care about. + +7) Then I start the depth first search. Notice that before I do I initialize an array called visitedNodes which I pass into the method to track which nodes were visited, and after the method is done I add the found nodes to the ordering. + +Now let's look at the depth first search method. + + +The DFS method is very simple, all I do is mark the node we're currently at to be visited, then for each edge going outwards from the node we're at I make sure the destination node is unvisited and then call the method again but on that destination node. On the call back when the method returns, that is when we're stuck and need to backtrack I add the current node to the visited nodes array. + + +Back to the topsort method. Now that we understand how the topsort algorithm works there's a neat optimization we can do to improve performance in terms of both time and space. + +Notice that every time the inner if statement block gets executed we allocate memory for an array, that array gets filled and then we iterate through the found nodes to place them inside the ordering array. How about we just directly insert found nodes inside the orderings array instead of doing all this additional work? + +Well that's exactly what we're going to do. I got rid of the unnecessary array and modified the DFS method to return the next valid insertion position in the orderings array. Now we need to pass in index i and the orderings array so it can be filled directly inside the DFS method. + +Inside the new DFS method one thing that changed is that now we have a return value and we're passing in some additional variables. Notice that instead of adding the current node to the visitedNodes array as we were doing before now we simply insert the node we are currently at directly inside the orderings array. The last thing to do is to return i-1 because the next insertion position is no longer at index i, but i - 1. + +As always there's some source code for the topological sort algorithm which can be found on my github account at github dot com slash william fiset slash algorithms. Thank you very much for watching, if you have any questions just leave a comment, please like this video and subscribe to my channel for more computer science and mathematics videos. See you next time. + +*Play multi second outro sound* + diff --git a/slides/graphtheory/travelling_salesman_problem.txt b/slides/graphtheory/scripts/travelling_salesman_problem.txt similarity index 100% rename from slides/graphtheory/travelling_salesman_problem.txt rename to slides/graphtheory/scripts/travelling_salesman_problem.txt diff --git a/slides/graphtheory/scripts/trees.txt b/slides/graphtheory/scripts/trees.txt new file mode 100644 index 000000000..1699496a9 --- /dev/null +++ b/slides/graphtheory/scripts/trees.txt @@ -0,0 +1,966 @@ + +Hello and welcome back, my name is William, and in today's video we're going to +start tackling the topic of trees. In particular, we're going to look at what +trees are and how we computationally store and represent trees. + +Conceptually it's fair to say most people know what I mean when I say I'm +working with a tree, or that something is structured as a tree. Below are four +graphs, but one of them is not a tree, do you know which one? + +Only the last graph is not a tree, but why, why is it not a tree? + +That is because we define a tree as being an undirected graph with no cycles, +and that's the key thing to remember -- trees cannot have cycles. You can see +that the rightmost graph has a cycle, and is therefore not a tree. However, +there's an even easier way to check whether a graph is a tree or not. + +Each tree has exactly n nodes and n-1 edges. If we count up all the nodes and +edges of each graph you can see that all the trees have one less edge than the +number of nodes, except for the rightmost graph which is not a tree. + +We now know what trees are, but where do they appear in computer science and in +the real world? Here are a few examples where you might encounter a tree +structure: + + First is your computer's file system which consists of directories, + subdirectories and files which is inherently a tree. + + Another place you see trees are in social hierarchies where you often see + CEO's, kings, priests and generals at the top; and interns, servants, children + and the lower class at the bottom. + + Trees are used to decompose source code and mathematical expressions into what + are called abstract syntax trees for easy evaluation. For example, the math + expression you see on this slides can be broken down into a tree structure. + + Every webpage you visit can be throught of as a tree due to HTML's nested tag + structure. This structure is used to tell your browser how to easily render + the page and how it should be displayed. + + Another large application of trees is in game theory to model decisions and + courses of action. On this slide is the famous prisoner's dilemma problem and + it's four different outcomes for whether each prisoner chooses to confess or + defect. + + There are many many more applications of trees in computer science and in the + real world, but we're not going to cover them all today. However, it's worth + mentioning that in computer science, the place you'll most often encounter + trees is as part of data structures, many of which are listed on this slide. + +Now we need to talk about how we actually store and represent these undirected +trees. + +First, you should label the nodes of your tree by indexing them from 0 to n non- +inclusive like the tree on the left of this slide. + +A simple way to store a tree is as an edge list, which is simply a list of +undirected edges indicating which two nodes have an edge between them. The great +thing about this representation is that it's super fast to iterate over and +cheap to store. + +The downside however, is that storing your tree as a list lacks the structure to +efficiently query all the neighbors of a node. + +This is why the adjacency list is usually a more popular representation for +storing an undirected tree. In this representation, you store a mapping between +a node and all its neighbors. + +For example, node 4 has the neighbors 1, 5 and 8 so in the adjacency list, node +4 maps to the list containing 1, 5 and 8 respectively. + +You could also store a tree as an adjacency matrix of size n by n where having a +1 in a particular cell means that the nodes which corresponding to the +row/column values have an edge between them. + +However, in practice I would say to always avoid storing a tree as an adjacency +matrix because it's a huge waste of space. You would not ever want to allocate n +squared memory and only use roughly 2n of the matrix cells, it just doesn't make +sense. + + +Alright, I can't keep talking about trees without mentioning rooted trees which +are trees with a designated root node. I have highlighted the root node in +orange, and most rooted trees as you'll notice have directed edges which point +away from the root node, however it's also possible to have edges which point +towards the root node, but those trees are much rarer from my experience. +Generally speaking, rooted trees are far easier to work with than undirected +trees because they have this well defined structure that allows for easy +recursive algorithm implementations. + +Related to rooted trees are binary trees which are trees for which every node +has at most two child nodes. The first two trees on this slide are binary trees, +but the last one is not because it has a node with more than 2 child nodes. You +don't often see binary trees manifest themselves in the real world, for the most +part binary trees are artificially created and integrated as part of data +structures to guarantee efficient insertions, removals and access to data. + +Now related to binary trees are binary search trees which are trees which +satisfy the BST invariant. The BST invariant states that for every node x the +values in the left subtree are less than or equal to x and that the values in +the right subtree are greater than or equal to x. This nice little property +enables you to quickly search through a tree and retrieve the values you want, +which is particularly handy. All the trees on this slide are BSTs except for the +last one, because 1 is *not* greater than or equal to 3. + +It's often useful to require uniqueness on the values of your binary search tree +so that you don't end up with duplicate values. To resolve this issue of +duplicate values you can change the invariant to be strictly less than rather +than less than or equal to. + +Now let's talk about how to store these rooted trees. Rooted trees are naturally +defined recursively in a top down manner. + +In practice, you always maintain a pointer reference to the root node so that +you can access the tree and its contents. + +Then each node also has access to a list of all its children -- which are also +called "child nodes". In this slides, the orange node is the current node we +have a reference to, and the purple nodes are all its children. All the bottom +or leaf nodes don't have any children. + +It's also sometimes useful to also maintain a pointer to a node’s parent node in +case you need to traverse up the tree. This effectively makes the edges in the +tree bidirectional. Again, if the current node is the orange node, than the pink +node in the case in the parent node of the orange node. + +However, maintaining an explicit reference to the parent node isn’t usually +necessary because you can access a node’s parent on a recursive function's +callback as you pop frames off the stack. + + +Another really neat way of storing a rooted tree if it is a binary tree is in a +flattened array. + +In the flattened array representation, each node has an assigned index position +based on where it is in the tree. The thing to understand here is that the tree +is actually an array, the diagrams are just a visual representation of what the +tree looks like.For instance, the node with value 5 in orange is associated with +the index 4 in the array. + +Similarly, this node with a value of 2 has an index of 6. + +Even nodes which aren't currently present have an index because they can be +mapped back to a unique position in the "index tree" as I call it. + +In this format, the root node is always at index 0 in the array, so you always +know where your starting point is. Another advantage of this format is that the +child nodes of node i can be access relative to position i. + +For example, if we're at position 2 in the array, we know that the left and +right children of the node at index 2 is given by 2 times i plus 1 and 2 times i +plus 2. Therefore the children of the node at index 2 can be found at positions +5 and 6. Reciprocally, this means if we have a node we know what the index of +the parent node should be, which is also very useful. + +Alright, that's all I have for this video, thank you for watching, please like +and subscribe and I'll see you in the next one. + + + + + + + +Hello and welcome, my name is William, and in today's video we're going to look +at some simple tree algorithms. This video is aimed at all you beginner's just +starting out who are still learning how to write code and especially recursive +code. We're going to have a look at two problems today, and these are the types +of problems which you might encounter as warm up questions in a job interview. + + Alright, let's start with our first problem. For this problem we +need to find the sum of all the leaf node values in a tree. + + For instance, given this tree we want to sum up all the bottom nodes. + + That would be all these nodes in red for a total of 9. If you're keen, you + pause this video and give this problem a try. + + + + Like all rooted tree problems we start with a reference to the root node. To + solve this problem all we need to do is perform a tree traversal and sum up + the value of the leaf nodes while we do the traversal. Two popular traversal + algorithms are doing a Breadth First Search and Depth First Search. On trees, + the preferred traversal method is typically a DFS because it lends itself to + be easily implemented recursively. It's pretty simple, watch how the + animation does it. You start at the root and plunge down the tree depth first. + + + + At the end when we sum up all the values, +and we can see that the sum of the leaf nodes is 9. Now let's have a look at +some pseudo-code for how this is implemented. + + The algorithm is quite short, but it may look strange at first if you haven't + seen much recursive code. + + We call the leafSum function by passing in a reference to the root node as a + starting point. + + The first thing we do is handle the special case where the tree is empty and + return zero in such a situation. + + next we check if the current node is a leaf node, and if it is we return the + value stored in the leaf node. + + The isLeaf method checks if a node is a leaf node by counting all its + children. If the number of child nodes is zero then we know the node is a leaf + node. + + If the node is not a leaf node then we iterate over all the children and call + the leafSum method recursively summing over the return values. This ensures + that we traverse the entire tree and properly accumulate values. + + Finally, once we have finished computing the sum for this node and its subtree + return the total. + + And that's all for summing up the leaf node values. + + Our second problem today is a classic problem in computer science +which is to find the height of a binary tree. The height of a tree is defined as +the number of edges from the root to the lowest leaf. Here the leftmost tree has +a height of zero because it has no edges, the middle tree has a height of 1, and +the rightmost tree has a height of 3 because the longest path from the root to +the lowest leaf is 3. + + To solve this problem we're going to break it down and define a new function + "h of x" which returns the height of the subtree rooted at node x. This new + function allows us to start thinking not only about the height of the tree as + a whole but also the height of the subtrees within our tree which are going to + help us find the overall height. + + For example, on this slide "h of a" has a value of 3, but "h of b" has a value + of 2 and "h of e" has a value of zero. + + By themselves, leaf nodes such as node e don't have children, so they don’t + add any additional height to the tree. So we can conclude that as a base case, + the height of any leaf node should be zero. + + Now, assuming node x is not a leaf node, we're able to formulate a recurrence + relation for the height, which is that the height of the subtree rooted at + node x is the maximum of the height of x's left and right subtrees plus one. + + Let's have a closer look at how this works with an example. Suppose we have + this tree and we want to compute its height. + + So we start at the root + + and then we start traversing down the tree depth first + + When we encounter a leaf node, we give it a height of zero and return + + We can't compute the height of a node until we know the height of all its + children, so we visit the right node. + + The right node is also a leaf node so it gets a height of zero + + On the callback we have visited both children of the current node so we take + the maximum heights of the left and the right children and add 1 for a total + of 1. + + We just finished exploring the right half of the tree, now let's finish the + left side. It doesn't matter which side you do first as long as you explore + the whole tree while doing your DFS. + + + + We found another leaf node so it gets a height of zero + + + + + + leaf node + + + + and another leaf node + + take the max and add 1 + + take the max and add 1 again + + finally compute the height of the final node by taking the max and adding 1 + + And there you have it, how the find the height of a tree. Let's have a look at + some pseudocode, shall we. + + + + This is the treeHeight function, and it is responsible for computing the + height of the tree. You would start by calling this function by passing in the + tree's root node like we did for the leafSum function. + + The first thing we do is handle the empty tree case. The height of an empty + tree is undefined so return -1. + + Next we check whether the current node is a leaf node by checking if both its + left and right child are null and return zero. If either of them are not null + then we know this node has more children and we need to keep digging down the + tree. + + In the event we haven't found a leaf node, return the maximum height of the + left subtree and the right subtree plus 1. + + I want to take a moment and go back to the previous statement and remark on a + simplification we can exploit. What do you reckon happens if we remove + checking whether a node is a leaf node or not, do you think the algorithm + would still behave correctly? Pause the video and think this over. + + Oddly enough, removing the leaf node check still makes the algorithm work + correctly. This works because the base case has adopted a new meaning, instead + of the base case checking for leaf nodes, it's now checking whether a node is + null and returning -1. Returning -1 now not only checks for the empty tree + case, but it also helps in correcting for the right tree height. + + Let's see what I mean by correcting for the right height. If our new base case + is now checking for null nodes instead of the leaf nodes, then our tree one + unit taller, so we need to correct for this. + + For the sake of being thorough, let's see how the height is calculated with + this new base case. + + + + + + + + + + Once we reach a null node, return -1 + + On the recursive callback remember that we always add +1 with our recurrence. + + + + + + + + So when we add up the counts, you see that we get the correct height of 3. + + And that's how you compute the height of a tree. In practice, if you're +designing a data structure where tree height is important you can dynamically +keep track of the height as you create the tree instead of computing it fully +like we're doing here, but of course, that it's always doable. + +Alright folks, thanks for watching, please like and subscribe if you learned +something and i'll catch you next time. + +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- + +ROOTING A TREE + +Hello and welcome for more tree videos, my name is William, and +today we're looking at how to root a +tree. This is one of those a very basic fundemental transformations +that's handy to have in your toolkit +in case you want to or need to work with a rooted tree. + +The motivation for rooting a tree is that often it can help to add structure and +simplify the problem you're trying to solve. Rooting a tree enables you to +easily perform recursive algorithms, it also transforms a tree to have +directed edges instead of undirected edges which are generally easier to +work with. + +To root a tree, first you need to select one of the tree's nodes to be the +root node. I'm going to pick node 0. + +Conceptually, rooting a tree is like "picking up" a tree by a specific node +and having all the edges point downwards. + +You can root a tree using any of its nodes. However, be cautious because not +every node you select will result in a well balanced tree, and if that's your +objective you may need to be more selective. + +In some situations it’s also useful to keep have a reference to the parent node +in order to be able to walk up the tree. I illustrated parent node pointers as +dotted lines on this slide. + +Let's look at an example of how to root a tree. One of the best ways to do this +is with a depth first search through the original tree and to create the rooted +tree during the traversal. + +The algorithm starts on the designed root node. The new rooted tree is being +displayed on the right. + +From the root node, begin the depth first search and add nodes to the rooted +tree as the algorithm proceeds. I'll let the animation play and it should be +clear what's going on. + + + +And that's rooting a tree in a nutshell + +Let's have a look at some pseudo code for this. + +On this slide I define an object I will be using to describe a tree node. + +Each node has a unique integer id + +As well as a reference to its parent pointer. This member is generally optional, +but I thought I should include it for completeness. Take note that every node +will have a parent pointer except the root node whose parent pointer will be +null. + +Additionally, each node also has a list of all its child nodes. + +Here's the algorithm to root a tree, it's relatively short and sweet. The input to the +rootTree function takes a graph g as input which is the tree we want to root, +the other input is the id of the designated root node. By default this is node +0, but it can be any other node. + +The first line in the rootTree method creates the root tree node object with the +id rootId, a parent reference of null and no child nodes. + +Then I call the buildTree method to start the depth first traversal to root the +tree. As input parameters I pass in the graph g, the root node as the current +node and the root's parent which is null. + +The build tree takes exactly three parameters we just talked about: the graph, +the current node, and the current node's parent node reference. + +Next we enter a for loop that loops over all the neighbors of the current node, +which in turn will become the children of the current node. + +Because edges are undirected in the original tree, we absolutely need to avoid the +situation where we add a directed edge pointing back to the current node's +parent. + +First check that the parent is not null so we don't get a null pointer exception +when trying to access the parent's id. + +then check if the child id is equal to the parent id and skip this node if +true. + +Otherwise we're dealing with a proper child node, so create a new node and add +the child tree node to the list of the current node's children + +Afterwards, dig deeper into the tree and do the same thing but for the newly +created child node. + +Once we have finished iterating over all the neighbors of this node return the +current node. + +And that's how you root a tree. + +Alright, thank you for watching, please give this video a thumbs up if you +learned something, and subscribe for +more mathematics and computer science videos. + +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- + +Center(s) of a tree + +Hello and welcome, my name is William, today we're looking at how to find the +center of a tree. This is the sort of problem you might encounter during +an interview or in a competitive programming setting. + +Finding the center of a tree can be a handy algorithm to have in your toolkit, +you'll see it from time to time as a subroutine in other algorithms, it can also +be useful as a way of selecting a root node when you want to root a tree. + +One thing to be aware of when finding the center of a tree is that there can be +more than one center! Either you'll run into the case with a unique center, which +is nice, or you'll bump into a tree like the one of the right where there are +two centers. However, there can't be more than 2 centers, take a moment and +think about why that's true.. + +You will notice that the center is always the middle vertex or the middle two +vertices along the longest path of a tree. + +For example, this pink path is one of the possible longest paths on this tree + +and the center of the tree is the middle vertex along the path. + + + +if we repeat this process and select another longest path + +you'll see that we again end up with the correct center node. + +Another approach to finding the center or centers of a tree is going to be to +repeatedly remove the outer layer of leaf nodes. This process resembles that of +peeling an onion, you start outside in. + +The first thing we're going to do it compute the degree of each node, that is +the number of node's it is connected to. You'll notice that each leaf node will +have a degree of 1 since leaf nodes can only be connected to one other node. + +For this tree, the leaf nodes are those in red. + +Once we know what the leaf nodes are we can prune them and update the degree +of each of the nodes. + +After that, repeat the same process. Find identify the leaf nodes. + +Prune them and update the degree values. + +If you keep doing this, you will eventually reach the center of the tree. + +It's a simple concept, let's do another example. Feel free to pause the video +and find the center yourself using the algorithm I just described. + +First compute all the node degree values. + +Identify all the leaf nodes with a degree of 1 + +Prune them, and update the degree values. + +Find the new set of leaf nodes, these are all the nodes with a degree of 1. + +Then prune them, and update the degree values. + +When you're left with either 1 or 2 nodes you've found the center or centers. + +Ok, let's have a look at some pseudo-code. Real code can be found in the +description. + +The tree centers function takes as input a tree represented as an undirected +graph stored in the variable 'g'. + +The variable 'n' represents the number of nodes in our tree. + +After this, I define two arrays. The first one is 'degree' of length 'n' which +will capture the degree of each node, and 'leaves' is an array that contains the +most recent layer of leaf nodes. + +Then for the first bit of logic, simply loop through all the nodes and compute +the degree of each one. I do this by inspecting the adjacency list and counting +the number of edges coming out of each node. + +Then, if the node has a degree of 0, meaning we're dealing with a single node +tree or the node is a leaf node because it has a degree of 1 then add the node +to the leaves array. +We're also going to set the degree of the leaf node to be zero so we don't +process it again later. + +The way we're going to know when we've found the center or centers is when we +have processed all the nodes in the tree. The variable count is going to keep +track how many nodes we've processed so far. Every iteration we're going to +increment count by the number of leaves we found in the last layer. + +Entering the loop, 'new_leaves' is a new array that will contain the new leaf +nodes on the next layer. I'm using a new array to avoid interfering with the +leaf nodes on the current layer. + +So for every leaf node on the current layer, + +process all the neighbors of those nodes + +and decrement the degree of the neighbor nodes. Since we're removing the current +node this means that the degree of the neighboring node needs to go down. If the +neighbor node after being decremented has a degree of 1 then we know it will be +in the next layer of leaf nodes so add it to the new_leaves array. + +Everytime we finish processing a former leaf node, feel free to explicitly give +it a degree of zero to mark it as done. + +When we've finished processing the current layer increment the count variable +and replaces the leaves array with the new leaves. + +And finally return the centers + +Alright, thanks for watching, if you learned something please give this video a thumbs +up, and subscribe for more mathematics and computer science videos. + +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- + +Isomorphisms in trees + +Hello and welcome, my name is William, and today we're diving into isomorphisms +in trees -- a question of tree equality and what that means. + +When we're talking about general graphs and ask whether two graphs are +isomorphic we're asking whether they're structurally the same. In the middle, +even though graphs G1 and G2 are labeled differently and may appear different +they are structurally the same graph. You could for example conceptually unfold +the graph on the right to match the one of the left and relabel its vertices' +and you would have two identical graphs. + +We can also define the notion of a graph isomorphism more rigorously because +simply saying that two graphs have the same structure is not well defined. + If we define a graph G1 as a set of edges E1 and vertices V1 and another graph G2 in +the same manner, we can say that two graph g1 and g2 are isomorphic if there exists a +bijection between the sets v1 and v2 such that: +For all pairs of vertices which form an edge in g1, applying a function phi to +the nodes of all those edges always results in an edge which is present in the +graph g2. +In simple terms, for an isomorphism to exist there needs to be a function which +can map all the nodes/edges in G1 to G2 and vice-versa. + +As it turns out, determining if two graphs are isomorphic is not only not +obvious to the human eye, but also a difficult problem for computers. +It is still an open question as to whether the graph isomorphism problem is NP +complete. However, many polynomial time algorithms exist for +specialized graph types in including trees. + +Let's have a look at a few examples, I'm going to show you two trees are you +have to tell me if they're isomorphic or not. + +Are tree1 and tree2 isomorphic? [small pause] + +No, they're structually different. + +what about tree3 and tree4? [small pause] + +Yup, they're isomorphic. + +In terms of writing an algorithm for identifying isomorphic trees, there are +several very quick probabilistic, usually hash or heuristic based algorithms +for identifying isomorphic trees. These tend to be very fast, but also more +error prone due to hash collisions in a limited integer space. However, +they do have a place when it comes to competitive programming and testing +the equality of absolutely enormous trees. +The method we'll be looking at today involves serializing a tree into a unique +encoding. This encoding is simply a string that represents the tree, and if +another tree has the same encoding then both trees are isomorphic. + +When going about encoding a tree, you can directly serialize an unrooted tree, +but in practice I find it easier to write code to serialize a rooted tree, just +my personal preference. +However, one small caveat to watch out for if your going for the rooted tree +approach is to make sure you root both your two trees T1 and T2 with the same root +node before you begin the serialization process, otherwise you'll get two +different encodings. + +One trick we can use to help ourselves select a common node between both trees, +is to reuse what we learned in the last video on finding the center(s) of a tree + +Let's have a look at the entire flow of how encoding a tree is going to work. +First we start out with two trees, T1 and T2, which may or may not be +isomorphic, that's what we're trying to figure out. + +From here, find the center of both trees, don't worry about the case where one of +the trees has more than one center, we'll see how to handle that later. + +Next, root the trees at their center nodes. + +Then generate the encoding for each tree and compare the serialized tree values +for equality. + +The tree encoding is simply a sequence of left '(' and right ')' brackets. +However, you can also think of them as 1’s and 0’s which is just a really large +number if you prefer. + +From this encoding it should also be possible to reconstruct the original tree +solely based on the encoding. I will leave this as an exercise to the reader. + +The AHU algorithm, short for the initials of the three authors who created the +algorithm, is a clever serialization technique for representing a tree as a unique string. +Unlike many tree isomorphism invariants and heuristics, AHU is able to capture +a complete history of a tree’s degree spectrum and structure. In turn, this +ensures a deterministic method of checking tree isomorphisms. Let's take a +closer look at how it works. + +Suppose we have this tree that we wish to serialize. + +The first thing we do is assign all leaf nodes Knuth tuples which are a pair of +left and right brackets. + + + +After that, for all nodes with all grayed out children combine their children's +labels and wrap them in a new pair of brackets. + +It's clearer if I highlight how the brackets were combined. If you look at the +leftmost branch, you can see that we combined the labels from node 6 and node 7 +and then wrapped them in a new bracket set to create the label for node 2. + + + +If we continue and process all nodes which have all grayed out children which is +currently only node 1, then you see that we've combined the labels from +node 4 and node 5 to create node 1's new label. + +One thing you'll notice is that in the combining phase the child labels need to +get sorted, this is very important because it is what ensures uniqueness. + + + +and now we get to process the last node + +here we combine and sort the labels from nodes 2, 1 and 3 to create the final +serialized encoding which will always be at the root node. In hindsight this +algorithm isn't too complicated, but I find it extremely clever. Props to the +original authors who created it. + + + +In summary of what we just looked at: +1) One, leaf nodes are assigned Knuth tuples consisting of a left and right +bracket to begin with. +2) Two, every time you move up a layer, the labels of the previous subtrees get sorted +lexicographically and wrapped in brackets. +3) Three, you cannot process a node until you have processed all its children. + +Alright, let's have a look at some pseudocode. + +The treesAreIsomorphioc method takes in as input two undirected trees, tree1 and +tree2 stored as adjacency lists. + +The first thing we do is find the center node or nodes of these two trees. +This is a method i'm borrowing from 1 slide deck ago. + +After that, I root tree1 using the first center node, which there will always be +at least 1 of. The rootTree method is the same one we covered 2 videos ago, +check the description if you missed it. + +As a reminder, I'm storing the rooted trees in this code as tree node objects +as defined on this slide. +We do this to facilitate writing recursive algorithms, but also to keep our tree data +structure organized. + +Once we have rooted our tree, we can serialize it with the encode method. Let's +take a closer look at what's going on in there. + +The encode method is the one which generates the unique string for our tree. + +The first thing I do is handle the base case where we have a null node. For this +we can return an empty string which will cause all leaf nodes to have a +left and right bracket as a starting value upon the callback. + +For each node, we maintain a list of labels for all the subtrees. To generate +the labels, +Iterate through all the children of this node and recursively call the encode +method adding the results to the labels list. + +After the for loop, the recursive calls have returned and the labels list +is populated and ready to be sorted. + +Afterwards, concatenate the labels and wrap the result in brackets. + +Coming back to the main method. Now the next thing we want to do is encode the +second tree and compare the encoded results, but if the tree has 2 centers we +don't know which center node in the second tree is the correct one, so we need +to try both. For this, iterate over both centers and root the tree comparing the +encoded result with the one from the first tree. If there's any match then we +know the trees are isomorphic. + + + +Thank you for watching, if you learned something please give this video a thumbs up +and subscribe for more mathematics and computer science videos. + + + + + + + +-------------------------------------------------------------------- +-------------------------------------------------------------------- +-------------------------------------------------------------------- + +Isomorphism source code + +Hello and welcome, my name is William, today we're going to take a +look at some source code for identifying isomorphic trees which will also +include the source code for rooting a tree and finding the center of a tree. + +In the previous video, we talked about identifying isomorphic trees by first +serializing the tree into a string and then using that as a means for equality. +Today we will be looking at the same approach in more detail, so make sure +you've given the previous video a watch if you haven't done so. There should be +a link in the description below. + +All the source code you see today can be found on Github at github dot com slash +william fiset slash algorithms. + +So here we are in the source code for identifying isomorphic trees written +in Java. Let's start by taking a look at an example of what this code does in +the main method... + + + +In this method we create two trees, tree1 and tree2 which are structurally the +same but have different labels. Today what we're going to take a look at is the +implementation behind the 'treesAreIsomorphic' method which is able to check +whether tree1 and tree2 are truly isomorphic or if they are actually two +fundamentally different trees. + + +--- + +Going back up to the treesAreIsomorphic method, you'll see that it takes as +input two undirected trees stored as adjacency lists: tree1 and tree2. + +Just so that we don't have to worry about it, the first thing I do is check if +either trees are empty and throw an exception. + +After that, we begin the whole tree serialization process, it's pretty simple, +start by finding the center or centers of both trees. + +Then encode the first tree into a string. The encode method takes as input a +rooted tree instead of an undirected tree, so make sure you root the tree first. +After the encode method has finished processing, it will return a unique string +for tree1. + +Now the next thing we want to do is encode the second tree so that we can +compare it to the first one. However, if the second tree has 2 centers we +don't know which center node in the second tree is the correct one, so we need +to try both. +For this, iterate over both centers and root the tree comparing the +encoded result with the first tree. If there's any match then we +know that the trees are isomorphic. +Awesome, so that's the algorithm from a bird's eye view, now let's dig into the +details and figure out what is going on inside the findTreeCenters, +rootTree and encode methods. + +--- + +findTreeCenters + +Let's begin with the findTreeCenters method, this is the source code from one +video ago. To refresh your memory, the approach we're taking to find the center +or centers of a tree is to iteratively remove leaf nodes layer by layer. This is +analogous to peeling the layers of an onion, when we're finished removing all +the layers what's left is the middle. + +The integer 'n' represents the number of nodes in our tree. + +After this, I define two variables. The first one is 'degree' with size 'n' +which will capture the degree of each node, followed by 'leaves' which is a +dynamic array that will contains most recent layer of leaf nodes. + +Then for the first bit of logic, simply loop through all the nodes and compute +the degree of each one. I do this by inspecting the adjacency list and counting +the number of edges coming out of each node. + +Then, if the node has a degree less than or equal to 1, meaning we're either +dealing with a single node tree or a leaf node then add the node to the leaves +array. After this, set the degree of the leaf node to be zero as though we +removed it from the tree. + +The way we're going to know when we've found the center or centers is when we +have processed all the nodes in the tree. The variable processedLeafs is going +to keep track how many nodes we've processed so far. Every iteration we're going +to increment processedLeafs by the number of leaves we found in the last layer. + +Entering the loop, 'new_leaves' is a new array that will contain the new leaf +nodes on the next layer. I'm using a new array to avoid interfering with the +leaf nodes on the current layer. + +So for every leaf node on the current layer, + +process all the neighbors of those nodes + +and decrement the degree of the neighbor nodes. Since we're removing the current +node this means that the degree of the neighboring nodes needs to go down by 1. +If the neighbor node after being decremented has a degree of 1 then we know it +will be in the next layer of leaf nodes so add it to the new_leaves array. + +Every time we finish processing a former leaf node, give it a degree of zero to +mark it as removed. + +When we've finished processing the current layer, increment the processedLeafs +variable and replace the leaves array with the new leaves. + +And finally return the centers which are stored in the leaves array. + +So that's how the findTreeCenters method works, now before we take a look at how +to root a tree we should look at the TreeNode class which gets used by the +rootTree method. + +--- + +TreeNode class + +The first thing I do is define this TreeNode object we'll be using to represent our +rooted trees. TreeNode's are straightforward, they only contain three members, +a unique id, a parent node reference and a list of references to all its children. + +To create a TreeNode, all you need is to give that TreeNode a unique id, you can +also optionally create a TreeNode with an id and a specified parent node. + +The one useful method I've added to this class is the ability to add children +to this TreeNode. Simply pass in any number of TreeNodes you wish to add as child +node to this node and they'll get added to the children's list. + +The rest of the methods in this class are just getter methods. + +--- + +rootTree + +Coming back to the rootTree method... + +You'll see that this function takes as input our tree represented as an +adjacency list and the id of the root we want to root the tree by. At lot of the +time the root id is going to be zero, but it's nice to be able to choose which +node will be the root in situations where it matters such as this tree +isomorphism problem. + +The first line in the rootTree method creates the root TreeNode. + +Then I call the buildTree method to start the depth first traversal to root the +tree. As input parameters I pass in the graph and the root node as the current +node. + +Inside the buildTree method we enter a for loop that loops over all the +neighbors of the current node. All these neighbor nodes are going to become the +children of the current node, except for the last parent node. + +One thing we need to be aware of is that edges are undirected in the original +tree meaning we absolutely need to avoid the situation where we add a directed +edge pointing back to the current node's parent, every other neighbor node will +become a child of the current node. + +To check if the neighbor node is the parent node first check that the parent is +not null so we don't get a null pointer exception, then access the parent's id +and check if the child id is equal to the parent id and skip this node if true. + +Otherwise we're dealing with a proper child node, so create a new node and add +the child TreeNode to the list of the current node's children. + +Afterwards, dig deeper into the tree and do the same thing but for the newly +created child node. + +Once we have finished iterating over all the neighbors of this node return the +current node. + +--- + +encode + +After we've finished rooting our tree, remember that the next thing we do is +encode it into a unique string so that we can easily compare two trees to see +if they're isomorphic. + +The first thing I do is handle the base case where we have a null node. For this +we can return an empty string which will cause all leaf nodes to have a +left and right bracket as a starting value upon the callback. + +For each node, we maintain a list of labels for all the subtrees. To generate +the labels, +Iterate through all the children of this node and recursively call the encode +method adding the results to the labels list. + +After the for loop, the recursive calls have returned and the labels list +is populated and ready to be sorted. + +Afterwards, concatenate the labels and wrap the result in brackets. + +--- + +Awesome, that's a wrap for this video, I hope you have had a large enough dose +of tree algorithms for the day. Please like this video if you learned something, +and subscribe for more mathematics and computer science videos. + + + + + + + + + + + diff --git a/slides/graphtheory/tarjans_scc.key b/slides/graphtheory/tarjans_scc.key deleted file mode 100644 index 159d86203..000000000 Binary files a/slides/graphtheory/tarjans_scc.key and /dev/null differ diff --git a/slides/graphtheory/tarjans_scc.pdf b/slides/graphtheory/tarjans_scc.pdf deleted file mode 100644 index 4f7d860ab..000000000 Binary files a/slides/graphtheory/tarjans_scc.pdf and /dev/null differ diff --git a/slides/graphtheory/tarjans_scc.pptx b/slides/graphtheory/tarjans_scc.pptx deleted file mode 100644 index 9e334cf57..000000000 Binary files a/slides/graphtheory/tarjans_scc.pptx and /dev/null differ diff --git a/slides/graphtheory/topsort.key b/slides/graphtheory/topsort.key deleted file mode 100644 index cb68b2b19..000000000 Binary files a/slides/graphtheory/topsort.key and /dev/null differ diff --git a/slides/graphtheory/topsort.pdf b/slides/graphtheory/topsort.pdf deleted file mode 100644 index 94bdd012a..000000000 Binary files a/slides/graphtheory/topsort.pdf and /dev/null differ diff --git a/slides/graphtheory/topsort.pptx b/slides/graphtheory/topsort.pptx deleted file mode 100644 index 7c2e8f2d4..000000000 Binary files a/slides/graphtheory/topsort.pptx and /dev/null differ diff --git a/slides/graphtheory/travelling_salesman_problem.key b/slides/graphtheory/travelling_salesman_problem.key deleted file mode 100644 index fac9a6782..000000000 Binary files a/slides/graphtheory/travelling_salesman_problem.key and /dev/null differ diff --git a/slides/graphtheory/travelling_salesman_problem.pdf b/slides/graphtheory/travelling_salesman_problem.pdf deleted file mode 100644 index 25fc28362..000000000 Binary files a/slides/graphtheory/travelling_salesman_problem.pdf and /dev/null differ diff --git a/slides/graphtheory/travelling_salesman_problem.pptx b/slides/graphtheory/travelling_salesman_problem.pptx deleted file mode 100644 index 16a415b40..000000000 Binary files a/slides/graphtheory/travelling_salesman_problem.pptx and /dev/null differ diff --git a/slides/graphtheory/tree_algorithm_slides.key b/slides/graphtheory/tree_algorithm_slides.key new file mode 100755 index 000000000..a47604a0b Binary files /dev/null and b/slides/graphtheory/tree_algorithm_slides.key differ diff --git a/slides/graphtheory/tree_algorithm_slides.pdf b/slides/graphtheory/tree_algorithm_slides.pdf new file mode 100644 index 000000000..6aed18b26 Binary files /dev/null and b/slides/graphtheory/tree_algorithm_slides.pdf differ diff --git a/slides/graphtheory/trees.txt b/slides/graphtheory/trees.txt deleted file mode 100644 index 6c28c0d60..000000000 --- a/slides/graphtheory/trees.txt +++ /dev/null @@ -1,291 +0,0 @@ - -Hello and welcome back, my name is William, and in today's video we're going to start tackling the topic of trees. In particular, we're going to look at what trees are and how we computationally store and represent trees. - -Conceptually it's fair to say most people know what I mean when I say I'm working with a tree, or that something is structured as a tree. Below are four graphs, but one of them is not a tree, do you know which one? - -Only the last graph is not a tree, but why, why is it not a tree? - -That is because we define a tree as being an undirected graph with no cycles, and that's the key thing to remember -- trees cannot have cycles. You can see that the rightmost graph has a cycle, and is therefore not a tree. However, there's an even easier way to check whether a graph is a tree or not. - -Each tree has exactly n nodes and n-1 edges. If we count up all the nodes and edges of each graph you can see that all the trees have one less edge than the number of nodes, except for the rightmost graph which is not a tree. - -We now know what trees are, but where do they appear in computer science and in the real world? Here are a few examples where you might encounter a tree structure: - - First is your computer's file system which consists of directories, subdirectories and files which is inherently a tree. - - Another place you see trees are in social hierarchies where you often see CEO's, kings, priests and generals at the top; and interns, servants, children and the lower class at the bottom. - - Trees are used to decompose source code and mathematical expressions into what are called abstract syntax trees for easy evaluation. For example, the math expression you see on this slides can be broken down into a tree structure. - - Every webpage you visit can be throught of as a tree due to HTML's nested tag structure. This structure is used to tell your browser how to easily render the page and how it should be displayed. - - Another large application of trees is in game theory to model decisions and courses of action. On this slide is the famous prisoner's dilemma problem and it's four different outcomes for whether each prisoner chooses to confess or defect. - - There are many many more applications of trees in computer science and in the real world, but we're not going to cover them all today. However, it's worth mentioning that in computer science, the place you'll most often encounter trees is as part of data structures, many of which are listed on this slide. - -Now we need to talk about how we actually store and represent these undirected trees. - -First, you should label the nodes of your tree by indexing them from 0 to n non-inclusive like the tree on the left of this slide. - -A simple way to store a tree is as an edge list, which is simply a list of undirected edges indicating which two nodes have an edge between them. The great thing about this representation is that it's super fast to iterate over and cheap to store. - -The downside however, is that storing your tree as a list lacks the structure to efficiently query all the neighbors of a node. - -This is why the adjacency list is usually a more popular representation for storing an undirected tree. In this representation, you store a mapping between a node and all its neighbors. - -For example, node 4 has the neighbors 1, 5 and 8 so in the adjacency list, node 4 maps to the list containing 1, 5 and 8 respectively. - -You could also store a tree as an adjacency matrix of size n by n where having a 1 in a particular cell means that the nodes which corresponding to the row/column values have an edge between them. - -However, in practice I would say to always avoid storing a tree as an adjacency matrix because it's a huge waste of space. You would not ever want to allocate n squared memory and only use roughly 2n of the matrix cells, it just doesn't make sense. - - -Alright, I can't keep talking about trees without mentioning rooted trees which are trees with a designated root node. I have highlighted the root node in orange, and most rooted trees as you'll notice have directed edges which point away from the root node, however it's also possible to have edges which point towards the root node, but those trees are much rarer from my experience. -Generally speaking, rooted trees are far easier to work with than undirected trees because they have this well defined structure that allows for easy recursive algorithm implementations. - -Related to rooted trees are binary trees which are trees for which every node has at most two child nodes. The first two trees on this slide are binary trees, but the last one is not because it has a node with more than 2 child nodes. -You don't often see binary trees manifest themselves in the real world, for the most part binary trees are artificially created and integrated as part of data structures to guarantee efficient insertions, removals and access to data. - -Now related to binary trees are binary search trees which are trees which satisfy the BST invariant. The BST invariant states that for every node x the values in the left subtree are less than or equal to x and that the values in the right subtree are greater than or equal to x. This nice little property enables you to quickly search through a tree and retrieve the values you want, which is particularly handy. All the trees on this slide are BSTs except for the last one, because 1 is *not* greater than or equal to 3. - -It's often useful to require uniqueness on the values of your binary search tree so that you don't end up with duplicate values. To resolve this issue of duplicate values you can change the invariant to be strictly less than rather than less than or equal to. - -Now let's talk about how to store these rooted trees. Rooted trees are naturally defined recursively in a top down manner. - -In practice, you always maintain a pointer reference to the root node so that you can access the tree and its contents. - -Then each node also has access to a list of all its children -- which are also called "child nodes". In this slides, the orange node is the current node we have a reference to, and the purple nodes are all its children. All the bottom or leaf nodes don't have any children. - -It's also sometimes useful to also maintain a pointer to a node’s parent node in case you need to traverse up the tree. This effectively makes the edges in the tree bidirectional. Again, if the current node is the orange node, than the pink node in the case in the parent node of the orange node. - -However, maintaining an explicit reference to the parent node isn’t usually necessary because you can access a node’s parent on a recursive function's callback as you pop frames off the stack. - - -Another really neat way of storing a rooted tree if it is a binary tree is in a flattened array. - -In the flattened array representation, each node has an assigned index position based on where it is in the tree. The thing to understand here is that the tree is actually an array, the diagrams are just a visual representation of what the tree looks like.For instance, the node with value 5 in orange is associated with the index 4 in the array. - -Similarly, this node with a value of 2 has an index of 6. - -Even nodes which aren't currently present have an index because they can be -mapped back to a unique position in the "index tree" as I call it. - -In this format, the root node is always at index 0 in the array, so you always know where your starting point is. Another advantage of this format is that the child nodes of node i can be access relative to position i. - -For example, if we're at position 2 in the array, we know that the left and right children of the node at index 2 is given by 2 times i plus 1 and 2 times i plus 2. Therefore the children of the node at index 2 can be found at positions 5 and 6. Reciprocally, this means if we have a node we know what the index of the parent node should be, which is also very useful. - -Alright, that's all I have for this video, thank you for watching, please like and subscribe and I'll see you in the next one. - - - -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- - - - -Hello and welcome, my name is William, and in today's video we're going to look at some simple tree algorithms. This video is aimed at all you beginner's just starting out who are still learning how to write code and especially recursive code. We're going to have a look at two problems today, and these are the types of problems which you might encounter as warm up questions in a job interview. - - - Alright, let's start with our first problem. For this problem we need to find the sum of all the leaf node values in a tree. - - For instance, given this tree we want to sum up all the bottom nodes. - - That would be all these nodes in red for a total of 9. If you're keen, you - pause this video and give this problem a try. - - - - Like all rooted tree problems we start with a reference to the root node. To solve this problem all we need to do is perform a tree traversal and sum up the value of the leaf nodes while we do the traversal. - Two popular traversal algorithms are doing a Breadth First Search and Depth First Search. On trees, the preferred traversal method is typically a DFS because it lends itself to be easily implemented recursively. - It's pretty simple, watch how the animation does it. You start at the root and plunge down the tree depth first. - - - - - At the end when we sum up all the values, and we can see that the sum of the leaf nodes is 9. Now let's have a look at some pseudo-code for how this is implemented. - - The algorithm is quite short, but it may look strange at first if you haven't seen much recursive code. - - We call the leafSum function by passing in a reference to the root node as a starting point. - - The first thing we do is handle the special case where the tree is empty and return zero in such a situation. - - next we check if the current node is a leaf node, and if it is we return the value stored in the leaf node. - - The isLeaf method checks if a node is a leaf node by counting all its children. If the number of child nodes is zero then we know the node is a leaf node. - - If the node is not a leaf node then we iterate over all the children and call the leafSum method recursively summing over the return values. This ensures that we traverse the entire tree and properly accumulate values. - - Finally, once we have finished computing the sum for this node and its subtree return the total. - - And that's all for summing up the leaf node values. - - - - Our second problem today is a classic problem in computer science which is to find the height of a binary tree. The height of a tree is defined as the number of edges from the root to the lowest leaf. Here the leftmost tree has a height of zero because it has no edges, the middle tree has a height of 1, and the rightmost tree has a height of 3 because the longest path from the root to the lowest leaf is 3. - - To solve this problem we're going to break it down and define a new function "h of x" which returns the height of the subtree rooted at node x. - This new function allows us to start thinking not only about the height of the tree as a whole but also the height of the subtrees within our tree which are going to help us find the overall height. - - For example, on this slide "h of a" has a value of 3, but "h of b" has a value of 2 and "h of e" has a value of zero. - - By themselves, leaf nodes such as node e don't have children, so they don’t add any additional height to the tree. So we can conclude that as a base case, the height of any leaf node should be zero. - - Now, assuming node x is not a leaf node, we're able to formulate a recurrence relation for the height, which is that the height of the subtree rooted at node x is the maximum of the height of x's left and right subtrees plus one. - - Let's have a closer look at how this works with an example. Suppose we have this tree and we want to compute its height. - - So we start at the root - - and then we start traversing down the tree depth first - - When we encounter a leaf node, we give it a height of zero and return - - We can't compute the height of a node until we know the height of all its children, so we visit the right node. - - The right node is also a leaf node so it gets a height of zero - - On the callback we have visited both children of the current node so we take the maximum heights of the left and the right children and add 1 for a total of 1. - - We just finished exploring the right half of the tree, now let's finish the left side. It doesn't matter which side you do first as long as you explore the whole tree while doing your DFS. - - - - We found another leaf node so it gets a height of zero - - - - - - leaf node - - - - and another leaf node - - take the max and add 1 - - take the max and add 1 again - - finally compute the height of the final node by taking the max and adding 1 - - And there you have it, how the find the height of a tree. Let's have a look at some pseudocode, shall we. - - - - This is the treeHeight function, and it is responsible for computing the height of the tree. You would start by calling this function by passing in the tree's root node like we did for the leafSum function. - - The first thing we do is handle the empty tree case. The height of an empty tree is undefined so return -1. - - Next we check whether the current node is a leaf node by checking if both its left and right child are null and return zero. If either of them are not null then we know this node has more children and we need to keep digging down the tree. - - In the event we haven't found a leaf node, return the maximum height of the left subtree and the right subtree plus 1. - - I want to take a moment and go back to the previous statement and remark on a simplification we can exploit. - What do you reckon happens if we remove checking whether a node is a leaf node or not, do you think the algorithm would still behave correctly? Pause the video and think this over. - - Oddly enough, removing the leaf node check still makes the algorithm work correctly. This works because the base case has adopted a new meaning, instead of the base case checking for leaf nodes, it's now checking whether a node is null and returning -1. Returning -1 now not only checks for the empty tree case, but it also helps in correcting for the right tree height. - - Let's see what I mean by correcting for the right height. If our new base case is now checking for null nodes instead of the leaf nodes, then our tree one unit taller, so we need to correct for this. - - For the sake of being thorough, let's see how the height is calculated with this new base case. - - - - - - - - - - Once we reach a null node, return -1 - - On the recursive callback remember that we always add +1 with our recurrence. - - - - - - - - So when we add up the counts, you see that we get the correct height of 3. - - And that's how you compute the height of a tree. In practice, if you're designing a data structure where tree height is important you can dynamically keep track of the height as you create the tree instead of computing it fully like we're doing here, but of course, that it's always doable. - - -Alright folks, thanks for watching, please like and subscribe if you learned something and i'll catch you next time. - - -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- - -Hello and welcome, my name is William, today we're looking at how to root a tree. This is a very basic transformation that's handy to have in your toolkit in case you need to or want to work with a rooted tree. - -The motivation for rooting a tree is that often it can help add structure and simplify the problem you're trying to solve. Rooting the tree enables you to easily perform recursive algorithms and transforms the tree to have directed edges instead of undirected edges which is also generally easier to work with. - -To root the tree, first you need to select a root node. I'm going to pick node 0. - -Conceptually, rooting a tree is like "picking up" the tree by a specific node and having all the edges point downwards. - -You can root a tree using any of its nodes. However, be cautious because not every node you select will result in a well balanced tree, so we may have to be more selective. - -In some situations it’s also useful to keep have a reference to the parent node in order to walk up the tree. I illustrated parent node pointers as dotted lines on this slide. - -Let's look at an example of how to root a tree. One of the best ways to do this is with a depth first search through the original tree and to create the rooted tree during the traversal. - -The algorithm starts on the designed root node. The new rooted tree is being displayed on the right. - -From the root node, begin the depth first search and add nodes to the rooted tree as the algorithm proceeds. I'll let the animation play and it should be clear what's going on. - - - -And that's rooting a tree in a nutshell - -Let's have a look at some pseudo code for this. - -On this slide I define a data object I will be using to describe a tree node. - -Each node has a unique integer id - -As well as a reference to its parent pointer. This member is generally optional, but I thought I should include it for completeness. Take note that every node will have a parent pointer except the root node whose parent pointer will be null. - -Additionally, each tree node will also have a list of all the child tree nodes it has a reference to. - -Here's the algorithm itself, it's relatively short and sweet. The input to the rootTree function takes a graph g as input which is the tree we want to root, the other input is the id of the designated root node. By default this is node 0, but it can be any other node. We assume that the root node exists in g and that the id is valid, otherwise i'm pretty sure this algorithm breaks. - -The first line in the rootTree method creates the root tree node object with the id rootId, a parent reference of null and no child nodes. - -Then I call the build tree method to start the depth first traversal to root the tree. As input parameters I pass in the graph g, the root node as the current node and the root's parent which is null. - -The build tree takes exactly three parameters we just talked about: the graph, the current node, and the current node's parent node reference. - -Next we enter a for loop that loops over all the neighbors of the current node, which in turn will become the children of the current node. - -Because edges are undirected in the original tree, we absolutely need to avoid a situation where we add a directed edge pointing back to the parent. - -So if the parent is null, meaning we're dealing with the root node. - -Or the child id is equal to the parent id skip this node. - -Otherwise we're dealing with a proper child node, so create a new node and add the child tree node to the list of the current node's children - -Afterwards, dig deeper into the tree and do the same thing but for the newly created child node. - -Once we have finished iterating over all the neighbors of this node return the current node. - -And that's rooting a tree - -goodnight. - - - - - - - - - - - diff --git a/slides/graphtheory/trees_slides.key b/slides/graphtheory/trees_slides.key deleted file mode 100755 index 5d4e1abc4..000000000 Binary files a/slides/graphtheory/trees_slides.key and /dev/null differ diff --git a/slides/graphtheory/tsp_xkcd.png b/slides/graphtheory/tsp_xkcd.png deleted file mode 100644 index f47060f89..000000000 Binary files a/slides/graphtheory/tsp_xkcd.png and /dev/null differ diff --git a/slides/other/Recursion_I.key b/slides/other/Recursion_I.key deleted file mode 100755 index af77b5f99..000000000 Binary files a/slides/other/Recursion_I.key and /dev/null differ diff --git a/slides/other/recursion/FloodFill.kt b/slides/other/recursion/FloodFill.kt new file mode 100644 index 000000000..f07a5953c --- /dev/null +++ b/slides/other/recursion/FloodFill.kt @@ -0,0 +1,39 @@ +val R = 7 +val C = 10 +var matrix: Array = arrayOf( + intArrayOf(0,0,0,0,0,0,0,0,0,0), + intArrayOf(1,0,0,1,1,1,0,1,0,0), + intArrayOf(0,1,0,0,0,0,1,1,0,0), + intArrayOf(0,0,1,1,1,0,0,0,0,0), + intArrayOf(0,0,1,0,1,1,0,1,1,1), + intArrayOf(0,0,1,1,0,0,1,1,0,0), + intArrayOf(0,0,1,0,0,0,0,1,0,0), +); + +fun fill(r: Int, c: Int) { + if (r < 0 || r >= R || c < 0 || c >= C) { + return + } + if (matrix[r][c] > 0) { + return + } + // Mark cell as visited + matrix[r][c] = 2; + fill(r-1, c) + fill(r+1, c) + fill(r, c-1) + fill(r, c+1) +} + +fun main() { + var components = 0 + for (r in 0..R-1) { + for (c in 0..C-1) { + if (matrix[r][c] == 0) { + components++ + fill(r, c) + } + } + } + println("Components = $components") +} diff --git a/slides/other/recursion/Multiplication.kt b/slides/other/recursion/Multiplication.kt new file mode 100644 index 000000000..9af879862 --- /dev/null +++ b/slides/other/recursion/Multiplication.kt @@ -0,0 +1,36 @@ +import kotlin.math.* + +// Best way to do multiplication: +fun mul(a: Int, b : Int): Int { + if (a == 0) { + return 0 + } + if (a < 0) { + return mul(a + 1, b) - b + } + return mul(a - 1, b) + b; +} + +fun mul2(a: Int, b: Int): Int { + // Assumes a >= 0 + fun mul3(a: Int, b: Int): Int { + if (a == 0) { + return 0; + } + return mul3(a - 1, b) + b; + } + + if (a < 0 && b >= 0 || a >= 0 && b < 0) { + return -mul3(abs(a), abs(b)) + } else { + return mul3(abs(a), abs(b)) + } +} + +fun main() { + for (i in -3..3) { + for (j in -3..3) { + println(i*j == mul2(i,j)) + } + } +} diff --git a/slides/other/recursion/RecursionExamples.kt b/slides/other/recursion/RecursionExamples.kt new file mode 100644 index 000000000..a9f41700a --- /dev/null +++ b/slides/other/recursion/RecursionExamples.kt @@ -0,0 +1,42 @@ + +fun f1(count: Int) { + if (count > 5) { + return + } + println("Hello World!") + f1(count + 1) +} + +fun countingDownToZero(n: Int) { + if (n <= 0) { + println("Done counting down") + return + } + println("At $n") + countingDownToZero(n - 1) +} + +// Assignment? +fun countingUp(n: Int, maxValue: Int) { + if (n >= maxValue) { + println("Done counting up. At or past $maxValue") + return + } + println(n) + countingUp(n+1, maxValue) +} + +// Prints all the even numbers in the range [i, j] +fun printEvenNumbers(i:Int, j:Int) { + if (i > j) { + return + } + if (i % 2 == 0) { + println("$i is even!") + } + printEvenNumbers(i+1, j) +} + +fun main() { + printEvenNumbers(5, 12) +} diff --git a/slides/other/recursion/SumList.kt b/slides/other/recursion/SumList.kt new file mode 100644 index 000000000..01340a871 --- /dev/null +++ b/slides/other/recursion/SumList.kt @@ -0,0 +1,35 @@ + + +fun sum(ar: IntArray): Int { + return sum(0, ar) +} + +fun sum(i: Int, ar: IntArray): Int { + if (i >= ar.size) { + return 0; + } + return ar[i] + sum(i+1, ar) +} + +fun divideAndConquer(ar: List): Int { + if (ar.size == 0) { + return 0 + } + return divideAndConquer(0, ar.size - 1, ar) +} + +fun divideAndConquer(i:Int, j:Int, ar: List): Int { + if (i == j) { + return ar[i] + } + val mid = (i+j) / 2 + return divideAndConquer(i, mid, ar) + divideAndConquer(mid+1, j, ar) +} + +fun main() { + for (n in 0..20) { + val ar = List(n, {1}) + println(ar) + println(divideAndConquer(ar)) + } +} diff --git a/slides/other/recursion/recursion.key b/slides/other/recursion/recursion.key new file mode 100644 index 000000000..701393c96 Binary files /dev/null and b/slides/other/recursion/recursion.key differ diff --git a/com/williamfiset/algorithms/ai/GeneticAlgorithm_knapsack_01.java b/src/main/java/com/williamfiset/algorithms/ai/GeneticAlgorithm_knapsack_01.java similarity index 99% rename from com/williamfiset/algorithms/ai/GeneticAlgorithm_knapsack_01.java rename to src/main/java/com/williamfiset/algorithms/ai/GeneticAlgorithm_knapsack_01.java index 584a53308..10861b01c 100644 --- a/com/williamfiset/algorithms/ai/GeneticAlgorithm_knapsack_01.java +++ b/src/main/java/com/williamfiset/algorithms/ai/GeneticAlgorithm_knapsack_01.java @@ -63,7 +63,6 @@ static long run(int capacity, int[] weights, int[] values) { // Setup selection roulette for (int i = 1; i <= P; i++) { - Individual in = generation[i]; double norm = fitness[i] / fitnessSum; lo[i] = hi[i - 1] = lo[i - 1] + norm; diff --git a/com/williamfiset/algorithms/ai/GeneticAlgorithm_textSearch.java b/src/main/java/com/williamfiset/algorithms/ai/GeneticAlgorithm_textSearch.java similarity index 99% rename from com/williamfiset/algorithms/ai/GeneticAlgorithm_textSearch.java rename to src/main/java/com/williamfiset/algorithms/ai/GeneticAlgorithm_textSearch.java index b32f8ae63..3d2489760 100644 --- a/com/williamfiset/algorithms/ai/GeneticAlgorithm_textSearch.java +++ b/src/main/java/com/williamfiset/algorithms/ai/GeneticAlgorithm_textSearch.java @@ -12,7 +12,8 @@ public class GeneticAlgorithm_textSearch { // Target sentence static final String TARGET = "to be or not to be that is the question"; - static final char[] ALPHA = " abcdefghijklmnopqrstuvwxyz".toCharArray();; + static final char[] ALPHA = " abcdefghijklmnopqrstuvwxyz".toCharArray(); + ; static final int TL = TARGET.length(); static final Random RANDOM = new Random(); diff --git a/com/williamfiset/algorithms/ai/GeneticAlgorithm_travelingSalesman.java b/src/main/java/com/williamfiset/algorithms/ai/GeneticAlgorithm_travelingSalesman.java similarity index 100% rename from com/williamfiset/algorithms/ai/GeneticAlgorithm_travelingSalesman.java rename to src/main/java/com/williamfiset/algorithms/ai/GeneticAlgorithm_travelingSalesman.java diff --git a/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeRecursive.java b/src/main/java/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeRecursive.java similarity index 99% rename from com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeRecursive.java rename to src/main/java/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeRecursive.java index 7c946e539..03b034538 100644 --- a/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeRecursive.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeRecursive.java @@ -76,7 +76,6 @@ public boolean contains(T value) { // Recursive contains helper method. private boolean contains(Node node, T value) { - if (node == null) return false; // Compare current value to the value in the node. @@ -105,7 +104,6 @@ public boolean insert(T value) { // Inserts a value inside the AVL tree. private Node insert(Node node, T value) { - // Base case. if (node == null) return new Node(value); @@ -115,7 +113,6 @@ private Node insert(Node node, T value) { // Insert node in left subtree. if (cmp < 0) { node.left = insert(node.left, value); - ; // Insert node in right subtree. } else { @@ -131,7 +128,6 @@ private Node insert(Node node, T value) { // Update a node's height and balance factor. private void update(Node node) { - int leftNodeHeight = (node.left == null) ? -1 : node.left.height; int rightNodeHeight = (node.right == null) ? -1 : node.right.height; @@ -144,7 +140,6 @@ private void update(Node node) { // Re-balance a node if its balance factor is +2 or -2. private Node balance(Node node) { - // Left heavy subtree. if (node.bf == -2) { @@ -212,7 +207,6 @@ private Node rightRotation(Node node) { // Remove a value from this binary tree if it exists, O(log(n)) public boolean remove(T elem) { - if (elem == null) return false; if (contains(root, elem)) { @@ -226,7 +220,6 @@ public boolean remove(T elem) { // Removes a value from the AVL tree. private Node remove(Node node, T elem) { - if (node == null) return null; int cmp = elem.compareTo(node.value); @@ -260,7 +253,7 @@ private Node remove(Node node, T elem) { // successor of the node being removed can either be the largest // value in the left subtree or the smallest value in the right // subtree. As a heuristic, I will remove from the subtree with - // the greatest hieght in hopes that this may help with balancing. + // the greatest height in hopes that this may help with balancing. } else { // Choose to remove from left subtree diff --git a/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeRecursiveOptimized.java b/src/main/java/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeRecursiveOptimized.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeRecursiveOptimized.java rename to src/main/java/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeRecursiveOptimized.java diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/balancedtree/RedBlackTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/balancedtree/RedBlackTree.java new file mode 100644 index 000000000..4059d435b --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/balancedtree/RedBlackTree.java @@ -0,0 +1,459 @@ +/** + * This file contains an implementation of a Red-Black tree. A RB tree is a special type of binary + * tree which self balances itself to keep operations logarithmic. + * + *

Great visualization tool: https://www.cs.usfca.edu/~galles/visualization/RedBlack.html + * + * @author nishantc1527 + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.datastructures.balancedtree; + +import java.awt.*; + +public class RedBlackTree> implements Iterable { + + public static final boolean RED = true; + public static final boolean BLACK = false; + + public class Node { + + // The color of this node. By default all nodes start red. + public boolean color = RED; + + // The value/data contained within the node. + public T value; + + // The left, right and parent references of this node. + public Node left, right, parent; + + public Node(T value, Node parent) { + this.value = value; + this.parent = parent; + } + + public Node(boolean color, T value) { + this.color = color; + this.value = value; + } + + Node(T key, boolean color, Node parent, Node left, Node right) { + this.value = key; + this.color = color; + + if (parent == null && left == null && right == null) { + parent = this; + left = this; + right = this; + } + + this.parent = parent; + this.left = left; + this.right = right; + } + + public boolean getColor() { + return color; + } + + public void setColor(boolean color) { + this.color = color; + } + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + + public Node getLeft() { + return left; + } + + public void setLeft(Node left) { + this.left = left; + } + + public Node getRight() { + return right; + } + + public void setRight(Node right) { + this.right = right; + } + + public Node getParent() { + return parent; + } + + public void setParent(Node parent) { + this.parent = parent; + } + } + + // The root node of the RB tree. + public Node root; + + // Tracks the number of nodes inside the tree. + private int nodeCount = 0; + + public final Node NIL; + + public RedBlackTree() { + NIL = new Node(BLACK, null); + NIL.left = NIL; + NIL.right = NIL; + NIL.parent = NIL; + + root = NIL; + } + + // Returns the number of nodes in the tree. + public int size() { + return nodeCount; + } + + // Returns whether or not the tree is empty. + public boolean isEmpty() { + return size() == 0; + } + + public boolean contains(T value) { + + Node node = root; + + if (node == null || value == null) return false; + + while (node != NIL) { + + // Compare current value to the value in the node. + int cmp = value.compareTo(node.value); + + // Dig into left subtree. + if (cmp < 0) node = node.left; + + // Dig into right subtree. + else if (cmp > 0) node = node.right; + + // Found value in tree. + else return true; + } + + return false; + } + + public boolean insert(T val) { + if (val == null) { + throw new IllegalArgumentException("Red-Black tree does not allow null values."); + } + + Node x = root, y = NIL; + + while (x != NIL) { + y = x; + + if (x.getValue().compareTo(val) > 0) { + x = x.left; + } else if (x.getValue().compareTo(val) < 0) { + x = x.right; + } else { + return false; + } + } + + Node z = new Node(val, RED, y, NIL, NIL); + + if (y == NIL) { + root = z; + } else if (z.getValue().compareTo(y.getValue()) < 0) { + y.left = z; + } else { + y.right = z; + } + insertFix(z); + + nodeCount++; + return true; + } + + private void insertFix(Node z) { + Node y; + while (z.parent.color == RED) { + if (z.parent == z.parent.parent.left) { + y = z.parent.parent.right; + if (y.color == RED) { + z.parent.color = BLACK; + y.color = BLACK; + z.parent.parent.color = RED; + z = z.parent.parent; + } else { + if (z == z.parent.right) { + z = z.parent; + leftRotate(z); + } + z.parent.color = BLACK; + z.parent.parent.color = RED; + rightRotate(z.parent.parent); + } + } else { + y = z.parent.parent.left; + if (y.color == RED) { + z.parent.color = BLACK; + y.color = BLACK; + z.parent.parent.color = RED; + z = z.parent.parent; + } else { + if (z == z.parent.left) { + z = z.parent; + rightRotate(z); + } + z.parent.color = BLACK; + z.parent.parent.color = RED; + leftRotate(z.parent.parent); + } + } + } + root.setColor(BLACK); + NIL.setParent(null); + } + + private void leftRotate(Node x) { + Node y = x.right; + x.setRight(y.getLeft()); + if (y.getLeft() != NIL) y.getLeft().setParent(x); + y.setParent(x.getParent()); + if (x.getParent() == NIL) root = y; + if (x == x.getParent().getLeft()) x.getParent().setLeft(y); + else x.getParent().setRight(y); + y.setLeft(x); + x.setParent(y); + } + + private void rightRotate(Node y) { + Node x = y.left; + y.left = x.right; + if (x.right != NIL) x.right.parent = y; + x.parent = y.parent; + if (y.parent == NIL) root = x; + if (y == y.parent.left) y.parent.left = x; + else y.parent.right = x; + x.right = y; + y.parent = x; + } + + public boolean delete(T key) { + Node z; + if (key == null || (z = (search(key, root))) == NIL) return false; + Node x; + Node y = z; // temporary reference y + boolean y_original_color = y.getColor(); + + if (z.getLeft() == NIL) { + x = z.getRight(); + transplant(z, z.getRight()); + } else if (z.getRight() == NIL) { + x = z.getLeft(); + transplant(z, z.getLeft()); + } else { + y = successor(z.getRight()); + y_original_color = y.getColor(); + x = y.getRight(); + if (y.getParent() == z) x.setParent(y); + else { + transplant(y, y.getRight()); + y.setRight(z.getRight()); + y.getRight().setParent(y); + } + transplant(z, y); + y.setLeft(z.getLeft()); + y.getLeft().setParent(y); + y.setColor(z.getColor()); + } + if (y_original_color == BLACK) deleteFix(x); + nodeCount--; + return true; + } + + private void deleteFix(Node x) { + while (x != root && x.getColor() == BLACK) { + if (x == x.getParent().getLeft()) { + Node w = x.getParent().getRight(); + if (w.getColor() == RED) { + w.setColor(BLACK); + x.getParent().setColor(RED); + leftRotate(x.parent); + w = x.getParent().getRight(); + } + if (w.getLeft().getColor() == BLACK && w.getRight().getColor() == BLACK) { + w.setColor(RED); + x = x.getParent(); + continue; + } else if (w.getRight().getColor() == BLACK) { + w.getLeft().setColor(BLACK); + w.setColor(RED); + rightRotate(w); + w = x.getParent().getRight(); + } + if (w.getRight().getColor() == RED) { + w.setColor(x.getParent().getColor()); + x.getParent().setColor(BLACK); + w.getRight().setColor(BLACK); + leftRotate(x.getParent()); + x = root; + } + } else { + Node w = (x.getParent().getLeft()); + if (w.color == RED) { + w.color = BLACK; + x.getParent().setColor(RED); + rightRotate(x.getParent()); + w = (x.getParent()).getLeft(); + } + if (w.right.color == BLACK && w.left.color == BLACK) { + w.color = RED; + x = x.getParent(); + continue; + } else if (w.left.color == BLACK) { + w.right.color = BLACK; + w.color = RED; + leftRotate(w); + w = (x.getParent().getLeft()); + } + if (w.left.color == RED) { + w.color = x.getParent().getColor(); + x.getParent().setColor(BLACK); + w.left.color = BLACK; + rightRotate(x.getParent()); + x = root; + } + } + } + x.setColor(BLACK); + } + + private Node successor(Node root) { + if (root == NIL || root.left == NIL) return root; + else return successor(root.left); + } + + private void transplant(Node u, Node v) { + if (u.parent == NIL) { + root = v; + } else if (u == u.parent.left) { + u.parent.left = v; + } else u.parent.right = v; + v.parent = u.parent; + } + + private Node search(T val, Node curr) { + if (curr == NIL) return NIL; + else if (curr.value.equals(val)) return curr; + else if (curr.value.compareTo(val) < 0) return search(val, curr.right); + else return search(val, curr.left); + } + + public int height() { + return height(root); + } + + private int height(Node curr) { + if (curr == NIL) { + return 0; + } + if (curr.left == NIL && curr.right == NIL) { + return 1; + } + + return 1 + Math.max(height(curr.left), height(curr.right)); + } + + private void swapColors(Node a, Node b) { + boolean tmpColor = a.color; + a.color = b.color; + b.color = tmpColor; + } + + // Sometimes the left or right child node of a parent changes and the + // parent's reference needs to be updated to point to the new child. + // This is a helper method to do just that. + private void updateParentChildLink(Node parent, Node oldChild, Node newChild) { + if (parent != NIL) { + if (parent.left == oldChild) { + parent.left = newChild; + } else { + parent.right = newChild; + } + } + } + + // Helper method to find the leftmost node (which has the smallest value) + private Node findMin(Node node) { + while (node.left != NIL) node = node.left; + return node; + } + + // Helper method to find the rightmost node (which has the largest value) + private Node findMax(Node node) { + while (node.right != NIL) node = node.right; + return node; + } + + // Returns as iterator to traverse the tree in order. + @Override + public java.util.Iterator iterator() { + + final int expectedNodeCount = nodeCount; + final java.util.Stack stack = new java.util.Stack<>(); + stack.push(root); + + return new java.util.Iterator() { + Node trav = root; + + @Override + public boolean hasNext() { + if (expectedNodeCount != nodeCount) throw new java.util.ConcurrentModificationException(); + return root != NIL && !stack.isEmpty(); + } + + @Override + public T next() { + + if (expectedNodeCount != nodeCount) throw new java.util.ConcurrentModificationException(); + + while (trav != NIL && trav.left != NIL) { + stack.push(trav.left); + trav = trav.left; + } + + Node node = stack.pop(); + + if (node.right != NIL) { + stack.push(node.right); + trav = node.right; + } + + return node.value; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + // Example usage of RB tree: + public static void main(String[] args) { + + int[] values = {5, 8, 1, -4, 6, -2, 0, 7}; + RedBlackTree rbTree = new RedBlackTree<>(); + for (int v : values) rbTree.insert(v); + + System.out.printf("RB tree contains %d: %s\n", 6, rbTree.contains(6)); + System.out.printf("RB tree contains %d: %s\n", -5, rbTree.contains(-5)); + System.out.printf("RB tree contains %d: %s\n", 1, rbTree.contains(1)); + System.out.printf("RB tree contains %d: %s\n", 99, rbTree.contains(99)); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/balancedtree/TreapTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/balancedtree/TreapTree.java new file mode 100644 index 000000000..8bf8da810 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/balancedtree/TreapTree.java @@ -0,0 +1,178 @@ +/** + * This file contains an implementation of a Treap Class. Any comparable data is allowed within this + * tree(numbers, strings, comparable Objects, etc...) this is a max heap implementation(highest + * priority at top) Supported operations include: insert(x) remove(x) contains(x) + * + * @author JZ Chang, jzisheng@gmail.com + */ +package com.williamfiset.algorithms.datastructures.balancedtree; + +import com.williamfiset.algorithms.datastructures.utils.TreePrinter; +import java.awt.*; +import java.util.Random; + +public class TreapTree> { + + static final int MAX_RAND_NUM = 100; + + private Random random; + + public class Node implements TreePrinter.PrintableNode { + + // The value/data contained within the node + public T value; + + // The int priority of this node for Treap + public int priority; + + // The left and right references of this node + public Node left, right; + + public Node(T value, int priority) { + this.value = value; + this.left = this.right = null; + this.priority = priority; + } + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + + @Override + public TreePrinter.PrintableNode getLeft() { + return left; + } + + @Override + public TreePrinter.PrintableNode getRight() { + return right; + } + + @Override + public String getText() { + return value.toString(); + } + } + + // The root node of the Treap tree. + public Node root; + + // Tracks the number of nodes inside the tree + private int nodeCount = 0; + + public TreapTree() { + random = new Random(); + } + + // returns the number of nodes in the tree + public int size() { + return this.nodeCount; + } + + public boolean contains(T value) { + return contains(root, value); + } + + private boolean contains(Node node, T value) { + if (node == null) return false; + + int cmp = value.compareTo(node.getValue()); + + if (cmp < 0) return contains(node.left, value); + else if (cmp > 0) return contains(node.right, value); + else return true; + } + + public boolean isEmpty() { + return nodeCount == 0; + } + + public boolean insert(T val, int priority) { + if (val == null) { + throw new IllegalArgumentException("TreapTree does not allow null values"); + } + if (!contains(root, val)) { + root = insert(this.root, val, priority); + nodeCount++; + return true; + } + return false; + } + + public boolean insert(T val) { + return insert(val, random.nextInt(MAX_RAND_NUM)); + } + + private Node insert(Node node, T value, int priority) { + if (node == null) { + return new Node(value, priority); + } + + int cmp = value.compareTo(node.value); + + if (cmp < 0) { + node.left = insert(node.left, value, priority); + if (node.left.priority > node.priority) { + node = rightRotation(node); + } + } else if (cmp > 0) { + node.right = insert(node.right, value, priority); + if (node.right.priority > node.priority) { + node = leftRotation(node); + } + } + return node; + } + + private Node leftRotation(Node node) { + Node newParent = node.right; + node.right = newParent.left; + newParent.left = node; + return newParent; + } + + private Node rightRotation(Node node) { + Node newParent = node.left; + node.left = newParent.right; + newParent.right = node; + return newParent; + } + + public boolean remove(T elem) { + if (elem == null) return false; + if (contains(root, elem)) { + root = remove(root, elem); + nodeCount--; + return true; + } + return false; + } + + private Node remove(Node t, T x) { + if (t == null) { + return t; + } + + int cmp = x.compareTo(t.value); + if (cmp < 0) t.left = remove(t.left, x); + else if (cmp > 0) t.right = remove(t.right, x); + else { + if (t.left == null) return t.right; + if (t.right == null) return t.left; + + if (t.left.priority > t.right.priority) { + t = rightRotation(t); + t.right = remove(t.right, x); + } else { + t = leftRotation(t); + t.left = remove(t.left, x); + } + } + + return t; + } +} diff --git a/com/williamfiset/algorithms/datastructures/binarysearchtree/BinarySearchTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/binarysearchtree/BinarySearchTree.java similarity index 97% rename from com/williamfiset/algorithms/datastructures/binarysearchtree/BinarySearchTree.java rename to src/main/java/com/williamfiset/algorithms/datastructures/binarysearchtree/BinarySearchTree.java index 23ff3755c..d48e7f1a6 100644 --- a/com/williamfiset/algorithms/datastructures/binarysearchtree/BinarySearchTree.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/binarysearchtree/BinarySearchTree.java @@ -112,25 +112,14 @@ private Node remove(Node node, T elem) { // no subtree at all. In this situation just // swap the node we wish to remove with its right child. if (node.left == null) { - - Node rightChild = node.right; - - node.data = null; - node = null; - - return rightChild; + return node.right; // This is the case with only a left subtree or // no subtree at all. In this situation just // swap the node we wish to remove with its left child. } else if (node.right == null) { - Node leftChild = node.left; - - node.data = null; - node = null; - - return leftChild; + return node.left; // When removing a node from a binary tree with two links the // successor of the node being removed can either be the largest diff --git a/com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTree.java similarity index 95% rename from com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTree.java rename to src/main/java/com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTree.java index 117dab073..a2402ba85 100644 --- a/com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTree.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTree.java @@ -1,3 +1,7 @@ +// javac -d classes -sourcepath src/main/java +// src/main/java/com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTree.java +// java -cp classes com.williamfiset.algorithms.datastructures.binarysearchtree.SplayTreeRun + package com.williamfiset.algorithms.datastructures.binarysearchtree; import com.williamfiset.algorithms.datastructures.utils.TreePrinter; @@ -283,14 +287,14 @@ public static void main(String[] args) { SplayTree splayTree = new SplayTree<>(); Scanner sc = new Scanner(System.in); int[] data = {2, 29, 26, -1, 10, 0, 2, 11}; - + int c = 0; for (int i : data) { splayTree.insert(i); } - while (true) { - System.out.println("1. Insert 2. Delete 3. Search 4.FindMin 5.FindMax 6. PrintTree"); - int c = sc.nextInt(); + while (c != 7) { + System.out.println("1. Insert 2. Delete 3. Search 4.FindMin 5.FindMax 6. PrintTree 7. Exit"); + c = sc.nextInt(); switch (c) { case 1: System.out.println("Enter Data :"); @@ -313,6 +317,9 @@ public static void main(String[] args) { case 6: System.out.println(splayTree); break; + case 7: + sc.close(); + break; } } } diff --git a/com/williamfiset/algorithms/datastructures/binarysearchtree/TreeTraversalOrder.java b/src/main/java/com/williamfiset/algorithms/datastructures/binarysearchtree/TreeTraversalOrder.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/binarysearchtree/TreeTraversalOrder.java rename to src/main/java/com/williamfiset/algorithms/datastructures/binarysearchtree/TreeTraversalOrder.java diff --git a/com/williamfiset/algorithms/datastructures/bloomfilter/AnagramSet.java b/src/main/java/com/williamfiset/algorithms/datastructures/bloomfilter/AnagramSet.java similarity index 95% rename from com/williamfiset/algorithms/datastructures/bloomfilter/AnagramSet.java rename to src/main/java/com/williamfiset/algorithms/datastructures/bloomfilter/AnagramSet.java index e8febacd3..c20007f93 100644 --- a/com/williamfiset/algorithms/datastructures/bloomfilter/AnagramSet.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/bloomfilter/AnagramSet.java @@ -53,9 +53,9 @@ public AnagramSet(int[] mods) { // // Assuming all mods are primes each mod value will have a modular inverse for (int i = 0; i < N_HASHES; i++) { - java.math.BigInteger mod = new java.math.BigInteger(String.valueOf(MODS[i])); + java.math.BigInteger mod = java.math.BigInteger.valueOf(MODS[i]); for (int j = 0; j < PRIMES.length; j++) { - java.math.BigInteger prime = new java.math.BigInteger(String.valueOf(PRIMES[j])); + java.math.BigInteger prime = java.math.BigInteger.valueOf(PRIMES[j]); MOD_INVERSES[i][j] = prime.modInverse(mod).intValue(); } } diff --git a/com/williamfiset/algorithms/datastructures/bloomfilter/BloomFilter.java b/src/main/java/com/williamfiset/algorithms/datastructures/bloomfilter/BloomFilter.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/bloomfilter/BloomFilter.java rename to src/main/java/com/williamfiset/algorithms/datastructures/bloomfilter/BloomFilter.java diff --git a/com/williamfiset/algorithms/datastructures/bloomfilter/StringSet.java b/src/main/java/com/williamfiset/algorithms/datastructures/bloomfilter/StringSet.java similarity index 97% rename from com/williamfiset/algorithms/datastructures/bloomfilter/StringSet.java rename to src/main/java/com/williamfiset/algorithms/datastructures/bloomfilter/StringSet.java index f9b978628..29ae01f13 100644 --- a/com/williamfiset/algorithms/datastructures/bloomfilter/StringSet.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/bloomfilter/StringSet.java @@ -53,11 +53,11 @@ public StringSet(int[] mods, int maxLen) { rollingHashes = new long[N_HASHES]; bloomFilter = new BloomFilter(mods); - java.math.BigInteger bigAlpha = new java.math.BigInteger(String.valueOf(ALPHABET_SZ)); + java.math.BigInteger bigAlpha = java.math.BigInteger.valueOf(ALPHABET_SZ); // Assuming all mods are primes each mod value will have a modular inverse for (int i = 0; i < N_HASHES; i++) { - java.math.BigInteger mod = new java.math.BigInteger(String.valueOf(MODS[i])); + java.math.BigInteger mod = java.math.BigInteger.valueOf(MODS[i]); MOD_INVERSES[i] = bigAlpha.modInverse(mod).intValue(); } diff --git a/com/williamfiset/algorithms/datastructures/dynamicarray/IntArray.java b/src/main/java/com/williamfiset/algorithms/datastructures/dynamicarray/IntArray.java similarity index 99% rename from com/williamfiset/algorithms/datastructures/dynamicarray/IntArray.java rename to src/main/java/com/williamfiset/algorithms/datastructures/dynamicarray/IntArray.java index 3a0ce98c8..623a5a87f 100644 --- a/com/williamfiset/algorithms/datastructures/dynamicarray/IntArray.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/dynamicarray/IntArray.java @@ -54,7 +54,7 @@ public void set(int index, int elem) { arr[index] = elem; } - // An an element to this dynamic array + // Add an element to this dynamic array public void add(int elem) { if (len + 1 >= capacity) { if (capacity == 0) capacity = 1; diff --git a/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeQueryPointUpdate.java b/src/main/java/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeQueryPointUpdate.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeQueryPointUpdate.java rename to src/main/java/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeQueryPointUpdate.java diff --git a/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeUpdatePointQuery.java b/src/main/java/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeUpdatePointQuery.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeUpdatePointQuery.java rename to src/main/java/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeUpdatePointQuery.java diff --git a/com/williamfiset/algorithms/datastructures/fenwicktree/README.md b/src/main/java/com/williamfiset/algorithms/datastructures/fenwicktree/README.md similarity index 96% rename from com/williamfiset/algorithms/datastructures/fenwicktree/README.md rename to src/main/java/com/williamfiset/algorithms/datastructures/fenwicktree/README.md index 26f450d5d..ad3f5cece 100644 --- a/com/williamfiset/algorithms/datastructures/fenwicktree/README.md +++ b/src/main/java/com/williamfiset/algorithms/datastructures/fenwicktree/README.md @@ -18,7 +18,7 @@ ft.get(1); // 11 ft.get(4); // 6 ft.get(5); // 5 -ft.updateRange(3, 6, -20); // Add -20 to interval [1, 4] in O(log(n)) +ft.updateRange(3, 6, -20); // Add -20 to interval [3, 6] in O(log(n)) ft.get(3); // -7 ft.get(4); // -14 ft.get(5); // -15 diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/fibonacciheap/FibonacciHeap.java b/src/main/java/com/williamfiset/algorithms/datastructures/fibonacciheap/FibonacciHeap.java new file mode 100644 index 000000000..9b815dafd --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/fibonacciheap/FibonacciHeap.java @@ -0,0 +1,465 @@ +/** + * FibonacciHeap data structure implementation. + * + *

Disclaimer: implementation based on: + * http://staff.ustc.edu.cn/~csli/graduate/algorithms/book6/chap21.htm Implementation credits to the + * respective code owners. + */ +package com.williamfiset.algorithms.datastructures.fibonacciheap; + +import static java.lang.Math.floor; +import static java.lang.Math.log; +import static java.lang.Math.sqrt; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.Set; +import java.util.Stack; + +public final class FibonacciHeap implements Queue { + + private static final double LOG_PHI = log((1 + sqrt(5)) / 2); + + private final Set elementsIndex = new HashSet(); + + private final Comparator comparator; + + private int size = 0; + + private int trees = 0; + + private int markedNodes = 0; + + private FibonacciHeapNode minimumNode; + + public FibonacciHeap() { + this(null); + } + + public FibonacciHeap(/* @Nullable */ Comparator comparator) { + this.comparator = comparator; + } + + private void moveToRoot(FibonacciHeapNode node) { + // 8' if min[H] = NIL + if (isEmpty()) { + // then min[H] <- x + minimumNode = node; + } else { + // 7 concatenate the root list containing x with root list H + node.getLeft().setRight(node.getRight()); + node.getRight().setLeft(node.getLeft()); + + node.setLeft(minimumNode); + node.setRight(minimumNode.getRight()); + minimumNode.setRight(node); + node.getRight().setLeft(node); + + // 8'' if key[x] < key[min[H]] + if (compare(node, minimumNode) < 0) { + // 9 then min[H] <- x + minimumNode = node; + } + } + } + + public boolean add(E e) { + if (e == null) { + throw new IllegalArgumentException( + "Null elements not allowed in this FibonacciHeap implementation."); + } + + // 1-6 performed in the node initialization + FibonacciHeapNode node = new FibonacciHeapNode(e); + + // 7-9 performed in the #moveToRoot( FibonacciHeapNode ) method + moveToRoot(node); + + // 10 n[H] <- n[H] + 1 + size++; + + elementsIndex.add(e); + + return true; + } + + /** {@inheritDoc} */ + public boolean addAll(Collection c) { + for (E element : c) { + add(element); + } + + return true; + } + + /** {@inheritDoc} */ + public void clear() { + minimumNode = null; + size = 0; + trees = 0; + markedNodes = 0; + elementsIndex.clear(); + } + + /** {@inheritDoc} */ + public boolean contains(Object o) { + if (o == null) { + return false; + } + + return elementsIndex.contains(o); + } + + /** {@inheritDoc} */ + public boolean containsAll(Collection c) { + if (c == null) { + return false; + } + + for (Object o : c) { + if (!contains(o)) { + return false; + } + } + + return true; + } + + /** {@inheritDoc} */ + public boolean isEmpty() { + return minimumNode == null; + } + + /** {@inheritDoc} */ + public Iterator iterator() { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + public int size() { + return size; + } + + /** {@inheritDoc} */ + public Object[] toArray() { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + public T[] toArray(T[] a) { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + public E element() { + if (isEmpty()) { + throw new NoSuchElementException(); + } + return peek(); + } + + /** {@inheritDoc} */ + public boolean offer(E e) { + return add(e); + } + + /** {@inheritDoc} */ + public E peek() { + if (isEmpty()) { + return null; + } + + return minimumNode.getElement(); + } + + public E poll() { + // 2 if z ≠ NIL + if (isEmpty()) { + return null; + } + + // 1 z <- min[H] + FibonacciHeapNode z = minimumNode; + int numOfKids = z.getDegree(); + + FibonacciHeapNode x = z.getChild(); + FibonacciHeapNode tempRight; + + while (numOfKids > 0) { + // 3 for each child x of z + tempRight = x.getRight(); + + // 4 do add x to the root list of H + moveToRoot(x); + + // 5 p[x] <- NIL + x.setParent(null); + + x = tempRight; + numOfKids--; + } + + // 6 remove z from the root list of H + z.getLeft().setRight(z.getRight()); + z.getRight().setLeft(z.getLeft()); + + // 7 if z = right[z] + if (z == z.getRight()) { + // 8 min[H] <- NIL + minimumNode = null; + } else { + // 9 min[H] <- right[z] + minimumNode = z.getRight(); + // 10 CONSOLIDATE(H) + consolidate(); + } + + // 11 n[H] <- n[H] - 1 + size--; + + E minimum = z.getElement(); + elementsIndex.remove(minimum); + // 12 return z + return minimum; + } + + /** {@inheritDoc} */ + public E remove() { + // FIB-HEAP-EXTRACT-MIN(H) + + if (isEmpty()) { + throw new NoSuchElementException(); + } + + return poll(); + } + + private void consolidate() { + if (isEmpty()) { + return; + } + + // D( n[H] ) <= log_phi( n[H] ) + // -> log_phi( n[H] ) = log( n[H] ) / log( phi ) + // -> D( n[H] ) = log( n[H] ) / log( phi ) + int arraySize = ((int) floor(log(size) / LOG_PHI)); + + // 1 for i <- 0 to D(n[H]) + List> nodeSequence = new ArrayList>(arraySize); + for (int i = 0; i < arraySize; i++) { + // 2 do A[i] <- NIL + nodeSequence.add(i, null); + } + + int numRoots = 0; + + // 3 for each node x in the root list of H + // 4 do x ← w + FibonacciHeapNode x = minimumNode; + + if (x != null) { + numRoots++; + x = x.getRight(); + + while (x != minimumNode) { + numRoots++; + x = x.getRight(); + } + } + + while (numRoots > 0) { + // 5 d <- degree[x] + int degree = x.getDegree(); + FibonacciHeapNode next = x.getRight(); + + // 6 while A[d] != NIL + while (nodeSequence.get(degree) != null) { + // 7 do y <- A[d] + FibonacciHeapNode y = nodeSequence.get(degree); + + // 8 if key[x] > key[y] + if (compare(x, y) > 0) { + // 9 exchange x <-> y + FibonacciHeapNode pointer = y; + y = x; + x = pointer; + } + + // 10 FIB-HEAP-LINK(H,y,x) + link(y, x); + + // 11 A[d] <- NIL + nodeSequence.set(degree, null); + + // 12 d <- d + 1 + degree++; + } + + // 13 A[d] <- x + nodeSequence.set(degree, x); + + x = next; + numRoots--; + } + + // 14 min[H] <- NIL + minimumNode = null; + + // 15 for i <- 0 to D(n[H]) + for (FibonacciHeapNode pointer : nodeSequence) { + if (pointer == null) { + continue; + } + if (minimumNode == null) { + minimumNode = pointer; + } + + // 16 if A[i] != NIL + // We've got a live one, add it to root list. + if (minimumNode != null) { + // First remove node from root list. + moveToRoot(pointer); + } + } + } + + private void link(FibonacciHeapNode y, FibonacciHeapNode x) { + // 1 remove y from the root list of H + y.getLeft().setRight(y.getRight()); + y.getRight().setLeft(y.getLeft()); + + y.setParent(x); + + if (x.getChild() == null) { + // 2 make y a child of x, incrementing degree[x] + x.setChild(y); + y.setRight(y); + y.setLeft(y); + } else { + y.setLeft(x.getChild()); + y.setRight(x.getChild().getRight()); + x.getChild().setRight(y); + y.getRight().setLeft(y); + } + + x.incraeseDegree(); + + // 3 mark[y] <- FALSE + y.setMarked(false); + markedNodes++; + } + + private void cut(FibonacciHeapNode x, FibonacciHeapNode y) { + // add x to the root list of H + moveToRoot(x); + + // remove x from the child list of y, decrementing degree[y] + y.decraeseDegree(); + // p[x] <- NIL + x.setParent(null); + + // mark[x] <- FALSE + x.setMarked(false); + markedNodes--; + } + + private void cascadingCut(FibonacciHeapNode y) { + // z <- p[y] + FibonacciHeapNode z = y.getParent(); + + // if z != NIL + if (z != null) { + // if mark[y] = FALSE + if (!y.isMarked()) { + // then mark[y] TRUE + y.setMarked(true); + markedNodes++; + } else { + // else CUT(H,y,z) + cut(y, z); + // CASCADING-CUT(H,z) + cascadingCut(z); + } + } + } + + public int potential() { + return trees + 2 * markedNodes; + } + + private int compare(FibonacciHeapNode o1, FibonacciHeapNode o2) { + if (comparator != null) { + return comparator.compare(o1.getElement(), o2.getElement()); + } + @SuppressWarnings("unchecked") // it will throw a ClassCastException at runtime + Comparable o1Comparable = (Comparable) o1.getElement(); + return o1Comparable.compareTo(o2.getElement()); + } + + /** + * Creates a String representation of this Fibonacci heap. + * + * @return String of this. + */ + public String toString() { + if (minimumNode == null) { + return "FibonacciHeap=[]"; + } + + // create a new stack and put root on it + Stack> stack = new Stack>(); + stack.push(minimumNode); + + StringBuilder buf = new StringBuilder("FibonacciHeap=["); + + // do a simple breadth-first traversal on the tree + while (!stack.empty()) { + FibonacciHeapNode curr = stack.pop(); + buf.append(curr); + buf.append(", "); + + if (curr.getChild() != null) { + stack.push(curr.getChild()); + } + + FibonacciHeapNode start = curr; + curr = curr.getRight(); + + while (curr != start) { + buf.append(curr); + buf.append(", "); + + if (curr.getChild() != null) { + stack.push(curr.getChild()); + } + + curr = curr.getRight(); + } + } + + buf.append(']'); + + return buf.toString(); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/fibonacciheap/FibonacciNode.java b/src/main/java/com/williamfiset/algorithms/datastructures/fibonacciheap/FibonacciNode.java new file mode 100644 index 000000000..66e79a016 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/fibonacciheap/FibonacciNode.java @@ -0,0 +1,99 @@ +package com.williamfiset.algorithms.datastructures.fibonacciheap; + +// Credits to the respective owner for the code + +final class FibonacciHeapNode { + + private final E element; + + private FibonacciHeapNode parent; + + private FibonacciHeapNode left = this; + + private FibonacciHeapNode right = this; + + private FibonacciHeapNode child; + + private int degree; + + private boolean marked; + + public FibonacciHeapNode(E element) { + // 1 degree[x] ← 0 + degree = 0; + // 2 p[x] <- NIL + setParent(null); + // 3 child[x] <- NIL + setChild(null); + // 4 left[x] <- x + setLeft(this); + // 5 right[x] <- x + setRight(this); + // 6 mark[x] <- FALSE + setMarked(false); + + // set the adapted element + this.element = element; + } + + public FibonacciHeapNode getParent() { + return parent; + } + + public void setParent(FibonacciHeapNode parent) { + this.parent = parent; + } + + public FibonacciHeapNode getLeft() { + return left; + } + + public void setLeft(FibonacciHeapNode left) { + this.left = left; + } + + public FibonacciHeapNode getRight() { + return right; + } + + public void setRight(FibonacciHeapNode right) { + this.right = right; + } + + public FibonacciHeapNode getChild() { + return child; + } + + public void setChild(FibonacciHeapNode child) { + this.child = child; + } + + public int getDegree() { + return degree; + } + + public void incraeseDegree() { + degree++; + } + + public void decraeseDegree() { + degree--; + } + + public boolean isMarked() { + return marked; + } + + public void setMarked(boolean marked) { + this.marked = marked; + } + + public E getElement() { + return element; + } + + @Override + public String toString() { + return element.toString(); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/hashtable/DoubleHashingTestObject.java b/src/main/java/com/williamfiset/algorithms/datastructures/hashtable/DoubleHashingTestObject.java new file mode 100644 index 000000000..59a66ddd7 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/hashtable/DoubleHashingTestObject.java @@ -0,0 +1,100 @@ +/** + * The DoubleHashingTestObject is an object to test the functionality of the hash-table implemented + * with double hashing. + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.datastructures.hashtable; + +import java.util.Random; + +public class DoubleHashingTestObject implements SecondaryHash { + + private int hash, hash2; + Integer intData = null; + int[] vectorData = null; + String stringData = null; + + static long[] randomVector; + static Random R = new Random(); + static int MAX_VECTOR_SIZE = 10000; + + static { + randomVector = new long[MAX_VECTOR_SIZE]; + for (int i = 0; i < MAX_VECTOR_SIZE; i++) { + long val = R.nextLong(); + while (val % 2 == 0) val = R.nextLong(); + randomVector[i] = val; + } + } + + public DoubleHashingTestObject(int data) { + intData = data; + intHash(); + computeHash(); + } + + public DoubleHashingTestObject(int[] data) { + if (data == null) throw new IllegalArgumentException("Cannot be null"); + vectorData = data; + vectorHash(); + computeHash(); + } + + public DoubleHashingTestObject(String data) { + if (data == null) throw new IllegalArgumentException("Cannot be null"); + stringData = data; + stringHash(); + computeHash(); + } + + private void intHash() { + hash2 = intData; + } + + private void vectorHash() { + for (int i = 0; i < vectorData.length; i++) hash2 += randomVector[i] * vectorData[i]; + } + + private void stringHash() { + + // Multipicative hash function: + final int INITIAL_VALUE = 0; + int prime = 17; + int power = 1; + hash = INITIAL_VALUE; + for (int i = 0; i < stringData.length(); i++) { + int ch = stringData.charAt(i); + hash2 += power * ch; + power *= prime; + } + } + + private void computeHash() { + if (intData != null) hash = intData.hashCode(); + else if (stringData != null) hash = stringData.hashCode(); + else hash = java.util.Arrays.hashCode(vectorData); + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public int hashCode2() { + return hash2; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + else if (o instanceof DoubleHashingTestObject) { + DoubleHashingTestObject obj = (DoubleHashingTestObject) o; + if (hash != obj.hash) return false; + if (intData != null) return intData.equals(obj.intData); + if (vectorData != null) return java.util.Arrays.equals(vectorData, obj.vectorData); + return stringData.equals(obj.stringData); + } else return false; + } +} diff --git a/com/williamfiset/algorithms/datastructures/hashtable/HashTableDoubleHashing.java b/src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableDoubleHashing.java similarity index 91% rename from com/williamfiset/algorithms/datastructures/hashtable/HashTableDoubleHashing.java rename to src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableDoubleHashing.java index 22d5caa90..580f30f0b 100644 --- a/com/williamfiset/algorithms/datastructures/hashtable/HashTableDoubleHashing.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableDoubleHashing.java @@ -7,7 +7,6 @@ import java.math.BigInteger; -@SuppressWarnings("unchecked") public class HashTableDoubleHashing extends HashTableOpenAddressingBase { @@ -45,7 +44,7 @@ protected int probe(int x) { // probing so that all the cells can be reached. @Override protected void adjustCapacity() { - while (!(new BigInteger(String.valueOf(capacity)).isProbablePrime(20))) { + while (!(BigInteger.valueOf(capacity).isProbablePrime(20))) { capacity++; } } diff --git a/com/williamfiset/algorithms/datastructures/hashtable/HashTableLinearProbing.java b/src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableLinearProbing.java similarity index 97% rename from com/williamfiset/algorithms/datastructures/hashtable/HashTableLinearProbing.java rename to src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableLinearProbing.java index 43fb0073f..f77573338 100644 --- a/com/williamfiset/algorithms/datastructures/hashtable/HashTableLinearProbing.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableLinearProbing.java @@ -6,7 +6,6 @@ */ package com.williamfiset.algorithms.datastructures.hashtable; -@SuppressWarnings("unchecked") public class HashTableLinearProbing extends HashTableOpenAddressingBase { // This is the linear constant used in the linear probing, it can be diff --git a/com/williamfiset/algorithms/datastructures/hashtable/HashTableOpenAddressingBase.java b/src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableOpenAddressingBase.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/hashtable/HashTableOpenAddressingBase.java rename to src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableOpenAddressingBase.java diff --git a/com/williamfiset/algorithms/datastructures/hashtable/HashTableQuadraticProbing.java b/src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableQuadraticProbing.java similarity index 98% rename from com/williamfiset/algorithms/datastructures/hashtable/HashTableQuadraticProbing.java rename to src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableQuadraticProbing.java index c2046d5ce..ab9ae3c6b 100644 --- a/com/williamfiset/algorithms/datastructures/hashtable/HashTableQuadraticProbing.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableQuadraticProbing.java @@ -13,7 +13,6 @@ */ package com.williamfiset.algorithms.datastructures.hashtable; -@SuppressWarnings("unchecked") public class HashTableQuadraticProbing extends HashTableOpenAddressingBase { public HashTableQuadraticProbing() { diff --git a/com/williamfiset/algorithms/datastructures/hashtable/HashTableSeperateChaining.java b/src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableSeparateChaining.java similarity index 97% rename from com/williamfiset/algorithms/datastructures/hashtable/HashTableSeperateChaining.java rename to src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableSeparateChaining.java index 8e71bab85..5660a3a52 100644 --- a/com/williamfiset/algorithms/datastructures/hashtable/HashTableSeperateChaining.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableSeparateChaining.java @@ -33,7 +33,7 @@ public String toString() { } @SuppressWarnings("unchecked") -public class HashTableSeperateChaining implements Iterable { +public class HashTableSeparateChaining implements Iterable { private static final int DEFAULT_CAPACITY = 3; private static final double DEFAULT_LOAD_FACTOR = 0.75; @@ -42,16 +42,16 @@ public class HashTableSeperateChaining implements Iterable { private int capacity, threshold, size = 0; private LinkedList>[] table; - public HashTableSeperateChaining() { + public HashTableSeparateChaining() { this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR); } - public HashTableSeperateChaining(int capacity) { + public HashTableSeparateChaining(int capacity) { this(capacity, DEFAULT_LOAD_FACTOR); } // Designated constructor - public HashTableSeperateChaining(int capacity, double maxLoadFactor) { + public HashTableSeparateChaining(int capacity, double maxLoadFactor) { if (capacity < 0) throw new IllegalArgumentException("Illegal capacity"); if (maxLoadFactor <= 0 || Double.isNaN(maxLoadFactor) || Double.isInfinite(maxLoadFactor)) throw new IllegalArgumentException("Illegal maxLoadFactor"); diff --git a/com/williamfiset/algorithms/datastructures/hashtable/SecondaryHash.java b/src/main/java/com/williamfiset/algorithms/datastructures/hashtable/SecondaryHash.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/hashtable/SecondaryHash.java rename to src/main/java/com/williamfiset/algorithms/datastructures/hashtable/SecondaryHash.java diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/kdtree/GeneralKDTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/kdtree/GeneralKDTree.java new file mode 100644 index 000000000..150a78703 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/kdtree/GeneralKDTree.java @@ -0,0 +1,197 @@ +/** + * A generic k-dimensional tree implementation. + * + * @author David Jagnow + */ +package com.williamfiset.algorithms.datastructures.kdtree; + +public class GeneralKDTree> { + + private int k; + private KDNode root; + + /* KDTREE DEFINITION */ + public GeneralKDTree(int dimensions) { + if (dimensions <= 0) + throw new IllegalArgumentException("Error: GeneralKDTree must have positive dimensions"); + k = dimensions; + root = null; + } + + /* ATTRIBUTE METHODS */ + public int getDimensions() { + return k; + } + + public T[] getRootPoint() { + return (root == null) ? null : root.point; + } + + /* TREE METHODS */ + // Insert Method + public void insert(T[] toAdd) { + // Create the new node and make it the root if the root is null + KDNode newNode = new KDNode(toAdd); + if (root == null) root = newNode; + // Otherwise, insert the node recursively + else insertRecursive(newNode, root, 0); + } + + private void insertRecursive(KDNode toAdd, KDNode curr, int axis) { + // If the new point should go to the left, go left and insert where a spot is available + if ((toAdd.point[axis]).compareTo(curr.point[axis]) < 0) { + if (curr.left == null) curr.left = toAdd; + else insertRecursive(toAdd, curr.left, (++axis) % k); + } + // Otherwise, go right and insert where a spot is available + else { + if (curr.right == null) curr.right = toAdd; + else insertRecursive(toAdd, curr.right, (++axis) % k); + } + } + + // Search Method + public boolean search(T[] element) { + KDNode elemNode = new KDNode(element); + return searchRecursive(elemNode, root, 0); + } + + private boolean searchRecursive(KDNode toSearch, KDNode curr, int axis) { + // If the search fails, the point is not in the tree + if (curr == null) return false; + // If the search succeeds, the point is in the tree + if ((curr.point).equals(toSearch.point)) return true; + // Otherwise, go where the point would go if it was inserted into the tree + KDNode nextNode = + ((toSearch.point[axis]).compareTo(curr.point[axis]) < 0) ? curr.left : curr.right; + return searchRecursive(toSearch, nextNode, (++axis) % k); + } + + // FindMin Method + public T[] findMin(int dim) { + if (dim < 0 || dim >= k) throw new IllegalArgumentException("Error: Dimension out of bounds"); + return findMinRecursive(dim, root, 0); + } + + private T[] findMinRecursive(int dim, KDNode curr, int axis) { + // If nothing is found, return nothing + if (curr == null) return null; + // If the axis and dimension match, follow typical search procedure + if (dim == axis) { + if (curr.left == null) return curr.point; + return findMinRecursive(dim, curr.left, (axis + 1) % k); + } + // If there are no children, return what you have + if (curr.left == null && curr.right == null) return curr.point; + // If there is at least one child, search the children of the current node + T[] leftSubTree = findMinRecursive(dim, curr.left, (axis + 1) % k); + T[] rightSubTree = findMinRecursive(dim, curr.right, (axis + 1) % k); + T[] minSubTree; + // If a child is null, pick the non-null child + if (leftSubTree == null || rightSubTree == null) { + minSubTree = (rightSubTree == null) ? leftSubTree : rightSubTree; + } + // Otherwise, compare them + else { + minSubTree = + ((leftSubTree[dim]).compareTo(rightSubTree[dim]) < 0) ? leftSubTree : rightSubTree; + } + // Compare with the value in the current node and return the point with the smallest value in + // the given dimension + T[] min = ((curr.point[dim]).compareTo(minSubTree[dim]) < 0) ? curr.point : minSubTree; + return min; + } + + // Remove Method + public T[] delete(T[] toRemove) { + // Return nothing if the point is not present + if (!search(toRemove)) return null; + // Delete and return root if it should be removed + if (toRemove.equals(root.point)) return deleteRecursiveRoot(); + // Create the comparison point to delete and remove recursively + KDNode removeElem = new KDNode(toRemove); + return deleteRecursiveSearch(removeElem, root, 0); + } + + private T[] deleteRecursiveRoot() { + // Store the point to remove + T[] replacedPoint = root.point; + // Set the root to null if it has no children + if (root.left == null && root.right == null) { + root = null; + return replacedPoint; + } + // If a right child exists, find a minimum to replace the root point + else if (root.right != null) { + root.point = findMinRecursive(0, root.right, 1 % k); + deleteRecursiveSearch(new KDNode(root.point), root, 0); + return replacedPoint; + } + // Otherwise, get the minimum from the left subtree and make it the right subtree + else { + root.point = findMinRecursive(0, root.left, 1 % k); + deleteRecursiveSearch(new KDNode(root.point), root, 0); + root.right = root.left; + root.left = null; + return replacedPoint; + } + } + + private T[] deleteRecursiveSearch(KDNode toRemove, KDNode curr, int axis) { + // If the node to remove is a direct child, extract it and remove it + if (curr.right != null && (toRemove.point).equals(curr.right.point)) { + T[] removed = deleteRecursiveExtract(toRemove, curr.right, (axis + 1) % k); + if (removed == null) curr.right = null; + return toRemove.point; + } else if (curr.left != null && (toRemove.point).equals(curr.left.point)) { + T[] removed = deleteRecursiveExtract(toRemove, curr.left, (axis + 1) % k); + if (removed == null) curr.left = null; + return toRemove.point; + } + // Otherwise, search again at the child that the node would be a child of + else { + KDNode nextNode = + ((toRemove.point[axis]).compareTo(curr.point[axis]) < 0) ? curr.left : curr.right; + return deleteRecursiveSearch(toRemove, nextNode, (axis + 1) % k); + } + } + + private T[] deleteRecursiveExtract(KDNode toRemove, KDNode curr, int axis) { + // Store the point to remove + T[] replacedPoint = curr.point; + // Set the node to null if it has no children + if (curr.left == null && curr.right == null) return null; + // If a right child exists, find a minimum to replace the root point + else if (curr.right != null) { + curr.point = findMinRecursive(axis, curr.right, (axis + 1) % k); + deleteRecursiveSearch(new KDNode(curr.point), curr, axis); + return replacedPoint; + } + // Otherwise, get the minimum from the left subtree and make it the right subtree + else { + curr.point = findMinRecursive(axis, curr.left, (axis + 1) % k); + deleteRecursiveSearch(new KDNode(curr.point), curr, axis); + curr.right = curr.left; + curr.left = null; + return replacedPoint; + } + } + + /* KDTREE NODE DEFINITION */ + private class KDNode> { + + private E[] point; + private KDNode left; + private KDNode right; + + public KDNode(E[] coords) { + if (coords == null) throw new IllegalArgumentException("Error: Null coordinate set passed"); + if (coords.length != k) + throw new IllegalArgumentException( + "Error: Expected " + k + "dimensions, but given " + coords.length); + point = coords; + left = null; + right = null; + } + } +} diff --git a/com/williamfiset/algorithms/datastructures/linkedlist/DoublyLinkedList.java b/src/main/java/com/williamfiset/algorithms/datastructures/linkedlist/DoublyLinkedList.java similarity index 90% rename from com/williamfiset/algorithms/datastructures/linkedlist/DoublyLinkedList.java rename to src/main/java/com/williamfiset/algorithms/datastructures/linkedlist/DoublyLinkedList.java index afcf90c66..e8074bdef 100644 --- a/com/williamfiset/algorithms/datastructures/linkedlist/DoublyLinkedList.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/linkedlist/DoublyLinkedList.java @@ -77,6 +77,32 @@ public void addFirst(T elem) { size++; } + // Add an element at a specified index + public void addAt(int index, T data) throws Exception { + if (index < 0 || index > size) { + throw new Exception("Illegal Index"); + } + if (index == 0) { + addFirst(data); + return; + } + + if (index == size) { + addLast(data); + return; + } + + Node temp = head; + for (int i = 0; i < index - 1; i++) { + temp = temp.next; + } + Node newNode = new Node<>(data, temp, temp.next); + temp.next.prev = newNode; + temp.next = newNode; + + size++; + } + // Check the value of the first node if it exists, O(1) public T peekFirst() { if (isEmpty()) throw new RuntimeException("Empty list"); @@ -171,11 +197,11 @@ public T removeAt(int index) { trav = trav.next; } // Search from the back of the list - } else + } else { for (i = size - 1, trav = tail; i != index; i--) { trav = trav.prev; } - + } return remove(trav); } @@ -216,13 +242,13 @@ public int indexOf(Object obj) { } } // Search for non null object - } else + } else { for (; trav != null; trav = trav.next, index++) { if (obj.equals(trav.data)) { return index; } } - + } return -1; } @@ -261,7 +287,10 @@ public String toString() { sb.append("[ "); Node trav = head; while (trav != null) { - sb.append(trav.data + ", "); + sb.append(trav.data); + if (trav.next != null) { + sb.append(", "); + } trav = trav.next; } sb.append(" ]"); diff --git a/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeap.java b/src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeap.java similarity index 85% rename from com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeap.java rename to src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeap.java index 750af44d3..9d6789d1b 100644 --- a/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeap.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeap.java @@ -11,12 +11,6 @@ public class BinaryHeap> { - // The number of elements currently inside the heap - private int heapSize = 0; - - // The internal capacity of the heap - private int heapCapacity = 0; - // A dynamic list to track the elements inside the heap private List heap = null; @@ -34,8 +28,8 @@ public BinaryHeap(int sz) { // http://www.cs.umd.edu/~meesh/351/mount/lectures/lect14-heapsort-analysis-part.pdf public BinaryHeap(T[] elems) { - heapSize = heapCapacity = elems.length; - heap = new ArrayList(heapCapacity); + int heapSize = elems.length; + heap = new ArrayList(heapSize); // Place all element in heap for (int i = 0; i < heapSize; i++) heap.add(elems[i]); @@ -44,26 +38,32 @@ public BinaryHeap(T[] elems) { for (int i = Math.max(0, (heapSize / 2) - 1); i >= 0; i--) sink(i); } - // Priority queue construction, O(nlog(n)) + // Priority queue construction, O(n) public BinaryHeap(Collection elems) { - this(elems.size()); - for (T elem : elems) add(elem); + + int heapSize = elems.size(); + heap = new ArrayList(heapSize); + + // Add all elements of the given collection to the heap + heap.addAll(elems); + + // Heapify process, O(n) + for (int i = Math.max(0, (heapSize / 2) - 1); i >= 0; i--) sink(i); } // Returns true/false depending on if the priority queue is empty public boolean isEmpty() { - return heapSize == 0; + return size() == 0; } // Clears everything inside the heap, O(n) public void clear() { - for (int i = 0; i < heapCapacity; i++) heap.set(i, null); - heapSize = 0; + heap.clear(); } // Return the size of the heap public int size() { - return heapSize; + return heap.size(); } // Returns the value of the element with the lowest @@ -82,7 +82,7 @@ public T poll() { // Test if an element is in heap, O(n) public boolean contains(T elem) { // Linear scan to check containment - for (int i = 0; i < heapSize; i++) if (heap.get(i).equals(elem)) return true; + for (int i = 0; i < size(); i++) if (heap.get(i).equals(elem)) return true; return false; } @@ -92,15 +92,10 @@ public void add(T elem) { if (elem == null) throw new IllegalArgumentException(); - if (heapSize < heapCapacity) { - heap.set(heapSize, elem); - } else { - heap.add(elem); - heapCapacity++; - } + heap.add(elem); - swim(heapSize); - heapSize++; + int indexOfLastElem = size() - 1; + swim(indexOfLastElem); } // Tests if the value of node i <= node j @@ -131,6 +126,7 @@ private void swim(int k) { // Top down node sink, O(log(n)) private void sink(int k) { + int heapSize = size(); while (true) { int left = 2 * k + 1; // Left node int right = 2 * k + 2; // Right node @@ -163,7 +159,7 @@ private void swap(int i, int j) { public boolean remove(T element) { if (element == null) return false; // Linear removal via search, O(n) - for (int i = 0; i < heapSize; i++) { + for (int i = 0; i < size(); i++) { if (element.equals(heap.get(i))) { removeAt(i); return true; @@ -176,15 +172,15 @@ public boolean remove(T element) { private T removeAt(int i) { if (isEmpty()) return null; - heapSize--; + int indexOfLastElem = size() - 1; T removed_data = heap.get(i); - swap(i, heapSize); + swap(i, indexOfLastElem); // Obliterate the value - heap.set(heapSize, null); + heap.remove(indexOfLastElem); // Check if the last element was removed - if (i == heapSize) return removed_data; + if (i == indexOfLastElem) return removed_data; T elem = heap.get(i); // Try sinking element @@ -201,6 +197,7 @@ private T removeAt(int i) { // Called this method with k=0 to start at the root public boolean isMinHeap(int k) { // If we are outside the bounds of the heap return true + int heapSize = size(); if (k >= heapSize) return true; int left = 2 * k + 1; diff --git a/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapQuickRemovals.java b/src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapQuickRemovals.java similarity index 91% rename from com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapQuickRemovals.java rename to src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapQuickRemovals.java index 3c914fd57..88029e1f7 100644 --- a/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapQuickRemovals.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapQuickRemovals.java @@ -16,12 +16,6 @@ public class BinaryHeapQuickRemovals> { - // The number of elements currently inside the heap - private int heapSize = 0; - - // The internal capacity of the heap - private int heapCapacity = 0; - // A dynamic list to track the elements inside the heap private List heap = null; @@ -45,8 +39,8 @@ public BinaryHeapQuickRemovals(int sz) { // http://www.cs.umd.edu/~meesh/351/mount/lectures/lect14-heapsort-analysis-part.pdf public BinaryHeapQuickRemovals(T[] elems) { - heapSize = heapCapacity = elems.length; - heap = new ArrayList(heapCapacity); + int heapSize = elems.length; + heap = new ArrayList(heapSize); // Place all element in heap for (int i = 0; i < heapSize; i++) { @@ -66,19 +60,18 @@ public BinaryHeapQuickRemovals(Collection elems) { // Returns true/false depending on if the priority queue is empty public boolean isEmpty() { - return heapSize == 0; + return size() == 0; } // Clears everything inside the heap, O(n) public void clear() { - for (int i = 0; i < heapCapacity; i++) heap.set(i, null); - heapSize = 0; + heap.clear(); map.clear(); } // Return the size of the heap public int size() { - return heapSize; + return heap.size(); } // Returns the value of the element with the lowest @@ -115,17 +108,11 @@ public void add(T elem) { if (elem == null) throw new IllegalArgumentException(); - if (heapSize < heapCapacity) { - heap.set(heapSize, elem); - } else { - heap.add(elem); - heapCapacity++; - } - - mapAdd(elem, heapSize); + heap.add(elem); + int indexOfLastElem = size() - 1; + mapAdd(elem, indexOfLastElem); - swim(heapSize); - heapSize++; + swim(indexOfLastElem); } // Tests if the value of node i <= node j @@ -158,6 +145,7 @@ private void swim(int k) { // Top down node sink, O(log(n)) private void sink(int k) { + int heapSize = size(); while (true) { @@ -215,16 +203,16 @@ private T removeAt(int i) { if (isEmpty()) return null; - heapSize--; + int indexOfLastElem = size() - 1; T removed_data = heap.get(i); - swap(i, heapSize); + swap(i, indexOfLastElem); // Obliterate the value - heap.set(heapSize, null); - mapRemove(removed_data, heapSize); + heap.remove(indexOfLastElem); + mapRemove(removed_data, indexOfLastElem); // Removed last element - if (i == heapSize) return removed_data; + if (i == indexOfLastElem) return removed_data; T elem = heap.get(i); @@ -244,6 +232,7 @@ private T removeAt(int i) { public boolean isMinHeap(int k) { // If we are outside the bounds of the heap return true + int heapSize = size(); if (k >= heapSize) return true; int left = 2 * k + 1; diff --git a/com/williamfiset/algorithms/datastructures/priorityqueue/MinDHeap.java b/src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinDHeap.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/priorityqueue/MinDHeap.java rename to src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinDHeap.java diff --git a/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedBinaryHeap.java b/src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedBinaryHeap.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedBinaryHeap.java rename to src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedBinaryHeap.java diff --git a/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedDHeap.java b/src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedDHeap.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedDHeap.java rename to src/main/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedDHeap.java diff --git a/com/williamfiset/algorithms/datastructures/quadtree/QuadTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/quadtree/QuadTree.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/quadtree/QuadTree.java rename to src/main/java/com/williamfiset/algorithms/datastructures/quadtree/QuadTree.java diff --git a/com/williamfiset/algorithms/datastructures/quadtree/README.md b/src/main/java/com/williamfiset/algorithms/datastructures/quadtree/README.md similarity index 100% rename from com/williamfiset/algorithms/datastructures/quadtree/README.md rename to src/main/java/com/williamfiset/algorithms/datastructures/quadtree/README.md diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/queue/ArrayQueue.java b/src/main/java/com/williamfiset/algorithms/datastructures/queue/ArrayQueue.java new file mode 100644 index 000000000..82be4bd4c --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/queue/ArrayQueue.java @@ -0,0 +1,73 @@ +package com.williamfiset.algorithms.datastructures.queue; + +/** + * Besides the Generics, the loss of property of size is another difference between ArrayQueue and + * IntQueue. The size of ArrayQueue is calculated by the formula, as are empty status and full + * status. + * + *

ArrayQueue maximum size is data.length - 1. The place of the variable rear is always in front + * of the variable front logistically if regard the data array as circular. so the number of states + * of the combination of rear and front is the length of the data array. And one of the total states + * is used to be the judge if the queue is empty or full. + * + * @author liujingkun, liujkon@gmail.com + */ +public class ArrayQueue implements Queue { + private Object[] data; + private int front; + private int rear; + + public ArrayQueue(int capacity) { + // ArrayQueue maximum size is data.length - 1. + data = new Object[capacity + 1]; + front = 0; + rear = 0; + } + + @Override + public void offer(T elem) { + if (isFull()) { + throw new RuntimeException("Queue is full"); + } + data[rear++] = elem; + rear = adjustIndex(rear, data.length); + } + + @Override + @SuppressWarnings("unchecked") + public T poll() { + if (isEmpty()) { + throw new RuntimeException("Queue is empty"); + } + front = adjustIndex(front, data.length); + return (T) data[front++]; + } + + @Override + @SuppressWarnings("unchecked") + public T peek() { + if (isEmpty()) { + throw new RuntimeException("Queue is empty"); + } + front = adjustIndex(front, data.length); + return (T) data[front]; + } + + @Override + public int size() { + return adjustIndex(rear + data.length - front, data.length); + } + + @Override + public boolean isEmpty() { + return rear == front; + } + + public boolean isFull() { + return (front + data.length - rear) % data.length == 1; + } + + private int adjustIndex(int index, int size) { + return index >= size ? index - size : index; + } +} diff --git a/com/williamfiset/algorithms/datastructures/queue/IntQueue.java b/src/main/java/com/williamfiset/algorithms/datastructures/queue/IntQueue.java similarity index 56% rename from com/williamfiset/algorithms/datastructures/queue/IntQueue.java rename to src/main/java/com/williamfiset/algorithms/datastructures/queue/IntQueue.java index 270b98131..956fe39e7 100644 --- a/com/williamfiset/algorithms/datastructures/queue/IntQueue.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/queue/IntQueue.java @@ -5,50 +5,66 @@ * is you need to know an upper bound on the number of elements that will be inside the queue at any * given time for this queue to work. * - * @author William Fiset, william.alexandre.fiset@gmail.com + * @author William Fiset, william.alexandre.fiset@gmail.com, liujingkun, liujkon@gmail.com */ package com.williamfiset.algorithms.datastructures.queue; -public class IntQueue { +public class IntQueue implements Queue { - private int[] ar; - private int front, end, sz; + private int[] data; + private int front, end; + private int size; // maxSize is the maximum number of items // that can be in the queue at any given time public IntQueue(int maxSize) { - front = end = 0; - sz = maxSize + 1; - ar = new int[sz]; + front = end = size = 0; + data = new int[maxSize]; } // Return true/false on whether the queue is empty public boolean isEmpty() { - return front == end; + return size == 0; } // Return the number of elements inside the queue public int size() { - if (front > end) return (end + sz - front); - return end - front; + return size; } - public int peek() { - return ar[front]; + @Override + public Integer peek() { + if (isEmpty()) { + throw new RuntimeException("Queue is empty"); + } + front = front % data.length; + return data[front]; + } + + public boolean isFull() { + return size == data.length; } // Add an element to the queue - public void enqueue(int value) { - ar[end] = value; - if (++end == sz) end = 0; - if (end == front) throw new RuntimeException("Queue too small!"); + @Override + public void offer(Integer value) { + if (isFull()) { + throw new RuntimeException("Queue too small!"); + } + data[end++] = value; + size++; + end = end % data.length; } - // Make sure you check is the queue is not empty before calling dequeue! - public int dequeue() { - int ret_val = ar[front]; - if (++front == sz) front = 0; - return ret_val; + // Make sure you check is the queue is not empty before calling poll! + @Override + public Integer poll() { + if (size == 0) { + throw new RuntimeException("Queue is empty"); + } + size--; + front = front % data.length; + return data[front++]; } // Example usage @@ -56,31 +72,31 @@ public static void main(String[] args) { IntQueue q = new IntQueue(5); - q.enqueue(1); - q.enqueue(2); - q.enqueue(3); - q.enqueue(4); - q.enqueue(5); + q.offer(1); + q.offer(2); + q.offer(3); + q.offer(4); + q.offer(5); - System.out.println(q.dequeue()); // 1 - System.out.println(q.dequeue()); // 2 - System.out.println(q.dequeue()); // 3 - System.out.println(q.dequeue()); // 4 + System.out.println(q.poll()); // 1 + System.out.println(q.poll()); // 2 + System.out.println(q.poll()); // 3 + System.out.println(q.poll()); // 4 System.out.println(q.isEmpty()); // false - q.enqueue(1); - q.enqueue(2); - q.enqueue(3); + q.offer(1); + q.offer(2); + q.offer(3); - System.out.println(q.dequeue()); // 5 - System.out.println(q.dequeue()); // 1 - System.out.println(q.dequeue()); // 2 - System.out.println(q.dequeue()); // 3 + System.out.println(q.poll()); // 5 + System.out.println(q.poll()); // 1 + System.out.println(q.poll()); // 2 + System.out.println(q.poll()); // 3 System.out.println(q.isEmpty()); // true - benchMarkTest(); + // benchMarkTest(); } // BenchMark IntQueue vs ArrayDeque. @@ -91,8 +107,8 @@ private static void benchMarkTest() { // IntQueue times at around 0.0324 seconds long start = System.nanoTime(); - for (int i = 0; i < n; i++) intQ.enqueue(i); - for (int i = 0; i < n; i++) intQ.dequeue(); + for (int i = 0; i < n; i++) intQ.offer(i); + for (int i = 0; i < n; i++) intQ.poll(); long end = System.nanoTime(); System.out.println("IntQueue Time: " + (end - start) / 1e9); diff --git a/com/williamfiset/algorithms/datastructures/queue/Queue.java b/src/main/java/com/williamfiset/algorithms/datastructures/queue/LinkedQueue.java similarity index 90% rename from com/williamfiset/algorithms/datastructures/queue/Queue.java rename to src/main/java/com/williamfiset/algorithms/datastructures/queue/LinkedQueue.java index 09813b4c9..14830d203 100644 --- a/com/williamfiset/algorithms/datastructures/queue/Queue.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/queue/LinkedQueue.java @@ -5,13 +5,13 @@ */ package com.williamfiset.algorithms.datastructures.queue; -public class Queue implements Iterable { +public class LinkedQueue implements Iterable, Queue { private java.util.LinkedList list = new java.util.LinkedList(); - public Queue() {} + public LinkedQueue() {} - public Queue(T firstElem) { + public LinkedQueue(T firstElem) { offer(firstElem); } diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/queue/Queue.java b/src/main/java/com/williamfiset/algorithms/datastructures/queue/Queue.java new file mode 100644 index 000000000..8ab15b8dc --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/queue/Queue.java @@ -0,0 +1,17 @@ +package com.williamfiset.algorithms.datastructures.queue; + +/** + * @author liujingkun, liujkon@gmail.com + * @param the type of element held int the queue + */ +public interface Queue { + public void offer(T elem); + + public T poll(); + + public T peek(); + + public int size(); + + public boolean isEmpty(); +} diff --git a/com/williamfiset/algorithms/datastructures/segmenttree/CompactSegmentTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/CompactSegmentTree.java similarity index 70% rename from com/williamfiset/algorithms/datastructures/segmenttree/CompactSegmentTree.java rename to src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/CompactSegmentTree.java index d7352d866..eac9e5960 100644 --- a/com/williamfiset/algorithms/datastructures/segmenttree/CompactSegmentTree.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/CompactSegmentTree.java @@ -7,6 +7,7 @@ package com.williamfiset.algorithms.datastructures.segmenttree; public class CompactSegmentTree { + private int N; // Let UNIQUE be a value which does NOT @@ -23,6 +24,7 @@ public CompactSegmentTree(int size) { public CompactSegmentTree(long[] values) { this(values.length); + // TODO(william): Implement smarter construction. for (int i = 0; i < N; i++) modify(i, values[i]); } @@ -35,9 +37,9 @@ private long function(long a, long b) { if (a == UNIQUE) return b; else if (b == UNIQUE) return a; - // return a + b; // sum over a range + return a + b; // sum over a range // return (a > b) ? a : b; // maximum value over a range - return (a < b) ? a : b; // minimum value over a range + // return (a < b) ? a : b; // minimum value over a range // return a * b; // product over a range (watch out for overflow!) } @@ -61,4 +63,25 @@ public long query(int l, int r) { } return res; } + + public static void main(String[] args) { + // exmaple1(); + example2(); + } + + private static void example1() { + long[] values = new long[] {3, 0, 8, 9, 8, 2, 5, 3, 7, 1}; + CompactSegmentTree st = new CompactSegmentTree(values); + System.out.println(java.util.Arrays.toString(st.tree)); + } + + private static void example2() { + long[] values = new long[] {1, 1, 1, 1, 1, 1}; + CompactSegmentTree st = new CompactSegmentTree(values); + System.out.println(java.util.Arrays.toString(st.tree)); + + System.out.println(st.query(0, 6)); // 6 + System.out.println(st.query(1, 5)); // 4 + System.out.println(st.query(0, 2)); // 2 + } } diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTree.java new file mode 100644 index 000000000..faba5b78e --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTree.java @@ -0,0 +1,558 @@ +/** + * A generic segment tree implementation that supports several range update and aggregation + * functions. This implementation of the segment tree differs from the `GenericSegmentTree2` impl in + * that it stores the segment tree information inside multiple arrays for node. + * + *

Run with: ./gradlew run -Palgorithm=datastructures.segmenttree.GenericSegmentTree + * + *

Several thanks to cp-algorithms for their great article on segment trees: + * https://cp-algorithms.com/data_structures/segment_tree.html + * + *

NOTE: This file is still a WIP + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +import java.util.function.BinaryOperator; + +public class GenericSegmentTree { + + // The type of segment combination function to use + public static enum SegmentCombinationFn { + SUM, + MIN, + MAX, + GCD, + PRODUCT + } + + // When updating the value of a specific index position, or a range of values, + // modify the affected values using the following function: + public static enum RangeUpdateFn { + // When a range update is issued, assign all the values in the range [l, r] to be `x` + ASSIGN, + // When a range update is issued, add a value of `x` to all the elements in the range [l, r] + ADDITION, + // When a range update is issued, multiply all elements in the range [l, r] by a value of `x` + MULTIPLICATION + } + + // The number of elements in the original input values array. + private int n; + + // The segment tree represented as a binary tree of ranges where t[0] is the + // root node and the left and right children of node i are i*2+1 and i*2+2. + private Long[] t; + + // The delta values associates with each segment. Used for lazy propagation + // when doing range updates. + private Long[] lazy; + + // The chosen range combination function + private BinaryOperator combinationFn; + + // The Range Update Function (RUF) interface. + private interface Ruf { + // base = the existing value + // tl, tr = the index value of the left/right endpoints, i.e: [tl, tr] + // delta = the delta value + // TODO(william): reorder to be base, delta, tl, tr + Long apply(Long base, long tl, long tr, Long delta); + } + + // The Range Update Function (RUF) that chooses how a lazy delta value is + // applied to a segment. + private Ruf ruf; + + // The Lazy Range Update Function (LRUF) associated with the RUF. How you + // propagate the lazy delta values is sometimes different than how you apply + // them to the current segment (but most of the time the RUF = LRUF). + private Ruf lruf; + + private long safeSum(Long a, Long b) { + if (a == null) a = 0L; + if (b == null) b = 0L; + return a + b; + } + + private Long safeMul(Long a, Long b) { + if (a == null) a = 1L; + if (b == null) b = 1L; + return a * b; + } + + private Long safeMin(Long a, Long b) { + if (a == null) return b; + if (b == null) return a; + return Math.min(a, b); + } + + private Long safeMax(Long a, Long b) { + if (a == null) return b; + if (b == null) return a; + return Math.max(a, b); + } + + private BinaryOperator sumCombinationFn = (a, b) -> safeSum(a, b); + private BinaryOperator minCombinationFn = (a, b) -> safeMin(a, b); + private BinaryOperator maxCombinationFn = (a, b) -> safeMax(a, b); + private BinaryOperator productCombinationFn = (a, b) -> safeMul(a, b); + private BinaryOperator gcdCombinationFn = + (a, b) -> { + if (a == null) return b; + if (b == null) return a; + long gcd = a; + while (b != 0) { + gcd = b; + b = a % b; + a = gcd; + } + return Math.abs(gcd); + }; + + // TODO(william): Document the justification for each function below + + // Range update functions + private Ruf minQuerySumUpdate = (b, tl, tr, d) -> safeSum(b, d); + private Ruf lminQuerySumUpdate = (b, tl, tr, d) -> safeSum(b, d); + + // TODO(issue/208): Can negative multiplication updates be supported? + private Ruf minQueryMulUpdate = (b, tl, tr, d) -> safeMul(b, d); + private Ruf lminQueryMulUpdate = (b, tl, tr, d) -> safeMul(b, d); + + private Ruf minQueryAssignUpdate = (b, tl, tr, d) -> d; + private Ruf lminQueryAssignUpdate = (b, tl, tr, d) -> d; + + private Ruf maxQuerySumUpdate = (b, tl, tr, d) -> safeSum(b, d); + private Ruf lmaxQuerySumUpdate = (b, tl, tr, d) -> safeSum(b, d); + + // TODO(issue/208): Can negative multiplication updates be supported? + private Ruf maxQueryMulUpdate = (b, tl, tr, d) -> safeMul(b, d); + private Ruf lmaxQueryMulUpdate = (b, tl, tr, d) -> safeMul(b, d); + + private Ruf maxQueryAssignUpdate = (b, tl, tr, d) -> d; + private Ruf lmaxQueryAssignUpdate = (b, tl, tr, d) -> d; + + private Ruf sumQuerySumUpdate = (b, tl, tr, d) -> b + (tr - tl + 1) * d; + private Ruf lsumQuerySumUpdate = (b, tl, tr, d) -> safeSum(b, d); + + private Ruf sumQueryMulUpdate = (b, tl, tr, d) -> safeMul(b, d); + private Ruf lsumQueryMulUpdate = (b, tl, tr, d) -> safeMul(b, d); + + private Ruf sumQueryAssignUpdate = (b, tl, tr, d) -> (tr - tl + 1) * d; + private Ruf lsumQueryAssignUpdate = (b, tl, tr, d) -> d; + + // TODO(william): confirm this cannot be supported? Can we maintain additional + // information to make it possible? + private Ruf gcdQuerySumUpdate = (b, tl, tr, d) -> null; + private Ruf lgcdQuerySumUpdate = (b, tl, tr, d) -> null; + + private Ruf gcdQueryMulUpdate = (b, tl, tr, d) -> safeMul(b, d); + private Ruf lgcdQueryMulUpdate = (b, tl, tr, d) -> safeMul(b, d); + + private Ruf gcdQueryAssignUpdate = (b, tl, tr, d) -> d; + private Ruf lgcdQueryAssignUpdate = (b, tl, tr, d) -> d; + + private Ruf productQuerySumUpdate = (b, tl, tr, d) -> b + (long) (Math.pow(d, (tr - tl + 1))); + private Ruf lproductQuerySumUpdate = (b, tl, tr, d) -> safeSum(b, d); + + private Ruf productQueryMulUpdate = (b, tl, tr, d) -> b * (long) (Math.pow(d, (tr - tl + 1))); + private Ruf lproductQueryMulUpdate = + (b, tl, tr, d) -> safeMul(b, d); // safeMul(b, (long)(Math.pow(d, (tr - tl + 1)))); + + private Ruf productQueryAssignUpdate = (b, tl, tr, d) -> d; + private Ruf lproductQueryAssignUpdate = (b, tl, tr, d) -> d; + + public GenericSegmentTree( + long[] values, + SegmentCombinationFn segmentCombinationFunction, + RangeUpdateFn rangeUpdateFunction) { + if (values == null) { + throw new IllegalArgumentException("Segment tree values cannot be null."); + } + if (segmentCombinationFunction == null) { + throw new IllegalArgumentException("Please specify a valid segment combination function."); + } + if (rangeUpdateFunction == null) { + throw new IllegalArgumentException("Please specify a valid range update function."); + } + n = values.length; + + // The size of the segment tree `t` + // + // TODO(william): Investigate to reduce this space. There are only 2n-1 segments, so we should + // be able to reduce the space, but may need to reorganize the tree/queries. One idea is to use + // the Eulerian tour structure of the tree to densely pack the segments. + int N = 4 * n; + + t = new Long[N]; + // TODO(william): Change this to be of size n to reduce memory from O(4n) to O(3n) + lazy = new Long[N]; + + // Select the specified combination function + if (segmentCombinationFunction == SegmentCombinationFn.SUM) { + combinationFn = sumCombinationFn; + if (rangeUpdateFunction == RangeUpdateFn.ADDITION) { + ruf = sumQuerySumUpdate; + lruf = lsumQuerySumUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.ASSIGN) { + ruf = sumQueryAssignUpdate; + lruf = lsumQueryAssignUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.MULTIPLICATION) { + ruf = sumQueryMulUpdate; + lruf = lsumQueryMulUpdate; + } + } else if (segmentCombinationFunction == SegmentCombinationFn.MIN) { + combinationFn = minCombinationFn; + if (rangeUpdateFunction == RangeUpdateFn.ADDITION) { + ruf = minQuerySumUpdate; + lruf = lminQuerySumUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.ASSIGN) { + ruf = minQueryAssignUpdate; + lruf = lminQueryAssignUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.MULTIPLICATION) { + ruf = minQueryMulUpdate; + lruf = lminQueryMulUpdate; + } + } else if (segmentCombinationFunction == SegmentCombinationFn.MAX) { + combinationFn = maxCombinationFn; + if (rangeUpdateFunction == RangeUpdateFn.ADDITION) { + ruf = maxQuerySumUpdate; + lruf = lmaxQuerySumUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.ASSIGN) { + ruf = maxQueryAssignUpdate; + lruf = lmaxQueryAssignUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.MULTIPLICATION) { + ruf = maxQueryMulUpdate; + lruf = lmaxQueryMulUpdate; + } + } else if (segmentCombinationFunction == SegmentCombinationFn.GCD) { + combinationFn = gcdCombinationFn; + if (rangeUpdateFunction == RangeUpdateFn.ADDITION) { + ruf = gcdQuerySumUpdate; + lruf = lgcdQuerySumUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.ASSIGN) { + ruf = gcdQueryAssignUpdate; + lruf = lgcdQueryAssignUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.MULTIPLICATION) { + ruf = gcdQueryMulUpdate; + lruf = lgcdQueryMulUpdate; + } + } else if (segmentCombinationFunction == SegmentCombinationFn.PRODUCT) { + combinationFn = productCombinationFn; + if (rangeUpdateFunction == RangeUpdateFn.ADDITION) { + ruf = productQuerySumUpdate; + lruf = lproductQuerySumUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.ASSIGN) { + ruf = productQueryAssignUpdate; + lruf = lproductQueryAssignUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.MULTIPLICATION) { + ruf = productQueryMulUpdate; + lruf = lproductQueryMulUpdate; + } + } else { + throw new UnsupportedOperationException( + "Combination function not supported: " + segmentCombinationFunction); + } + + buildSegmentTree(0, 0, n - 1, values); + } + + /** + * Builds a segment tree by starting with the leaf nodes and combining segment values on callback. + * + * @param i the index of the segment in the segment tree + * @param tl the left index (inclusive) of the segment range + * @param tr the right index (inclusive) of the segment range + * @param values the initial values array + */ + private void buildSegmentTree(int i, int tl, int tr, long[] values) { + if (tl == tr) { + t[i] = values[tl]; + return; + } + int tm = (tl + tr) / 2; + buildSegmentTree(2 * i + 1, tl, tm, values); + buildSegmentTree(2 * i + 2, tm + 1, tr, values); + + t[i] = combinationFn.apply(t[2 * i + 1], t[2 * i + 2]); + } + + /** + * Returns the query of the range [l, r] on the original `values` array (+ any updates made to it) + * + * @param l the left endpoint of the range query (inclusive) + * @param r the right endpoint of the range query (inclusive) + */ + public Long rangeQuery1(int l, int r) { + return rangeQuery1(0, 0, n - 1, l, r); + } + + /** + * Returns the range query value of the range [l, r] + * + * @param i the index of the current segment in the tree + * @param tl the left endpoint (inclusive) of the current segment + * @param tr the right endpoint (inclusive) of the current segment + * @param l the target left endpoint (inclusive) for the range query + * @param r the target right endpoint (inclusive) for the range query + */ + private Long rangeQuery1(int i, int tl, int tr, int l, int r) { + // Different segment tree types have different base cases + if (l > r) { + return null; + } + propagate1(i, tl, tr); + if (tl == l && tr == r) { + return t[i]; + } + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case return the + // default value for invalid intervals. + return combinationFn.apply( + rangeQuery1(2 * i + 1, tl, tm, l, Math.min(tm, r)), + rangeQuery1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r)); + } + + // Apply the lazy delta value to the current node and push it to the child segments + private void propagate1(int i, int tl, int tr) { + // No lazy value to propagate + if (lazy[i] == null) { + return; + } + // Apply the lazy delta to the current segment. + t[i] = ruf.apply(t[i], tl, tr, lazy[i]); + // Push the lazy delta to left/right segments for non-leaf nodes + propagateLazy1(i, tl, tr, lazy[i]); + lazy[i] = null; + } + + private void propagateLazy1(int i, int tl, int tr, long delta) { + // Ignore leaf segments + if (tl == tr) return; + lazy[2 * i + 1] = lruf.apply(lazy[2 * i + 1], tl, tr, delta); + lazy[2 * i + 2] = lruf.apply(lazy[2 * i + 2], tl, tr, delta); + } + + public void rangeUpdate1(int l, int r, long x) { + rangeUpdate1(0, 0, n - 1, l, r, x); + } + + private void rangeUpdate1(int i, int tl, int tr, int l, int r, long x) { + propagate1(i, tl, tr); + if (l > r) { + return; + } + + if (tl == l && tr == r) { + t[i] = ruf.apply(t[i], tl, tr, x); + propagateLazy1(i, tl, tr, x); + } else { + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case disregard + // invalid intervals. + rangeUpdate1(2 * i + 1, tl, tm, l, Math.min(tm, r), x); + rangeUpdate1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r, x); + + t[i] = combinationFn.apply(t[2 * i + 1], t[2 * i + 2]); + } + } + + // // Updates the value at index `i` in the original `values` array to be `newValue`. + // public void pointUpdate(int i, long newValue) { + // pointUpdate(0, i, 0, n - 1, newValue); + // } + + // /** + // * Update a point value to a new value and update all affected segments, O(log(n)) + // * + // *

Do this by performing a binary search to find the interval containing the point, then + // update + // * the leaf segment with the new value, and re-compute all affected segment values on the + // * callback. + // * + // * @param i the index of the current segment in the tree + // * @param pos the target position to update + // * @param tl the left segment endpoint (inclusive) + // * @param tr the right segment endpoint (inclusive) + // * @param newValue the new value to update + // */ + // private void pointUpdate(int i, int pos, int tl, int tr, long newValue) { + // if (tl == tr) { // `tl == pos && tr == pos` might be clearer + // t[i] = newValue; + // return; + // } + // int tm = (tl + tr) / 2; + // if (pos <= tm) { + // // The point index `pos` is contained within the left segment [tl, tm] + // pointUpdate(2 * i + 1, pos, tl, tm, newValue); + // } else { + // // The point index `pos` is contained within the right segment [tm+1, tr] + // pointUpdate(2 * i + 2, pos, tm + 1, tr, newValue); + // } + // // Re-compute the segment value of the current segment on the callback + // // t[i] = rangeUpdateFn.apply(t[2 * i + 1], t[2 * i + 2]); + // t[i] = combinationFn.apply(t[2 * i + 1], t[2 * i + 2]); + // } + + public void printDebugInfo() { + printDebugInfo(0, 0, n - 1); + System.out.println(); + } + + private void printDebugInfo(int i, int tl, int tr) { + System.out.printf("[%d, %d], t[i] = %d, lazy[i] = %d\n", tl, tr, t[i], lazy[i]); + if (tl == tr) { + return; + } + int tm = (tl + tr) / 2; + printDebugInfo(2 * i + 1, tl, tm); + printDebugInfo(2 * i + 2, tm + 1, tr); + } + + //////////////////////////////////////////////////// + // Example usage: // + //////////////////////////////////////////////////// + + public static void main(String[] args) { + t(); + // sumQuerySumUpdateExample(); + // minQueryAssignUpdateExample(); + // gcdQueryMulUpdateExample(); + // gcdQueryAssignUpdateExample(); + // productQueryMulUpdateExample(); + } + + private static void productQueryMulUpdateExample() { + // 0, 1, 2, 3 + long[] v = {3, 2, 2, 1}; + GenericSegmentTree st = + new GenericSegmentTree(v, SegmentCombinationFn.PRODUCT, RangeUpdateFn.MULTIPLICATION); + + int l = 0; + int r = 3; + long q = st.rangeQuery1(l, r); + if (q != 12) System.out.println("Error"); + System.out.printf("The product between indeces [%d, %d] is: %d\n", l, r, q); + + // 3, 8, 8, 1 + // 3 * 8 * 8 * 1 = 192 + st.rangeUpdate1(1, 2, 4); + q = st.rangeQuery1(l, r); + if (q != 192) System.out.println("Error"); + System.out.printf("The product between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, r)); + + // 3, 8, 16, 2 + // 3 * 8 * 16 * 2 = 768 + st.rangeUpdate1(2, 3, 2); + q = st.rangeQuery1(l, r); + if (q != 768) System.out.println("Error"); + System.out.printf("The product between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, r)); + + // 12, 24, 24, 24, 48 + // st.rangeUpdate1(2, 3, 24); + // l = 0; + // r = 4; + // q = st.rangeQuery1(l, r); + // if (q != 12) System.out.println("Error"); + // System.out.printf("The product between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, + // r)); + } + + private static void gcdQueryMulUpdateExample() { + // 0, 1, 2, 3, 4 + long[] v = {12, 24, 3, 4, -1}; + GenericSegmentTree st = + new GenericSegmentTree(v, SegmentCombinationFn.GCD, RangeUpdateFn.MULTIPLICATION); + + int l = 0; + int r = 2; + long q = st.rangeQuery1(l, r); + if (q != 3) System.out.println("Error"); + System.out.printf("The gcd between indeces [%d, %d] is: %d\n", l, r, q); + st.rangeUpdate1(2, 2, 2); + q = st.rangeQuery1(l, r); + if (q != 6) System.out.println("Error"); + System.out.printf("The gcd between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, r)); + + r = 1; // [l, r] = [0, 1] + q = st.rangeQuery1(l, r); + if (q != 12) System.out.println("Error"); + System.out.printf("The gcd between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, r)); + } + + private static void gcdQueryAssignUpdateExample() { + // 0, 1, 2, 3, 4 + long[] v = {12, 24, 3, 12, 48}; + GenericSegmentTree st = + new GenericSegmentTree(v, SegmentCombinationFn.GCD, RangeUpdateFn.ASSIGN); + + int l = 0; + int r = 2; + long q = st.rangeQuery1(l, r); + if (q != 3) System.out.println("Error"); + System.out.printf("The gcd between indeces [%d, %d] is: %d\n", l, r, q); + + // 12, 24, 48, 12, 48 + st.rangeUpdate1(2, 2, 48); + q = st.rangeQuery1(l, r); + if (q != 12) System.out.println("Error"); + System.out.printf("The gcd between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, r)); + + // 12, 24, 24, 24, 48 + st.rangeUpdate1(2, 3, 24); + l = 0; + r = 4; + q = st.rangeQuery1(l, r); + if (q != 12) System.out.println("Error"); + System.out.printf("The gcd between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, r)); + } + + private static void sumQuerySumUpdateExample() { + // 0, 1, 2, 3, 4 + long[] v = {2, 1, 3, 4, -1}; + GenericSegmentTree st = + new GenericSegmentTree(v, SegmentCombinationFn.SUM, RangeUpdateFn.ADDITION); + + int l = 1; + int r = 3; + long q = st.rangeQuery1(l, r); + if (q != 8) System.out.println("Error"); + System.out.printf("The sum between indeces [%d, %d] is: %d\n", l, r, q); + st.rangeUpdate1(1, 3, 3); + q = st.rangeQuery1(l, r); + if (q != 17) System.out.println("Error"); + System.out.printf("The sum between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, r)); + } + + private static void t() { + long[] v = {1, 4, 3, 0, 5, 8, -2, 7, 5, 2, 9}; + GenericSegmentTree st = + new GenericSegmentTree(v, SegmentCombinationFn.MIN, RangeUpdateFn.ASSIGN); + st.printDebugInfo(); + } + + private static void minQueryAssignUpdateExample() { + // 0, 1, 2, 3, 4 + long[] v = {2, 1, 3, 4, -1}; + GenericSegmentTree st = + new GenericSegmentTree(v, SegmentCombinationFn.MIN, RangeUpdateFn.ASSIGN); + + // System.out.println(java.util.Arrays.toString(st.t)); + + int l = 1; + int r = 3; + long q = st.rangeQuery1(l, r); + if (q != 1) System.out.println("Error"); + System.out.printf("The min between indeces [%d, %d] is: %d\n", l, r, q); + st.rangeUpdate1(1, 3, 3); + l = 0; + r = 1; + q = st.rangeQuery1(l, r); + if (q != 2) System.out.println("Error"); + System.out.printf("The min between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, r)); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTree2.java b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTree2.java new file mode 100644 index 000000000..02dc3de9c --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTree2.java @@ -0,0 +1,490 @@ +/** + * A generic segment tree implementation that supports several range update and aggregation + * functions. + * + *

Run with: ./gradlew run -Palgorithm=datastructures.segmenttree.GenericSegmentTree2 + * + *

Several thanks to cp-algorithms for their great article on segment trees: + * https://cp-algorithms.com/data_structures/segment_tree.html + * + *

NOTE: This file is still a WIP + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +import java.util.function.BinaryOperator; + +public class GenericSegmentTree2 { + + // The type of segment combination function to use + public static enum SegmentCombinationFn { + SUM, + MIN, + MAX + } + + // When updating the value of a specific index position, or a range of values, + // modify the affected values using the following function: + public static enum RangeUpdateFn { + // When a range update is issued, assign all the values in the range [l, r] to be `x` + ASSIGN, + // When a range update is issued, add a value of `x` to all the elements in the range [l, r] + ADDITION, + // When a range update is issued, multiply all elements in the range [l, r] by a value of `x` + MULTIPLICATION + } + + private static class Segment { // implements PrintableNode + // TODO(william): investigate if we really need this, it's unlikely that we do since it should + // be able to implicitly determine the index. + int i; + + Long value; + Long lazy; + + // Used only for Min/Max mul queries. Used in an attempt to resolve: + // https://github.com/williamfiset/Algorithms/issues/208 + Long min; + Long max; + + // The range of the segment [tl, tr] + int tl; + int tr; + + public Segment(int i, Long value, Long min, Long max, int tl, int tr) { + this.i = i; + this.value = value; + this.min = min; + this.max = max; + this.tl = tl; + this.tr = tr; + } + + // @Override + // public PrintableNode getLeft() { + // return left; + // } + + // @Override + // public PrintableNode getRight() { + // return right; + // } + + // @Override + // public String getText() { + // return value.toString(); + // } + + @Override + public String toString() { + return String.format("[%d, %d], value = %d, lazy = %d", tl, tr, value, lazy); + } + } + + // The number of elements in the original input values array. + private int n; + + // The segment tree represented as a binary tree of ranges where st[0] is the + // root node and the left and right children of node i are i*2+1 and i*2+2. + private Segment[] st; + + // The chosen range combination function + private BinaryOperator combinationFn; + + private interface Ruf { + Long apply(Segment segment, Long delta); + } + + // The Range Update Function (RUF) that chooses how a lazy delta value is + // applied to a segment. + private Ruf ruf; + + // The Lazy Range Update Function (LRUF) associated with the RUF. How you + // propagate the lazy delta values is sometimes different than how you apply + // them to the current segment (but most of the time the RUF = LRUF). + private Ruf lruf; + + private long safeSum(Long a, Long b) { + if (a == null) a = 0L; + if (b == null) b = 0L; + return a + b; + } + + private Long safeMul(Long a, Long b) { + if (a == null) a = 1L; + if (b == null) b = 1L; + return a * b; + } + + private Long safeMin(Long a, Long b) { + if (a == null) return b; + if (b == null) return a; + return Math.min(a, b); + } + + private Long safeMax(Long a, Long b) { + if (a == null) return b; + if (b == null) return a; + return Math.max(a, b); + } + + private BinaryOperator sumCombinationFn = (a, b) -> safeSum(a, b); + private BinaryOperator minCombinationFn = (a, b) -> safeMin(a, b); + private BinaryOperator maxCombinationFn = (a, b) -> safeMax(a, b); + + // TODO(william): Document the justification for each function below + + // Range update functions + private Ruf minQuerySumUpdate = (s, x) -> safeSum(s.value, x); + private Ruf lminQuerySumUpdate = (s, x) -> safeSum(s.lazy, x); + + // // TODO(issue/208): support this multiplication update + private Ruf minQueryMulUpdate = + (s, x) -> { + if (x == 0) { + return 0L; + } else if (x < 0) { + // s.min was already calculated + if (safeMul(s.value, x) == s.min) { + return s.max; + } else { + return s.min; + } + } else { + return safeMul(s.value, x); + } + }; + private Ruf lminQueryMulUpdate = (s, x) -> safeMul(s.lazy, x); + + private Ruf minQueryAssignUpdate = (s, x) -> x; + private Ruf lminQueryAssignUpdate = (s, x) -> x; + + private Ruf maxQuerySumUpdate = (s, x) -> safeSum(s.value, x); + private Ruf lmaxQuerySumUpdate = (s, x) -> safeSum(s.lazy, x); + + // TODO(issue/208): support this multiplication update + private Ruf maxQueryMulUpdate = + (s, x) -> { + if (x == 0) { + return 0L; + } else if (x < 0) { + if (safeMul(s.value, x) == s.min) { + return s.max; + } else { + return s.min; + } + } else { + return safeMul(s.value, x); + } + }; + private Ruf lmaxQueryMulUpdate = (s, x) -> safeMul(s.lazy, x); + + private Ruf maxQueryAssignUpdate = (s, x) -> x; + private Ruf lmaxQueryAssignUpdate = (s, x) -> x; + + private Ruf sumQuerySumUpdate = (s, x) -> s.value + (s.tr - s.tl + 1) * x; + private Ruf lsumQuerySumUpdate = (s, x) -> safeSum(s.lazy, x); + + private Ruf sumQueryMulUpdate = (s, x) -> safeMul(s.value, x); + private Ruf lsumQueryMulUpdate = (s, x) -> safeMul(s.lazy, x); + + private Ruf sumQueryAssignUpdate = (s, x) -> (s.tr - s.tl + 1) * x; + private Ruf lsumQueryAssignUpdate = (s, x) -> x; + + public GenericSegmentTree2( + long[] values, + SegmentCombinationFn segmentCombinationFunction, + RangeUpdateFn rangeUpdateFunction) { + if (values == null) { + throw new IllegalArgumentException("Segment tree values cannot be null."); + } + if (segmentCombinationFunction == null) { + throw new IllegalArgumentException("Please specify a valid segment combination function."); + } + if (rangeUpdateFunction == null) { + throw new IllegalArgumentException("Please specify a valid range update function."); + } + n = values.length; + + // The size of the segment tree `t` + // + // TODO(william): Investigate to reduce this space. There are only 2n-1 segments, so we should + // be able to reduce the space, but may need to reorganize the tree/queries. One idea is to use + // the Eulerian tour structure of the tree to densely pack the segments. + int N = 4 * n; + + st = new Segment[N]; + + // Select the specified combination function + if (segmentCombinationFunction == SegmentCombinationFn.SUM) { + combinationFn = sumCombinationFn; + if (rangeUpdateFunction == RangeUpdateFn.ADDITION) { + ruf = sumQuerySumUpdate; + lruf = lsumQuerySumUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.ASSIGN) { + ruf = sumQueryAssignUpdate; + lruf = lsumQueryAssignUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.MULTIPLICATION) { + ruf = sumQueryMulUpdate; + lruf = lsumQueryMulUpdate; + } + } else if (segmentCombinationFunction == SegmentCombinationFn.MIN) { + combinationFn = minCombinationFn; + if (rangeUpdateFunction == RangeUpdateFn.ADDITION) { + ruf = minQuerySumUpdate; + lruf = lminQuerySumUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.ASSIGN) { + ruf = minQueryAssignUpdate; + lruf = lminQueryAssignUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.MULTIPLICATION) { + ruf = minQueryMulUpdate; + lruf = lminQueryMulUpdate; + } + } else if (segmentCombinationFunction == SegmentCombinationFn.MAX) { + combinationFn = maxCombinationFn; + if (rangeUpdateFunction == RangeUpdateFn.ADDITION) { + ruf = maxQuerySumUpdate; + lruf = lmaxQuerySumUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.ASSIGN) { + ruf = maxQueryAssignUpdate; + lruf = lmaxQueryAssignUpdate; + } else if (rangeUpdateFunction == RangeUpdateFn.MULTIPLICATION) { + ruf = maxQueryMulUpdate; + lruf = lmaxQueryMulUpdate; + } + } else { + throw new UnsupportedOperationException( + "Combination function not supported: " + segmentCombinationFunction); + } + + buildSegmentTree(0, 0, n - 1, values); + } + + /** + * Builds a segment tree by starting with the leaf nodes and combining segment values on callback. + * + * @param i the index of the segment in the segment tree + * @param tl the left index (inclusive) of the segment range + * @param tr the right index (inclusive) of the segment range + * @param values the initial values array + */ + private void buildSegmentTree(int i, int tl, int tr, long[] values) { + if (tl == tr) { + st[i] = new Segment(i, values[tl], values[tl], values[tl], tl, tr); + return; + } + int tm = (tl + tr) / 2; + buildSegmentTree(2 * i + 1, tl, tm, values); + buildSegmentTree(2 * i + 2, tm + 1, tr, values); + + Long segmentValue = combinationFn.apply(st[2 * i + 1].value, st[2 * i + 2].value); + Long minValue = Math.min(st[2 * i + 1].min, st[2 * i + 2].min); + Long maxValue = Math.max(st[2 * i + 1].max, st[2 * i + 2].max); + Segment segment = new Segment(i, segmentValue, minValue, maxValue, tl, tr); + + st[i] = segment; + } + + /** + * Returns the query of the range [l, r] on the original `values` array (+ any updates made to it) + * + * @param l the left endpoint of the range query (inclusive) + * @param r the right endpoint of the range query (inclusive) + */ + public Long rangeQuery1(int l, int r) { + return rangeQuery1(0, 0, n - 1, l, r); + } + + /** + * Returns the range query value of the range [l, r] + * + * @param i the index of the current segment in the tree + * @param tl the left endpoint (inclusive) of the current segment + * @param tr the right endpoint (inclusive) of the current segment + * @param l the target left endpoint (inclusive) for the range query + * @param r the target right endpoint (inclusive) for the range query + */ + private Long rangeQuery1(int i, int tl, int tr, int l, int r) { + // Different segment tree types have different base cases + if (l > r) { + return null; + } + propagate1(i, tl, tr); + if (tl == l && tr == r) { + return st[i].value; + } + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case return the + // default value for invalid intervals. + return combinationFn.apply( + rangeQuery1(2 * i + 1, tl, tm, l, Math.min(tm, r)), + rangeQuery1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r)); + } + + // Apply the delta value to the current node and push it to the child segments + private void propagate1(int i, int tl, int tr) { + if (st[i].lazy != null) { + // Only used for min/max mul queries + st[i].min = st[i].min * st[i].lazy; + st[i].max = st[i].max * st[i].lazy; + + // Apply the delta to the current segment. + st[i].value = ruf.apply(st[i], st[i].lazy); + // Push the delta to left/right segments for non-leaf nodes + propagateLazy1(i, tl, tr, st[i].lazy); + st[i].lazy = null; + } + } + + private void propagateLazy1(int i, int tl, int tr, long delta) { + // Ignore leaf segments + if (tl == tr) return; + st[2 * i + 1].lazy = lruf.apply(st[2 * i + 1], delta); + st[2 * i + 2].lazy = lruf.apply(st[2 * i + 2], delta); + } + + public void rangeUpdate1(int l, int r, long x) { + rangeUpdate1(0, 0, n - 1, l, r, x); + } + + private void rangeUpdate1(int i, int tl, int tr, int l, int r, long x) { + propagate1(i, tl, tr); + if (l > r) { + return; + } + + if (tl == l && tr == r) { + // Only used for min/max mul queries + st[i].min = st[i].min * x; + st[i].max = st[i].max * x; + + st[i].value = ruf.apply(st[i], x); + propagateLazy1(i, tl, tr, x); + } else { + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case disregard + // invalid intervals. + rangeUpdate1(2 * i + 1, tl, tm, l, Math.min(tm, r), x); + rangeUpdate1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r, x); + + st[i].value = combinationFn.apply(st[2 * i + 1].value, st[2 * i + 2].value); + st[i].max = Math.max(st[2 * i + 1].max, st[2 * i + 2].max); + st[i].min = Math.min(st[2 * i + 1].min, st[2 * i + 2].min); + } + } + + // // Updates the value at index `i` in the original `values` array to be `newValue`. + // public void pointUpdate(int i, long newValue) { + // pointUpdate(0, i, 0, n - 1, newValue); + // } + + // /** + // * Update a point value to a new value and update all affected segments, O(log(n)) + // * + // *

Do this by performing a binary search to find the interval containing the point, then + // update + // * the leaf segment with the new value, and re-compute all affected segment values on the + // * callback. + // * + // * @param i the index of the current segment in the tree + // * @param pos the target position to update + // * @param tl the left segment endpoint (inclusive) + // * @param tr the right segment endpoint (inclusive) + // * @param newValue the new value to update + // */ + // private void pointUpdate(int i, int pos, int tl, int tr, long newValue) { + // if (tl == tr) { // `tl == pos && tr == pos` might be clearer + // t[i] = newValue; + // return; + // } + // int tm = (tl + tr) / 2; + // if (pos <= tm) { + // // The point index `pos` is contained within the left segment [tl, tm] + // pointUpdate(2 * i + 1, pos, tl, tm, newValue); + // } else { + // // The point index `pos` is contained within the right segment [tm+1, tr] + // pointUpdate(2 * i + 2, pos, tm + 1, tr, newValue); + // } + // // Re-compute the segment value of the current segment on the callback + // // t[i] = rangeUpdateFn.apply(t[2 * i + 1], t[2 * i + 2]); + // t[i] = combinationFn.apply(t[2 * i + 1], t[2 * i + 2]); + // } + + public void printDebugInfo() { + printDebugInfo(0); + System.out.println(); + } + + private void printDebugInfo(int i) { + System.out.println(st[i]); + if (st[i].tl == st[i].tr) { + return; + } + printDebugInfo(2 * i + 1); + printDebugInfo(2 * i + 2); + } + + //////////////////////////////////////////////////// + // Example usage: // + //////////////////////////////////////////////////// + + public static void main(String[] args) { + minQuerySumUpdate(); + sumQuerySumUpdateExample(); + minQueryAssignUpdateExample(); + } + + private static void minQuerySumUpdate() { + // 0, 1, 2, 3, 4 + long[] v = {2, 1, 3, 4, -1}; + GenericSegmentTree2 st = + new GenericSegmentTree2(v, SegmentCombinationFn.MIN, RangeUpdateFn.ADDITION); + + int l = 1; + int r = 3; + long q = st.rangeQuery1(l, r); + if (q != 1) System.out.println("Error"); + System.out.printf("The min between indeces [%d, %d] is: %d\n", l, r, q); + + st.printDebugInfo(); + } + + private static void sumQuerySumUpdateExample() { + // 0, 1, 2, 3, 4 + long[] v = {2, 1, 3, 4, -1}; + GenericSegmentTree2 st = + new GenericSegmentTree2(v, SegmentCombinationFn.SUM, RangeUpdateFn.ADDITION); + + int l = 1; + int r = 3; + long q = st.rangeQuery1(l, r); + if (q != 8) System.out.println("Error"); + System.out.printf("The sum between indeces [%d, %d] is: %d\n", l, r, q); + st.rangeUpdate1(1, 3, 3); + q = st.rangeQuery1(l, r); + if (q != 17) System.out.println("Error"); + System.out.printf("The sum between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, r)); + } + + private static void minQueryAssignUpdateExample() { + // 0, 1, 2, 3, 4 + long[] v = {2, 1, 3, 4, -1}; + GenericSegmentTree2 st = + new GenericSegmentTree2(v, SegmentCombinationFn.MIN, RangeUpdateFn.ASSIGN); + + int l = 1; + int r = 3; + long q = st.rangeQuery1(l, r); + if (q != 1) System.out.println("Error"); + System.out.printf("The min between indeces [%d, %d] is: %d\n", l, r, q); + st.rangeUpdate1(1, 3, 3); + l = 0; + r = 1; + q = st.rangeQuery1(l, r); + if (q != 2) System.out.println("Error"); + System.out.printf("The min between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, r)); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTree3.java b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTree3.java new file mode 100644 index 000000000..db529d742 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTree3.java @@ -0,0 +1,500 @@ +/** + * A generic segment tree implementation that supports several range update and aggregation + * functions. + * + *

Run with: ./gradlew run -Palgorithm=datastructures.segmenttree.GenericSegmentTree3 + * + *

Several thanks to cp-algorithms for their great article on segment trees: + * https://cp-algorithms.com/data_structures/segment_tree.html + * + *

NOTE: This file is still a WIP + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +public class GenericSegmentTree3 { + + // // The type of segment combination function to use + // public static enum SegmentCombinationFn { + // SUM, + // MIN, + // MAX + // } + + // // When updating the value of a specific index position, or a range of values, + // // modify the affected values using the following function: + // public static enum RangeUpdateFn { + // // When a range update is issued, assign all the values in the range [l, r] to be `x` + // ASSIGN, + // // When a range update is issued, add a value of `x` to all the elements in the range [l, r] + // ADDITION, + // // When a range update is issued, multiply all elements in the range [l, r] by a value of `x` + // MULTIPLICATION + // } + + // // TODO(william): Make this class static if possible to avoid sharing members with parent ST + // class + // private class SegmentNode implements PrintableNode { + // // TODO(william): investigate if we really need this, it's unlikely that we do since it + // should + // // be able to implicitly determine the index. + // int i; + + // Long value; + // Long lazy; + + // // Used only for Min/Max mul queries. Used in an attempt to resolve: + // // https://github.com/williamfiset/Algorithms/issues/208 + // Long min; + // Long max; + + // // The range of the segment [l, r] + // int l; + // int r; + + // // The two child segments of this segment (null otherwise). + // // Left segment is [l, m) and right segment is [m, r] where m = (l+r)/2 + // SegmentNode left; + // SegmentNode right; + + // public SegmentNode(int i, Long value, Long min, Long max, int l, int r) { + // this.i = i; + // this.value = value; + // this.min = min; + // this.max = max; + // this.l = l; + // this.r = r; + // } + + // public Long rangeQuery1(int ll, int rr) { + // // Different segment tree types have different base cases + // if (ll > rr) { + // return null; + // } + // propagate1(); + // if (exactOverlap(l, r)) { + // return value; + // } + // int m = (l + r) / 2; + // // Instead of checking if [ll, m] overlaps [l, r] and [m+1, rr] overlaps + // // [l, r], simply recurse on both segments and let the base case return the + // // default value for invalid intervals. + // return combinationFn.apply( + // rangeQuery1(left, l, Math.min(m, rr)), + // rangeQuery1(right, Math.max(ll, m + 1), rr)); + // } + + // // Apply the delta value to the current node and push it to the child segments + // public void propagate1() { + // if (lazy != null) { + // // Only used for min/max mul queries + // min = min * lazy; + // max = max * lazy; + + // // Apply the delta to the current segment. + // value = ruf.apply(node, lazy); + // // Push the delta to left/right segments for non-leaf nodes + // propagateLazy1(lazy); + // lazy = null; + // } + // } + + // public void propagateLazy1(long delta) { + // // Ignore leaf segments since they don't have children. + // if (isLeaf()) { + // return; + // } + // left.lazy = lruf.apply(left, delta); + // right.lazy = lruf.apply(right, delta); + // } + + // public boolean exactOverlap(int ll, int rr) { + // return l == ll && r = rr; + // } + + // public boolean isLeaf() { + // return l == r; + // } + + // @Override + // public PrintableNode getLeft() { + // return left; + // } + + // @Override + // public PrintableNode getRight() { + // return right; + // } + + // @Override + // public String getText() { + // return value.toString(); + // } + + // @Override + // public String toString() { + // return String.format("[%d, %d], value = %d, lazy = %d", tl, tr, value, lazy); + // } + // } + + // // The number of elements in the original input values array. + // private int n; + + // private SegmentNode root; + + // // The chosen range combination function + // private BinaryOperator combinationFn; + + // private interface Ruf { + // Long apply(SegmentNode segment, Long delta); + // } + + // // The Range Update Function (RUF) that chooses how a lazy delta value is + // // applied to a segment. + // private Ruf ruf; + + // // The Lazy Range Update Function (LRUF) associated with the RUF. How you + // // propagate the lazy delta values is sometimes different than how you apply + // // them to the current segment (but most of the time the RUF = LRUF). + // private Ruf lruf; + + // private long safeSum(Long a, Long b) { + // if (a == null) a = 0L; + // if (b == null) b = 0L; + // return a + b; + // } + + // private Long safeMul(Long a, Long b) { + // if (a == null) a = 1L; + // if (b == null) b = 1L; + // return a * b; + // } + + // private Long safeMin(Long a, Long b) { + // if (a == null) return b; + // if (b == null) return a; + // return Math.min(a, b); + // } + + // private Long safeMax(Long a, Long b) { + // if (a == null) return b; + // if (b == null) return a; + // return Math.max(a, b); + // } + + // private BinaryOperator sumCombinationFn = (a, b) -> safeSum(a, b); + // private BinaryOperator minCombinationFn = (a, b) -> safeMin(a, b); + // private BinaryOperator maxCombinationFn = (a, b) -> safeMax(a, b); + + // // TODO(william): Document the justification for each function below + + // // Range update functions + // private Ruf minQuerySumUpdate = (s, x) -> safeSum(s.value, x); + // private Ruf lminQuerySumUpdate = (s, x) -> safeSum(s.lazy, x); + + // // // TODO(issue/208): support this multiplication update + // private Ruf minQueryMulUpdate = + // (s, x) -> { + // if (x == 0) { + // return 0L; + // } else if (x < 0) { + // // s.min was already calculated + // if (safeMul(s.value, x) == s.min) { + // return s.max; + // } else { + // return s.min; + // } + // } else { + // return safeMul(s.value, x); + // } + // }; + // private Ruf lminQueryMulUpdate = (s, x) -> safeMul(s.lazy, x); + + // private Ruf minQueryAssignUpdate = (s, x) -> x; + // private Ruf lminQueryAssignUpdate = (s, x) -> x; + + // private Ruf maxQuerySumUpdate = (s, x) -> safeSum(s.value, x); + // private Ruf lmaxQuerySumUpdate = (s, x) -> safeSum(s.lazy, x); + + // // TODO(issue/208): support this multiplication update + // private Ruf maxQueryMulUpdate = + // (s, x) -> { + // if (x == 0) { + // return 0L; + // } else if (x < 0) { + // if (safeMul(s.value, x) == s.min) { + // return s.max; + // } else { + // return s.min; + // } + // } else { + // return safeMul(s.value, x); + // } + // }; + // private Ruf lmaxQueryMulUpdate = (s, x) -> safeMul(s.lazy, x); + + // private Ruf maxQueryAssignUpdate = (s, x) -> x; + // private Ruf lmaxQueryAssignUpdate = (s, x) -> x; + + // private Ruf sumQuerySumUpdate = (s, x) -> s.value + (s.tr - s.tl + 1) * x; + // private Ruf lsumQuerySumUpdate = (s, x) -> safeSum(s.lazy, x); + + // private Ruf sumQueryMulUpdate = (s, x) -> safeMul(s.value, x); + // private Ruf lsumQueryMulUpdate = (s, x) -> safeMul(s.lazy, x); + + // private Ruf sumQueryAssignUpdate = (s, x) -> (s.tr - s.tl + 1) * x; + // private Ruf lsumQueryAssignUpdate = (s, x) -> x; + + // public GenericSegmentTree3( + // long[] values, + // SegmentCombinationFn segmentCombinationFunction, + // RangeUpdateFn rangeUpdateFunction) { + // if (values == null) { + // throw new IllegalArgumentException("Segment tree values cannot be null."); + // } + // if (segmentCombinationFunction == null) { + // throw new IllegalArgumentException("Please specify a valid segment combination function."); + // } + // if (rangeUpdateFunction == null) { + // throw new IllegalArgumentException("Please specify a valid range update function."); + // } + // n = values.length; + + // // The size of the segment tree `t` + // // + // // TODO(william): Investigate to reduce this space. There are only 2n-1 segments, so we + // should + // // be able to reduce the space, but may need to reorganize the tree/queries. One idea is to + // use + // // the Eulerian tour structure of the tree to densely pack the segments. + // int N = 4 * n; + + // // Select the specified combination function + // if (segmentCombinationFunction == SegmentCombinationFn.SUM) { + // combinationFn = sumCombinationFn; + // if (rangeUpdateFunction == RangeUpdateFn.ADDITION) { + // ruf = sumQuerySumUpdate; + // lruf = lsumQuerySumUpdate; + // } else if (rangeUpdateFunction == RangeUpdateFn.ASSIGN) { + // ruf = sumQueryAssignUpdate; + // lruf = lsumQueryAssignUpdate; + // } else if (rangeUpdateFunction == RangeUpdateFn.MULTIPLICATION) { + // ruf = sumQueryMulUpdate; + // lruf = lsumQueryMulUpdate; + // } + // } else if (segmentCombinationFunction == SegmentCombinationFn.MIN) { + // combinationFn = minCombinationFn; + // if (rangeUpdateFunction == RangeUpdateFn.ADDITION) { + // ruf = minQuerySumUpdate; + // lruf = lminQuerySumUpdate; + // } else if (rangeUpdateFunction == RangeUpdateFn.ASSIGN) { + // ruf = minQueryAssignUpdate; + // lruf = lminQueryAssignUpdate; + // } else if (rangeUpdateFunction == RangeUpdateFn.MULTIPLICATION) { + // ruf = minQueryMulUpdate; + // lruf = lminQueryMulUpdate; + // } + // } else if (segmentCombinationFunction == SegmentCombinationFn.MAX) { + // combinationFn = maxCombinationFn; + // if (rangeUpdateFunction == RangeUpdateFn.ADDITION) { + // ruf = maxQuerySumUpdate; + // lruf = lmaxQuerySumUpdate; + // } else if (rangeUpdateFunction == RangeUpdateFn.ASSIGN) { + // ruf = maxQueryAssignUpdate; + // lruf = lmaxQueryAssignUpdate; + // } else if (rangeUpdateFunction == RangeUpdateFn.MULTIPLICATION) { + // ruf = maxQueryMulUpdate; + // lruf = lmaxQueryMulUpdate; + // } + // } else { + // throw new UnsupportedOperationException( + // "Combination function not supported: " + segmentCombinationFunction); + // } + + // root = buildSegmentTree(0, 0, n - 1, values); + // } + + // /** + // * Builds a segment tree by starting with the leaf nodes and combining segment values on + // callback. + // * + // * @param i the index of the segment in the segment tree + // * @param l the left index (inclusive) of the segment range + // * @param r the right index (inclusive) of the segment range + // * @param values the initial values array + // */ + // private SegmentNode buildSegmentTree(int i, int l, int r, long[] values) { + // if (l == r) { + // return new SegmentNode(i, values[l], values[l], values[l], l, r); + // } + // int tm = (l + r) / 2; + // SegmentNode left = buildSegmentTree(2 * i + 1, l, tm, values); + // SegmentNode right = buildSegmentTree(2 * i + 2, tm + 1, r, values); + + // Long segmentValue = combinationFn.apply(left.value, right.value); + // Long minValue = Math.min(left.min, right.min); + // Long maxValue = Math.max(left.max, right.max); + + // // TODO(william): move assigning children to the constructor? + // Segment segment = new Segment(i, segmentValue, minValue, maxValue, l, r); + // segment.left = left; + // segment.right = right; + + // return segment; + // } + + // /** + // * Returns the query of the range [l, r] on the original `values` array (+ any updates made to + // it) + // * + // * @param l the left endpoint of the range query (inclusive) + // * @param r the right endpoint of the range query (inclusive) + // */ + // public Long rangeQuery1(int l, int r) { + // return root.rangeQuery1(l, r); + // } + + // public void rangeUpdate1(int l, int r, long x) { + // rangeUpdate1(node, l, r, x); + // } + + // private void rangeUpdate1(SegmentNode node, int l, int r, long x) { + // node.propagate1(); + // if (l > r) { + // return; + // } + + // if (node.exactOverlap(l, r)) { + // // Only used for min/max mul queries + // node.min = node.min * x; + // node.max = node.max * x; + + // node.value = ruf.apply(node, x); + // node.propagateLazy1(x); + // } else { + // int m = (l + r) / 2; + // // Instead of checking if [tl, m] overlaps [l, r] and [m+1, tr] overlaps + // // [l, r], simply recurse on both segments and let the base case disregard + // // invalid intervals. + // rangeUpdate1(node.left, l, Math.min(m, r), x); + // rangeUpdate1(node.right, Math.max(l, m + 1), r, x); + + // node.value = combinationFn.apply(node.left.value, node.right.value); + // node.max = Math.max(node.left.max, node.right.max); + // node.min = Math.min(node.left.min, node.right.min); + // } + // } + + // // // Updates the value at index `i` in the original `values` array to be `newValue`. + // // public void pointUpdate(int i, long newValue) { + // // pointUpdate(0, i, 0, n - 1, newValue); + // // } + + // // /** + // // * Update a point value to a new value and update all affected segments, O(log(n)) + // // * + // // *

Do this by performing a binary search to find the interval containing the point, then + // // update + // // * the leaf segment with the new value, and re-compute all affected segment values on the + // // * callback. + // // * + // // * @param i the index of the current segment in the tree + // // * @param pos the target position to update + // // * @param tl the left segment endpoint (inclusive) + // // * @param tr the right segment endpoint (inclusive) + // // * @param newValue the new value to update + // // */ + // // private void pointUpdate(int i, int pos, int tl, int tr, long newValue) { + // // if (tl == tr) { // `tl == pos && tr == pos` might be clearer + // // t[i] = newValue; + // // return; + // // } + // // int tm = (tl + tr) / 2; + // // if (pos <= tm) { + // // // The point index `pos` is contained within the left segment [tl, tm] + // // pointUpdate(2 * i + 1, pos, tl, tm, newValue); + // // } else { + // // // The point index `pos` is contained within the right segment [tm+1, tr] + // // pointUpdate(2 * i + 2, pos, tm + 1, tr, newValue); + // // } + // // // Re-compute the segment value of the current segment on the callback + // // // t[i] = rangeUpdateFn.apply(t[2 * i + 1], t[2 * i + 2]); + // // t[i] = combinationFn.apply(t[2 * i + 1], t[2 * i + 2]); + // // } + + // public void printDebugInfo() { + // printDebugInfo(0); + // System.out.println(); + // } + + // private void printDebugInfo(int i) { + // // System.out.println(st[i]); + // // if (st[i].tl == st[i].tr) { + // // return; + // // } + // // printDebugInfo(2 * i + 1); + // // printDebugInfo(2 * i + 2); + // } + + // //////////////////////////////////////////////////// + // // Example usage: // + // //////////////////////////////////////////////////// + + // public static void main(String[] args) { + // minQuerySumUpdate(); + // sumQuerySumUpdateExample(); + // minQueryAssignUpdateExample(); + // } + + // private static void minQuerySumUpdate() { + // // 0, 1, 2, 3, 4 + // long[] v = {2, 1, 3, 4, -1}; + // GenericSegmentTree3 st = + // new GenericSegmentTree3(v, SegmentCombinationFn.MIN, RangeUpdateFn.ADDITION); + + // int l = 1; + // int r = 3; + // long q = st.rangeQuery1(l, r); + // if (q != 1) System.out.println("Error"); + // System.out.printf("The min between indeces [%d, %d] is: %d\n", l, r, q); + + // st.printDebugInfo(); + // } + + // private static void sumQuerySumUpdateExample() { + // // 0, 1, 2, 3, 4 + // long[] v = {2, 1, 3, 4, -1}; + // GenericSegmentTree3 st = + // new GenericSegmentTree3(v, SegmentCombinationFn.SUM, RangeUpdateFn.ADDITION); + + // int l = 1; + // int r = 3; + // long q = st.rangeQuery1(l, r); + // if (q != 8) System.out.println("Error"); + // System.out.printf("The sum between indeces [%d, %d] is: %d\n", l, r, q); + // st.rangeUpdate1(1, 3, 3); + // q = st.rangeQuery1(l, r); + // if (q != 17) System.out.println("Error"); + // System.out.printf("The sum between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, r)); + // } + + // private static void minQueryAssignUpdateExample() { + // // 0, 1, 2, 3, 4 + // long[] v = {2, 1, 3, 4, -1}; + // GenericSegmentTree3 st = + // new GenericSegmentTree3(v, SegmentCombinationFn.MIN, RangeUpdateFn.ASSIGN); + + // int l = 1; + // int r = 3; + // long q = st.rangeQuery1(l, r); + // if (q != 1) System.out.println("Error"); + // System.out.printf("The min between indeces [%d, %d] is: %d\n", l, r, q); + // st.rangeUpdate1(1, 3, 3); + // l = 0; + // r = 1; + // q = st.rangeQuery1(l, r); + // if (q != 2) System.out.println("Error"); + // System.out.printf("The min between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, r)); + // } +} diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/MaxQuerySumUpdateSegmentTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/MaxQuerySumUpdateSegmentTree.java new file mode 100644 index 000000000..e2628b65c --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/MaxQuerySumUpdateSegmentTree.java @@ -0,0 +1,189 @@ +/** + * Run with: ./gradlew run -Palgorithm=datastructures.segmenttree.MaxQuerySumUpdateSegmentTree + * + *

Several thanks to cp-algorithms for their great article on segment trees: + * https://cp-algorithms.com/data_structures/segment_tree.html + * + *

NOTE: This file is still a WIP + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +public class MaxQuerySumUpdateSegmentTree { + + // The number of elements in the original input values array. + private final int n; + + // The segment tree represented as a binary tree of ranges where t[0] is the + // root node and the left and right children of node i are i*2+1 and i*2+2. + private Long[] t; + + // The delta values associates with each segment. Used for lazy propagation + // when doing range updates. + private Long[] lazy; + + private Long maxFunction(Long a, Long b) { + if (a == null && b == null) return null; + if (a == null) return b; + if (b == null) return a; + return Math.max(a, b); + } + + private Long sumFunction(Long a, Long b) { + if (a == null) a = 0L; + if (b == null) b = 0L; + return a + b; + } + + private Long minSegmentUpdateFn(long base, int tl, int tr, long x) { + return base + x; + } + + public MaxQuerySumUpdateSegmentTree(long[] values) { + if (values == null) { + throw new IllegalArgumentException("Segment tree values cannot be null."); + } + n = values.length; + + // The size of the segment tree `t` + // + // TODO(william): Investigate to reduce this space. There are only 2n-1 segments, so we should + // be able to reduce the space, but may need to reorganize the tree/queries. One idea is to use + // the Eulerian tour structure of the tree to densely pack the segments. + int N = 4 * n; + + t = new Long[N]; + lazy = new Long[N]; + + buildSegmentTree(0, 0, n - 1, values); + } + + /** + * Builds a segment tree by starting with the leaf nodes and combining segment values on callback. + * + * @param i the index of the segment in the segment tree + * @param tl the left index (inclusive) of the segment range + * @param tr the right index (inclusive) of the segment range + * @param values the initial values array + */ + private void buildSegmentTree(int i, int tl, int tr, long[] values) { + if (tl == tr) { + t[i] = values[tl]; + return; + } + int tm = (tl + tr) / 2; + buildSegmentTree(2 * i + 1, tl, tm, values); + buildSegmentTree(2 * i + 2, tm + 1, tr, values); + + t[i] = maxFunction(t[2 * i + 1], t[2 * i + 2]); + } + + /** + * Returns the query of the range [l, r] on the original `values` array (+ any updates made to it) + * + * @param l the left endpoint of the range query (inclusive) + * @param r the right endpoint of the range query (inclusive) + */ + public Long rangeQuery1(int l, int r) { + return rangeQuery1(0, 0, n - 1, l, r); + } + + /** + * Returns the range query value of the range [l, r] + * + * @param i the index of the current segment in the tree + * @param tl the left endpoint (inclusive) of the current segment + * @param tr the right endpoint (inclusive) of the current segment + * @param l the target left endpoint (inclusive) for the range query + * @param r the target right endpoint (inclusive) for the range query + */ + private Long rangeQuery1(int i, int tl, int tr, int l, int r) { + if (l > r) { + return null; + } + propagate1(i, tl, tr); + if (tl == l && tr == r) { + return t[i]; + } + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case return the + // default value for invalid intervals. + return maxFunction( + rangeQuery1(2 * i + 1, tl, tm, l, Math.min(tm, r)), + rangeQuery1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r)); + } + + public void rangeUpdate1(int l, int r, long x) { + rangeUpdate1(0, 0, n - 1, l, r, x); + } + + private void propagateLazy(int i, int tl, int tr, long delta) { + // Ignore leaf segments + if (tl == tr) return; + // TODO(william): should this also used the minSegmentUpdateFn + lazy[2 * i + 1] = sumFunction(lazy[2 * i + 1], delta); + lazy[2 * i + 2] = sumFunction(lazy[2 * i + 2], delta); + } + + private void propagate1(int i, int tl, int tr) { + // Check for default value because you don't want to assign to the lazy + // value if it's the default value. + if (lazy[i] != null) { + // The minimum value increases by the delta over the whole range. + t[i] = minSegmentUpdateFn(t[i], /*unused*/ 0, /*unused*/ 0, lazy[i]); + // Push delta to left/right segments for non-leaf nodes + propagateLazy(i, tl, tr, lazy[i]); + lazy[i] = null; + } + } + + private void rangeUpdate1(int i, int tl, int tr, int l, int r, long x) { + propagate1(i, tl, tr); + if (l > r) { + return; + } + + if (tl == l && tr == r) { + t[i] = minSegmentUpdateFn(t[i], /*unused*/ 0, /*unused*/ 0, x); + propagateLazy(i, tl, tr, x); + // TODO(william): confirm if this is needed if we already propagated? + lazy[i] = null; + } else { + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case disregard + // invalid intervals. + rangeUpdate1(2 * i + 1, tl, tm, l, Math.min(tm, r), x); + rangeUpdate1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r, x); + + t[i] = maxFunction(t[2 * i + 1], t[2 * i + 2]); + } + } + + public void printDebugInfo() { + printDebugInfo(0, 0, n - 1); + System.out.println(); + } + + private void printDebugInfo(int i, int tl, int tr) { + System.out.printf("[%d, %d], t[i] = %d, lazy[i] = %d\n", tl, tr, t[i], lazy[i]); + if (tl == tr) { + return; + } + int tm = (tl + tr) / 2; + printDebugInfo(2 * i + 1, tl, tm); + printDebugInfo(2 * i + 2, tm + 1, tr); + } + + //////////////////////////////////////////////////// + // Example usage: // + //////////////////////////////////////////////////// + + public static void main(String[] args) { + // 0, 1, 2, 3, 4 + long[] v = {2, 1, 3, 4, -1}; + MaxQuerySumUpdateSegmentTree st = new MaxQuerySumUpdateSegmentTree(v); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/MinQueryAssignUpdateSegmentTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/MinQueryAssignUpdateSegmentTree.java new file mode 100644 index 000000000..3e233f8f3 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/MinQueryAssignUpdateSegmentTree.java @@ -0,0 +1,302 @@ +/** + * Run with: ./gradlew run -Palgorithm=datastructures.segmenttree.MinQueryAssignUpdateSegmentTree + * + *

Several thanks to cp-algorithms for their great article on segment trees: + * https://cp-algorithms.com/data_structures/segment_tree.html + * + *

NOTE: This file is still a WIP + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +public class MinQueryAssignUpdateSegmentTree { + + // The number of elements in the original input values array. + private int n; + + // The segment tree represented as a binary tree of ranges where t[0] is the + // root node and the left and right children of node i are i*2+1 and i*2+2. + private Long[] t; + + // The delta values associates with each segment. Used for lazy propagation + // when doing range updates. + private Long[] lazy; + + private Long minFunction(Long a, Long b) { + if (a == null && b == null) return null; + if (a == null) return b; + if (b == null) return a; + return Math.min(a, b); + } + + public MinQueryAssignUpdateSegmentTree(long[] values) { + if (values == null) { + throw new IllegalArgumentException("Segment tree values cannot be null."); + } + + n = values.length; + + // The size of the segment tree `t` + // + // TODO(william): Investigate to reduce this space. There are only 2n-1 segments, so we should + // be able to reduce the space, but may need to reorganize the tree/queries. One idea is to use + // the Eulerian tour structure of the tree to densely pack the segments. + int N = 4 * n; + + t = new Long[N]; + lazy = new Long[N]; + + buildSegmentTree(0, 0, n - 1, values); + } + + /** + * Builds a segment tree by starting with the leaf nodes and combining segment values on callback. + * + * @param i the index of the segment in the segment tree + * @param tl the left index (inclusive) of the segment range + * @param tr the right index (inclusive) of the segment range + * @param values the initial values array + */ + private void buildSegmentTree(int i, int tl, int tr, long[] values) { + if (tl == tr) { + t[i] = values[tl]; + return; + } + int tm = (tl + tr) / 2; + buildSegmentTree(2 * i + 1, tl, tm, values); + buildSegmentTree(2 * i + 2, tm + 1, tr, values); + + t[i] = minFunction(t[2 * i + 1], t[2 * i + 2]); + } + + /** + * Returns the query of the range [l, r] on the original `values` array (+ any updates made to it) + * + * @param l the left endpoint of the range query (inclusive) + * @param r the right endpoint of the range query (inclusive) + */ + public long rangeQuery1(int l, int r) { + return rangeQuery1(0, 0, n - 1, l, r); + } + + /** + * Returns the range query value of the range [l, r] + * + * @param i the index of the current segment in the tree + * @param tl the left endpoint (inclusive) of the current segment + * @param tr the right endpoint (inclusive) of the current segment + * @param l the target left endpoint (inclusive) for the range query + * @param r the target right endpoint (inclusive) for the range query + */ + private Long rangeQuery1(int i, int tl, int tr, int l, int r) { + if (l > r) { + return null; + } + propagate1(i, tl, tr); + if (tl == l && tr == r) { + return t[i]; + } + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case return the + // default value for invalid intervals. + return minFunction( + rangeQuery1(2 * i + 1, tl, tm, l, Math.min(tm, r)), + rangeQuery1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r)); + } + + public void rangeUpdate1(int l, int r, long x) { + rangeUpdate1(0, 0, n - 1, l, r, x); + } + + // TODO(william): cleanup this function + private Long assignFunction(Long a, Long b) { + return b; + } + + private void propagateLazy(int i, int tl, int tr, long val) { + // Ignore leaf segments + if (tl == tr) return; + lazy[2 * i + 1] = assignFunction(/*unused*/ 0L, val); + lazy[2 * i + 2] = assignFunction(/*unused*/ 0L, val); + } + + private void propagate1(int i, int tl, int tr) { + // Check for default value because you don't want to assign to the lazy + // value if it's the default value. + if (lazy[i] != null) { + t[i] = lazy[i]; + // Push delta to left/right segments for non-leaf nodes + propagateLazy(i, tl, tr, lazy[i]); + lazy[i] = null; + } + } + + private void rangeUpdate1(int i, int tl, int tr, int l, int r, long x) { + propagate1(i, tl, tr); + if (l > r) { + return; + } + + if (tl == l && tr == r) { + t[i] = x; + propagateLazy(i, tl, tr, x); + // TODO(william): confirm if this is needed if we already propagated? + lazy[i] = null; + } else { + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case disregard + // invalid intervals. + rangeUpdate1(2 * i + 1, tl, tm, l, Math.min(tm, r), x); + rangeUpdate1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r, x); + + t[i] = minFunction(t[2 * i + 1], t[2 * i + 2]); + } + } + + // /** + // * Returns the query of the range [l, r] on the original `values` array (+ any updates made to + // it) + // * + // * @param l the left endpoint of the range query (inclusive) + // * @param r the right endpoint of the range query (inclusive) + // */ + // public long rangeQuery2(int l, int r) { + // return rangeQuery2(0, 0, n - 1, l, r); + // } + + // /** + // * Returns the range query value of the range [l, r] + // * + // *

An alternative implementation of the range query function that intelligently only digs + // into + // * the branches of the segment tree which overlap with the query [l, r]. + // * + // *

This version of the range query implementation has the advantage that it doesn't need to + // * know the explicit base case value for each range query type. + // * + // * @param i the index of the current segment in the tree + // * @param tl the left endpoint (inclusive) of the current segment + // * @param tr the right endpoint (inclusive) of the current segment + // * @param l the target left endpoint (inclusive) for the range query + // * @param r the target right endpoint (inclusive) for the range query + // */ + // private Long rangeQuery2(int i, int tl, int tr, int l, int r) { + // if (tl == l && tr == r) { + // return t[i]; + // } + // propagate2(i, tl, tr); + // int tm = (tl + tr) / 2; + // // Test how the left and right segments of the interval [tl, tr] overlap with the query [l, + // r] + // boolean overlapsLeftSegment = (l <= tm); + // boolean overlapsRightSegment = (r > tm); + // if (overlapsLeftSegment && overlapsRightSegment) { + // return minFunction( + // rangeQuery2(2 * i + 1, tl, tm, l, Math.min(tm, r)), + // rangeQuery2(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r)); + // } else if (overlapsLeftSegment) { + // return rangeQuery2(2 * i + 1, tl, tm, l, Math.min(tm, r)); + // } else { + // return rangeQuery2(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r); + // } + // } + + // Alternative range update impl that propagates a little differently. + // public void rangeUpdate2(int l, int r, long x) { + // rangeUpdate2(0, 0, n - 1, l, r, x); + // } + + // // Propagates ahead so that when you lookup a value of a node it's already pre-propagated + // // in a sense. Cleans up the code a bit. You don't want to call this method on the leaf nodes. + // private void propagate2(int i, int tl, int tr) { + // // Check for default value because you don't want to assign to the lazy + // // value if it's the default value. + // if (lazy[i] != null) { + // t[2 * i + 1] = lazy[i]; + // lazy[2 * i + 1] = lazy[i]; + // t[2 * i + 2] = lazy[i]; + // lazy[2 * i + 2] = lazy[i]; + // lazy[i] = null; + // } + // } + + // private void rangeUpdate2(int i, int tl, int tr, int l, int r, long x) { + // if (l > r) { + // return; + // } + + // if (tl == l && tr == r) { + // t[i] = x; + // lazy[i] = x; + // } else { + // propagate2(i, tl, tr); + // int tm = (tl + tr) / 2; + // // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // // [l, r], simply recurse on both segments and let the base case disregard + // // invalid intervals. + // rangeUpdate2(2 * i + 1, tl, tm, l, Math.min(tm, r), x); + // rangeUpdate2(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r, x); + + // t[i] = minFunction(t[2 * i + 1], t[2 * i + 2]); + // } + // } + + public void printDebugInfo() { + printDebugInfo(0, 0, n - 1); + System.out.println(); + } + + private void printDebugInfo(int i, int tl, int tr) { + System.out.printf("[%d, %d], t[i] = %d, lazy[i] = %d\n", tl, tr, t[i], lazy[i]); + if (tl == tr) { + return; + } + int tm = (tl + tr) / 2; + printDebugInfo(2 * i + 1, tl, tm); + printDebugInfo(2 * i + 2, tm + 1, tr); + } + + //////////////////////////////////////////////////// + // Example usage: // + //////////////////////////////////////////////////// + + public static void main(String[] args) { + // 0, 1, 2, 3, 4 + long[] v = {2, 1, 3, 4, -1}; + MinQueryAssignUpdateSegmentTree st = new MinQueryAssignUpdateSegmentTree(v); + st.printDebugInfo(); + System.out.println(st.rangeQuery1(0, 4)); // -1 + System.out.println(st.rangeQuery1(3, 3)); // 4 + System.out.println(st.rangeQuery1(4, 4)); // -1 + System.out.println(st.rangeQuery1(3, 4)); // -1 + System.out.println(); + + // 0, 1, 2, 3, 4 + // v = {2, 1, 3, 2, 2}; + st.rangeUpdate1(3, 4, 2); + + System.out.println(st.rangeQuery1(0, 4)); // 1 + System.out.println(st.rangeQuery1(3, 4)); // 2 + System.out.println(st.rangeQuery1(3, 3)); // 2 + System.out.println(st.rangeQuery1(4, 4)); // 2 + + // 0, 1, 2, 3, 4 + // v = {2, 4, 4, 4, 2}; + st.printDebugInfo(); + st.rangeUpdate1(1, 3, 4); + st.printDebugInfo(); + + System.out.println(st.rangeQuery1(0, 4)); // 2 + System.out.println(st.rangeQuery1(0, 1)); // 2 + System.out.println(st.rangeQuery1(3, 4)); // 2 + System.out.println(st.rangeQuery1(1, 1)); // 4 + System.out.println(st.rangeQuery1(2, 2)); // 4 + System.out.println(st.rangeQuery1(3, 3)); // 4 + System.out.println(st.rangeQuery1(1, 3)); // 4 + System.out.println(st.rangeQuery1(2, 3)); // 4 + System.out.println(st.rangeQuery1(1, 2)); // 4 + } +} diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/MinQuerySumUpdateSegmentTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/MinQuerySumUpdateSegmentTree.java new file mode 100644 index 000000000..b3eb68f61 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/MinQuerySumUpdateSegmentTree.java @@ -0,0 +1,188 @@ +/** + * Run with: ./gradlew run -Palgorithm=datastructures.segmenttree.MinQuerySumUpdateSegmentTree + * + *

Several thanks to cp-algorithms for their great article on segment trees: + * https://cp-algorithms.com/data_structures/segment_tree.html + * + *

NOTE: This file is still a WIP + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +public class MinQuerySumUpdateSegmentTree { + + // The number of elements in the original input values array. + private final int n; + + // The segment tree represented as a binary tree of ranges where t[0] is the + // root node and the left and right children of node i are i*2+1 and i*2+2. + private Long[] t; + + // The delta values associates with each segment. Used for lazy propagation + // when doing range updates. + private Long[] lazy; + + // Min function + private Long minFunction(Long a, Long b) { + if (a == null && b == null) return null; + if (a == null) return b; + if (b == null) return a; + return Math.min(a, b); + } + + private Long sumFunction(Long a, Long b) { + if (a == null) a = 0L; + if (b == null) b = 0L; + return a + b; + } + + private Long minSegmentUpdateFn(long base, int tl, int tr, long x) { + return base + x; + } + + public MinQuerySumUpdateSegmentTree(long[] values) { + if (values == null) { + throw new IllegalArgumentException("Segment tree values cannot be null."); + } + n = values.length; + + // The size of the segment tree `t` + // + // TODO(william): Investigate to reduce this space. There are only 2n-1 segments, so we should + // be able to reduce the space, but may need to reorganize the tree/queries. One idea is to use + // the Eulerian tour structure of the tree to densely pack the segments. + int N = 4 * n; + + t = new Long[N]; + lazy = new Long[N]; + + buildSegmentTree(0, 0, n - 1, values); + } + + /** + * Builds a segment tree by starting with the leaf nodes and combining segment values on callback. + * + * @param i the index of the segment in the segment tree + * @param tl the left index (inclusive) of the segment range + * @param tr the right index (inclusive) of the segment range + * @param values the initial values array + */ + private void buildSegmentTree(int i, int tl, int tr, long[] values) { + if (tl == tr) { + t[i] = values[tl]; + return; + } + int tm = (tl + tr) / 2; + buildSegmentTree(2 * i + 1, tl, tm, values); + buildSegmentTree(2 * i + 2, tm + 1, tr, values); + + t[i] = minFunction(t[2 * i + 1], t[2 * i + 2]); + } + + /** + * Returns the query of the range [l, r] on the original `values` array (+ any updates made to it) + * + * @param l the left endpoint of the range query (inclusive) + * @param r the right endpoint of the range query (inclusive) + */ + public Long rangeQuery1(int l, int r) { + return rangeQuery1(0, 0, n - 1, l, r); + } + + /** + * Returns the range query value of the range [l, r] + * + * @param i the index of the current segment in the tree + * @param tl the left endpoint (inclusive) of the current segment + * @param tr the right endpoint (inclusive) of the current segment + * @param l the target left endpoint (inclusive) for the range query + * @param r the target right endpoint (inclusive) for the range query + */ + private Long rangeQuery1(int i, int tl, int tr, int l, int r) { + if (l > r) { + return null; + } + propagate1(i, tl, tr); + if (tl == l && tr == r) { + return t[i]; + } + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case return the + // default value for invalid intervals. + return minFunction( + rangeQuery1(2 * i + 1, tl, tm, l, Math.min(tm, r)), + rangeQuery1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r)); + } + + public void rangeUpdate1(int l, int r, long x) { + rangeUpdate1(0, 0, n - 1, l, r, x); + } + + private void propagateLazy(int i, int tl, int tr, long delta) { + // Ignore leaf segments + if (tl == tr) return; + // TODO(william): should this also used the minSegmentUpdateFn + lazy[2 * i + 1] = sumFunction(lazy[2 * i + 1], delta); + lazy[2 * i + 2] = sumFunction(lazy[2 * i + 2], delta); + } + + private void propagate1(int i, int tl, int tr) { + // Check for default value because you don't want to assign to the lazy + // value if it's the default value. + if (lazy[i] != null) { + // The minimum value increases by the delta over the whole range. + t[i] = minSegmentUpdateFn(t[i], /*unused*/ 0, /*unused*/ 0, lazy[i]); + // Push delta to left/right segments for non-leaf nodes + propagateLazy(i, tl, tr, lazy[i]); + lazy[i] = null; + } + } + + private void rangeUpdate1(int i, int tl, int tr, int l, int r, long x) { + propagate1(i, tl, tr); + if (l > r) { + return; + } + + if (tl == l && tr == r) { + t[i] = minSegmentUpdateFn(t[i], /*unused*/ 0, /*unused*/ 0, x); + propagateLazy(i, tl, tr, x); + } else { + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case disregard + // invalid intervals. + rangeUpdate1(2 * i + 1, tl, tm, l, Math.min(tm, r), x); + rangeUpdate1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r, x); + + t[i] = minFunction(t[2 * i + 1], t[2 * i + 2]); + } + } + + public void printDebugInfo() { + printDebugInfo(0, 0, n - 1); + System.out.println(); + } + + private void printDebugInfo(int i, int tl, int tr) { + System.out.printf("[%d, %d], t[i] = %d, lazy[i] = %d\n", tl, tr, t[i], lazy[i]); + if (tl == tr) { + return; + } + int tm = (tl + tr) / 2; + printDebugInfo(2 * i + 1, tl, tm); + printDebugInfo(2 * i + 2, tm + 1, tr); + } + + //////////////////////////////////////////////////// + // Example usage: // + //////////////////////////////////////////////////// + + public static void main(String[] args) { + // 0, 1, 2, 3, 4 + long[] v = {2, 1, 3, 4, -1}; + MinQuerySumUpdateSegmentTree st = new MinQuerySumUpdateSegmentTree(v); + } +} diff --git a/com/williamfiset/algorithms/datastructures/segmenttree/Node.java b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/Node.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/segmenttree/Node.java rename to src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/Node.java diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/RangeQueryPointUpdateSegmentTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/RangeQueryPointUpdateSegmentTree.java new file mode 100644 index 000000000..757e3e214 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/RangeQueryPointUpdateSegmentTree.java @@ -0,0 +1,305 @@ +/** + * Simple segment tree implementation that supports a few range query operations (sum, min and max) + * along with point updates on individual elements. + * + *

Run with: ./gradlew run + * -Palgorithm=datastructures.segmenttree.RangeQueryPointUpdateSegmentTree + * + *

Several thanks to cp-algorithms for their great article on segment trees: + * https://cp-algorithms.com/data_structures/segment_tree.html + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +import java.util.Arrays; +import java.util.function.BinaryOperator; + +public class RangeQueryPointUpdateSegmentTree { + + // The type of segment combination function to use + public static enum SegmentCombinationFn { + SUM, + MIN, + MAX + } + + // When updating the value of a specific index position, or a range of values, + // modify the affected values using the following function: + public static enum RangeUpdateFn { + // When a range update is issued, add a value of `x` to all the elements in the range [l, r] + ADDITION, + // When a range update is issued, multiply all elements in the range [l, r] by a value of `x` + MULTIPLICATION + } + + // The number of elements in the original input values array. + private int n; + + // The segment tree represented as a binary tree of ranges where t[0] is the + // root node and the left and right children of node i are i*2+1 and i*2+2. + private long[] t; + + // The delta values associates with each segment. Used for lazy propagation + // when doing range updates. + private long[] lazy; + + private SegmentCombinationFn segmentCombinationFn; + + // The chosen range combination function + private BinaryOperator combinationFn; + + // The chosen range update function + private BinaryOperator rangeUpdateFn; + + private BinaryOperator sumFn = (a, b) -> a + b; + private BinaryOperator mulFn = (a, b) -> a * b; + private BinaryOperator minFn = (a, b) -> Math.min(a, b); + private BinaryOperator maxFn = (a, b) -> Math.max(a, b); + + public RangeQueryPointUpdateSegmentTree( + long[] values, SegmentCombinationFn segmentCombinationFunction) { + // By default, specify ADDITION as the range update function. + this(values, segmentCombinationFunction, RangeUpdateFn.ADDITION); + } + + public RangeQueryPointUpdateSegmentTree( + long[] values, + SegmentCombinationFn segmentCombinationFunction, + RangeUpdateFn rangeUpdateFunction) { + if (values == null) { + throw new IllegalArgumentException("Segment tree values cannot be null."); + } + if (segmentCombinationFunction == null) { + throw new IllegalArgumentException("Please specify a valid segment combination function."); + } + if (rangeUpdateFunction == null) { + throw new IllegalArgumentException("Please specify a valid range update function."); + } + n = values.length; + this.segmentCombinationFn = segmentCombinationFunction; + + // The size of the segment tree `t` + // + // TODO(william): Investigate to reduce this space. There are only 2n-1 segments, so we should + // be able to reduce the space, but may need to reorganize the tree/queries. One idea is to use + // the Eulerian tour structure of the tree to densely pack the segments. + int N = 4 * n; + + t = new long[N]; + lazy = new long[N]; + + // Select the specified combination function + if (segmentCombinationFunction == SegmentCombinationFn.SUM) { + combinationFn = sumFn; + } else if (segmentCombinationFunction == SegmentCombinationFn.MIN) { + Arrays.fill(t, Long.MAX_VALUE); + combinationFn = minFn; + } else if (segmentCombinationFunction == SegmentCombinationFn.MAX) { + Arrays.fill(t, Long.MIN_VALUE); + combinationFn = maxFn; + } + + // Select the specified range update function + if (rangeUpdateFunction == RangeUpdateFn.ADDITION) { + rangeUpdateFn = sumFn; + } else if (rangeUpdateFunction == RangeUpdateFn.MULTIPLICATION) { + rangeUpdateFn = mulFn; + } + + buildSegmentTree(0, 0, n - 1, values); + } + + /** + * Builds a segment tree by starting with the leaf nodes and combining segment values on callback. + * + * @param i the index of the segment in the segment tree + * @param tl the left index (inclusive) of the segment range + * @param tr the right index (inclusive) of the segment range + * @param values the initial values array + */ + private void buildSegmentTree(int i, int tl, int tr, long[] values) { + if (tl == tr) { + t[i] = values[tl]; + return; + } + int tm = (tl + tr) / 2; + buildSegmentTree(2 * i + 1, tl, tm, values); + buildSegmentTree(2 * i + 2, tm + 1, tr, values); + + t[i] = combinationFn.apply(t[2 * i + 1], t[2 * i + 2]); + } + + /** + * Returns the query of the range [l, r] on the original `values` array (+ any updates made to it) + * + * @param l the left endpoint of the range query (inclusive) + * @param r the right endpoint of the range query (inclusive) + */ + public long rangeQuery(int l, int r) { + return rangeQuery(0, 0, n - 1, l, r); + } + + /** + * Returns the query of the range [l, r] on the original `values` array (+ any updates made to it) + * + * @param l the left endpoint of the range query (inclusive) + * @param r the right endpoint of the range query (inclusive) + */ + public long rangeQuery2(int l, int r) { + return rangeQuery2(0, 0, n - 1, l, r); + } + + /** + * Returns the range query value of the range [l, r] + * + * @param i the index of the current segment in the tree + * @param tl the left endpoint (inclusive) of the current segment + * @param tr the right endpoint (inclusive) of the current segment + * @param l the target left endpoint (inclusive) for the range query + * @param r the target right endpoint (inclusive) for the range query + */ + private long rangeQuery(int i, int tl, int tr, int l, int r) { + if (l > r) { + // Different segment tree types have different base cases: + if (segmentCombinationFn == SegmentCombinationFn.SUM) { + return 0; + } else if (segmentCombinationFn == SegmentCombinationFn.MIN) { + return Long.MAX_VALUE; + } else if (segmentCombinationFn == SegmentCombinationFn.MAX) { + return Long.MIN_VALUE; + } + } + if (tl == l && tr == r) { + return t[i]; + } + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case return the + // default value for invalid intervals. + return combinationFn.apply( + rangeQuery(2 * i + 1, tl, tm, l, Math.min(tm, r)), + rangeQuery(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r)); + } + + /** + * Returns the range query value of the range [l, r] + * + *

An alternative implementation of the range query function that intelligently only digs into + * the branches of the segment tree which overlap with the query [l, r]. + * + *

This version of the range query implementation has the advantage that it doesn't need to + * know the explicit base case value for each range query type. + * + * @param i the index of the current segment in the tree + * @param tl the left endpoint (inclusive) of the current segment + * @param tr the right endpoint (inclusive) of the current segment + * @param l the target left endpoint (inclusive) for the range query + * @param r the target right endpoint (inclusive) for the range query + */ + private long rangeQuery2(int i, int tl, int tr, int l, int r) { + if (tl == l && tr == r) { + return t[i]; + } + int tm = (tl + tr) / 2; + // Test how the left and right segments of the interval [tl, tr] overlap with the query [l, r] + boolean overlapsLeftSegment = (l <= tm); + boolean overlapsRightSegment = (r > tm); + if (overlapsLeftSegment && overlapsRightSegment) { + return combinationFn.apply( + rangeQuery2(2 * i + 1, tl, tm, l, Math.min(tm, r)), + rangeQuery2(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r)); + } else if (overlapsLeftSegment) { + return rangeQuery2(2 * i + 1, tl, tm, l, Math.min(tm, r)); + } else { + return rangeQuery2(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r); + } + } + + // Updates the value at index `i` in the original `values` array to be `newValue`. + public void pointUpdate(int i, long newValue) { + pointUpdate(0, i, 0, n - 1, newValue); + } + + /** + * Update a point value to a new value and update all affected segments, O(log(n)) + * + *

Do this by performing a binary search to find the interval containing the point, then update + * the leaf segment with the new value, and re-compute all affected segment values on the + * callback. + * + * @param i the index of the current segment in the tree + * @param pos the target position to update + * @param tl the left segment endpoint (inclusive) + * @param tr the right segment endpoint (inclusive) + * @param newValue the new value to update + */ + private void pointUpdate(int i, int pos, int tl, int tr, long newValue) { + if (tl == tr) { // `tl == pos && tr == pos` might be clearer + t[i] = newValue; + return; + } + int tm = (tl + tr) / 2; + if (pos <= tm) { + // The point index `pos` is contained within the left segment [tl, tm] + pointUpdate(2 * i + 1, pos, tl, tm, newValue); + } else { + // The point index `pos` is contained within the right segment [tm+1, tr] + pointUpdate(2 * i + 2, pos, tm + 1, tr, newValue); + } + // Re-compute the segment value of the current segment on the callback + t[i] = combinationFn.apply(t[2 * i + 1], t[2 * i + 2]); + } + + // Updates the range of values between [l, r] the segment tree with `x` based + // on what RangeUpdateFn was chosen. + public void rangeUpdate(int l, int r, long x) { + throw new UnsupportedOperationException("rangeUpdate is not yet implemented"); + } + + //////////////////////////////////////////////////// + // Example usage: // + //////////////////////////////////////////////////// + + public static void main(String[] args) { + rangeSumQueryExample(); + rangeMinQueryExample(); + rangeMaxQueryExample(); + } + + private static void rangeSumQueryExample() { + // 0 1 2 3 + long[] values = {1, 2, 3, 2}; + RangeQueryPointUpdateSegmentTree st = + new RangeQueryPointUpdateSegmentTree(values, SegmentCombinationFn.SUM); + + int l = 0, r = 3; + System.out.printf("The sum between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery(l, r)); + // Prints: + // The sum between indeces [0, 3] is: 8 + } + + private static void rangeMinQueryExample() { + // 0 1 2 3 + long[] values = {1, 2, 3, 2}; + RangeQueryPointUpdateSegmentTree st = + new RangeQueryPointUpdateSegmentTree(values, SegmentCombinationFn.MIN); + + int l = 0, r = 3; + System.out.printf("The sum between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery(l, r)); + // Prints: + // The sum between indeces [0, 3] is: 1 + } + + private static void rangeMaxQueryExample() { + // 0 1 2 3 + long[] values = {1, 2, 3, 2}; + RangeQueryPointUpdateSegmentTree st = + new RangeQueryPointUpdateSegmentTree(values, SegmentCombinationFn.MAX); + + int l = 0, r = 3; + System.out.printf("The sum between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery(l, r)); + // Prints: + // The sum between indeces [0, 3] is: 3 + } +} diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQueryAssignUpdateSegmentTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQueryAssignUpdateSegmentTree.java new file mode 100644 index 000000000..015a8835c --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQueryAssignUpdateSegmentTree.java @@ -0,0 +1,189 @@ +/** + * Run with: ./gradlew run -Palgorithm=datastructures.segmenttree.SumQueryAssignUpdateSegmentTree + * + *

Several thanks to cp-algorithms for their great article on segment trees: + * https://cp-algorithms.com/data_structures/segment_tree.html + * + *

NOTE: This file is still a WIP + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +public class SumQueryAssignUpdateSegmentTree { + + // The number of elements in the original input values array. + private int n; + + // The segment tree represented as a binary tree of ranges where t[0] is the + // root node and the left and right children of node i are i*2+1 and i*2+2. + private Long[] t; + + // The delta values associates with each segment. Used for lazy propagation + // when doing range updates. + private Long[] lazy; + + private Long sumCombinationFn(Long a, Long b) { + if (a == null && b == null) return null; + if (a == null) return b; + if (b == null) return a; + return a + b; + } + + // Return the segment value if `x` was added to every element in the segment [tl, tr] + // during an assign update. + private Long sumRangeUpdateAssignFn(long base, int tl, int tr, long x) { + return (tr - tl + 1) * x; + } + + public SumQueryAssignUpdateSegmentTree(long[] values) { + if (values == null) { + throw new IllegalArgumentException("Segment tree values cannot be null."); + } + + n = values.length; + + // The size of the segment tree `t` + // + // TODO(william): Investigate to reduce this space. There are only 2n-1 segments, so we should + // be able to reduce the space, but may need to reorganize the tree/queries. One idea is to use + // the Eulerian tour structure of the tree to densely pack the segments. + int N = 4 * n; + + t = new Long[N]; + lazy = new Long[N]; + + buildSegmentTree(0, 0, n - 1, values); + } + + /** + * Builds a segment tree by starting with the leaf nodes and combining segment values on callback. + * + * @param i the index of the segment in the segment tree + * @param tl the left index (inclusive) of the segment range + * @param tr the right index (inclusive) of the segment range + * @param values the initial values array + */ + private void buildSegmentTree(int i, int tl, int tr, long[] values) { + if (tl == tr) { + t[i] = values[tl]; + return; + } + int tm = (tl + tr) / 2; + buildSegmentTree(2 * i + 1, tl, tm, values); + buildSegmentTree(2 * i + 2, tm + 1, tr, values); + + t[i] = sumCombinationFn(t[2 * i + 1], t[2 * i + 2]); + } + + /** + * Returns the query of the range [l, r] on the original `values` array (+ any updates made to it) + * + * @param l the left endpoint of the range query (inclusive) + * @param r the right endpoint of the range query (inclusive) + */ + public Long rangeQuery1(int l, int r) { + return rangeQuery1(0, 0, n - 1, l, r); + } + + /** + * Returns the range query value of the range [l, r] + * + * @param i the index of the current segment in the tree + * @param tl the left endpoint (inclusive) of the current segment + * @param tr the right endpoint (inclusive) of the current segment + * @param l the target left endpoint (inclusive) for the range query + * @param r the target right endpoint (inclusive) for the range query + */ + private Long rangeQuery1(int i, int tl, int tr, int l, int r) { + if (l > r) { + return null; + } + propagate1(i, tl, tr); + if (tl == l && tr == r) { + // System.out.printf("[%d, %d], t[i] = %d, lazy[i] = %d\n", tl, tr, t[i], lazy[i]); + return t[i]; + } + // System.out.printf("[%d, %d]\n", tl, tr); + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case return the + // default value for invalid intervals. + return sumCombinationFn( + rangeQuery1(2 * i + 1, tl, tm, l, Math.min(tm, r)), + rangeQuery1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r)); + } + + public void rangeUpdate1(int l, int r, long x) { + rangeUpdate1(0, 0, n - 1, l, r, x); + } + + // TODO(william): cleanup this function + private Long assignFunction(Long a, Long b) { + return b; + } + + private void propagateLazy(int i, int tl, int tr, long val) { + // Ignore leaf segments + if (tl == tr) return; + lazy[2 * i + 1] = assignFunction(/*unused*/ 0L, val); + lazy[2 * i + 2] = assignFunction(/*unused*/ 0L, val); + } + + private void propagate1(int i, int tl, int tr) { + // Check for default value because you don't want to assign to the lazy + // value if it's the default value. + if (lazy[i] != null) { + t[i] = sumRangeUpdateAssignFn(/*unused*/ 0L, tl, tr, lazy[i]); + // Push delta to left/right segments for non-leaf nodes + propagateLazy(i, tl, tr, lazy[i]); + lazy[i] = null; + } + } + + private void rangeUpdate1(int i, int tl, int tr, int l, int r, long x) { + propagate1(i, tl, tr); + if (l > r) { + return; + } + + if (tl == l && tr == r) { + t[i] = sumRangeUpdateAssignFn(/*unused*/ 0L, tl, tr, x); + propagateLazy(i, tl, tr, x); + } else { + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case disregard + // invalid intervals. + rangeUpdate1(2 * i + 1, tl, tm, l, Math.min(tm, r), x); + rangeUpdate1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r, x); + + t[i] = sumCombinationFn(t[2 * i + 1], t[2 * i + 2]); + } + } + + public void printDebugInfo() { + printDebugInfo(0, 0, n - 1); + System.out.println(); + } + + private void printDebugInfo(int i, int tl, int tr) { + System.out.printf("[%d, %d], t[i] = %d, lazy[i] = %d\n", tl, tr, t[i], lazy[i]); + if (tl == tr) { + return; + } + int tm = (tl + tr) / 2; + printDebugInfo(2 * i + 1, tl, tm); + printDebugInfo(2 * i + 2, tm + 1, tr); + } + + //////////////////////////////////////////////////// + // Example usage: // + //////////////////////////////////////////////////// + + public static void main(String[] args) { + // 0, 1, 2, 3, 4 + long[] v = {2, 1, 3, 4, -1}; + SumQueryAssignUpdateSegmentTree st = new SumQueryAssignUpdateSegmentTree(v); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQueryMultiplicationUpdateSegmentTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQueryMultiplicationUpdateSegmentTree.java new file mode 100644 index 000000000..4f2428eec --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQueryMultiplicationUpdateSegmentTree.java @@ -0,0 +1,195 @@ +/** + * Run with: ./gradlew run -Palgorithm=datastructures.segmenttree.SumQuerySumUpdateSegmentTree + * + *

Several thanks to cp-algorithms for their great article on segment trees: + * https://cp-algorithms.com/data_structures/segment_tree.html + * + *

NOTE: This file is still a WIP + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +public class SumQueryMultiplicationUpdateSegmentTree { + + // The number of elements in the original input values array. + private final int n; + + // The segment tree represented as a binary tree of ranges where t[0] is the + // root node and the left and right children of node i are i*2+1 and i*2+2. + private Long[] t; + + // The delta values associates with each segment. Used for lazy propagation + // when doing range updates. + private Long[] lazy; + + // Sum sumFunction + private Long sumFunction(Long a, Long b) { + if (a == null) a = 0L; + if (b == null) b = 0L; + return a + b; + } + + // Multiplication range update function + private Long multRuf(Long base, int tl, int tr, Long delta) { + // When we hit a null value, multiply by 1 since this is the + // multiplication identity, i.e: 1*x = x + if (base == null) base = 1L; + if (delta == null) delta = 1L; + return base * delta; + } + + // Lazy multiplication range update function + private long multLruf(Long delta1, Long delta2) { + // When we hit a null value, multiply by 1 since this is the + // multiplication identity, i.e: 1*x = x + if (delta1 == null) delta1 = 1L; + if (delta2 == null) delta2 = 1L; + // Multiply together the existing delta and the new delta to properly + // propagate the changes. + return delta1 * delta2; + } + + public SumQueryMultiplicationUpdateSegmentTree(long[] values) { + if (values == null) { + throw new IllegalArgumentException("Segment tree values cannot be null."); + } + n = values.length; + + // The size of the segment tree `t` + // + // TODO(william): Investigate to reduce this space. There are only 2n-1 segments, so we should + // be able to reduce the space, but may need to reorganize the tree/queries. One idea is to use + // the Eulerian tour structure of the tree to densely pack the segments. + int N = 4 * n; + + t = new Long[N]; + lazy = new Long[N]; + + buildSegmentTree(0, 0, n - 1, values); + } + + /** + * Builds a segment tree by starting with the leaf nodes and combining segment values on callback. + * + * @param i the index of the segment in the segment tree + * @param tl the left index (inclusive) of the segment range + * @param tr the right index (inclusive) of the segment range + * @param values the initial values array + */ + private void buildSegmentTree(int i, int tl, int tr, long[] values) { + if (tl == tr) { + t[i] = values[tl]; + return; + } + int tm = (tl + tr) / 2; + buildSegmentTree(2 * i + 1, tl, tm, values); + buildSegmentTree(2 * i + 2, tm + 1, tr, values); + + t[i] = sumFunction(t[2 * i + 1], t[2 * i + 2]); + } + + /** + * Returns the query of the range [l, r] on the original `values` array (+ any updates made to it) + * + * @param l the left endpoint of the range query (inclusive) + * @param r the right endpoint of the range query (inclusive) + */ + public Long rangeQuery1(int l, int r) { + return rangeQuery1(0, 0, n - 1, l, r); + } + + /** + * Returns the range query value of the range [l, r] + * + * @param i the index of the current segment in the tree + * @param tl the left endpoint (inclusive) of the current segment + * @param tr the right endpoint (inclusive) of the current segment + * @param l the target left endpoint (inclusive) for the range query + * @param r the target right endpoint (inclusive) for the range query + */ + private Long rangeQuery1(int i, int tl, int tr, int l, int r) { + if (l > r) { + return null; + } + propagate1(i, tl, tr); + if (tl == l && tr == r) { + return t[i]; + } + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case return the + // default value for invalid intervals. + return sumFunction( + rangeQuery1(2 * i + 1, tl, tm, l, Math.min(tm, r)), + rangeQuery1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r)); + } + + public void rangeUpdate1(int l, int r, long x) { + rangeUpdate1(0, 0, n - 1, l, r, x); + } + + private void propagateLazy(int i, int tl, int tr, long val) { + // Ignore leaf segments + if (tl == tr) return; + lazy[2 * i + 1] = multLruf(lazy[2 * i + 1], val); + lazy[2 * i + 2] = multLruf(lazy[2 * i + 2], val); + } + + private void propagate1(int i, int tl, int tr) { + // Check for default value because you don't want to assign to the lazy + // value if it's the default value. + if (lazy[i] != null) { + t[i] = multRuf(t[i], /*unused*/ 0, /*unused*/ 0, lazy[i]); + // Push delta to left/right segments for non-leaf nodes + propagateLazy(i, tl, tr, lazy[i]); + lazy[i] = null; + } + } + + private void rangeUpdate1(int i, int tl, int tr, int l, int r, long x) { + propagate1(i, tl, tr); + if (l > r) { + return; + } + + if (tl == l && tr == r) { + t[i] = multRuf(t[i], /*unused*/ 0, /*unused*/ 0, x); + propagateLazy(i, tl, tr, x); + } else { + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case disregard + // invalid intervals. + rangeUpdate1(2 * i + 1, tl, tm, l, Math.min(tm, r), x); + rangeUpdate1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r, x); + + t[i] = sumFunction(t[2 * i + 1], t[2 * i + 2]); + } + } + + public void printDebugInfo() { + printDebugInfo(0, 0, n - 1); + System.out.println(); + } + + private void printDebugInfo(int i, int tl, int tr) { + System.out.printf("[%d, %d], t[i] = %d, lazy[i] = %d\n", tl, tr, t[i], lazy[i]); + if (tl == tr) { + return; + } + int tm = (tl + tr) / 2; + printDebugInfo(2 * i + 1, tl, tm); + printDebugInfo(2 * i + 2, tm + 1, tr); + } + + //////////////////////////////////////////////////// + // Example usage: // + //////////////////////////////////////////////////// + + public static void main(String[] args) { + // 0, 1, 2, 3, 4 + long[] v = {2, 1, 3, 4, -1}; + SumQuerySumUpdateSegmentTree st = new SumQuerySumUpdateSegmentTree(v); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQuerySumUpdateSegmentTree.java b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQuerySumUpdateSegmentTree.java new file mode 100644 index 000000000..dc3498008 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQuerySumUpdateSegmentTree.java @@ -0,0 +1,185 @@ +/** + * Run with: ./gradlew run -Palgorithm=datastructures.segmenttree.SumQuerySumUpdateSegmentTree + * + *

Several thanks to cp-algorithms for their great article on segment trees: + * https://cp-algorithms.com/data_structures/segment_tree.html + * + *

NOTE: This file is still a WIP + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +public class SumQuerySumUpdateSegmentTree { + + // The number of elements in the original input values array. + private final int n; + + // The segment tree represented as a binary tree of ranges where t[0] is the + // root node and the left and right children of node i are i*2+1 and i*2+2. + private Long[] t; + + // The delta values associates with each segment. Used for lazy propagation + // when doing range updates. + private Long[] lazy; + + // Sum sumFunction + private Long sumFunction(Long a, Long b) { + if (a == null) a = 0L; + if (b == null) b = 0L; + return a + b; + } + + public SumQuerySumUpdateSegmentTree(long[] values) { + if (values == null) { + throw new IllegalArgumentException("Segment tree values cannot be null."); + } + n = values.length; + + // The size of the segment tree `t` + // + // TODO(william): Investigate to reduce this space. There are only 2n-1 segments, so we should + // be able to reduce the space, but may need to reorganize the tree/queries. One idea is to use + // the Eulerian tour structure of the tree to densely pack the segments. + int N = 4 * n; + + t = new Long[N]; + lazy = new Long[N]; + + buildSegmentTree(0, 0, n - 1, values); + } + + /** + * Builds a segment tree by starting with the leaf nodes and combining segment values on callback. + * + * @param i the index of the segment in the segment tree + * @param tl the left index (inclusive) of the segment range + * @param tr the right index (inclusive) of the segment range + * @param values the initial values array + */ + private void buildSegmentTree(int i, int tl, int tr, long[] values) { + if (tl == tr) { + t[i] = values[tl]; + return; + } + int tm = (tl + tr) / 2; + buildSegmentTree(2 * i + 1, tl, tm, values); + buildSegmentTree(2 * i + 2, tm + 1, tr, values); + + t[i] = sumFunction(t[2 * i + 1], t[2 * i + 2]); + } + + /** + * Returns the query of the range [l, r] on the original `values` array (+ any updates made to it) + * + * @param l the left endpoint of the range query (inclusive) + * @param r the right endpoint of the range query (inclusive) + */ + public Long rangeQuery1(int l, int r) { + return rangeQuery1(0, 0, n - 1, l, r); + } + + /** + * Returns the range query value of the range [l, r] + * + * @param i the index of the current segment in the tree + * @param tl the left endpoint (inclusive) of the current segment + * @param tr the right endpoint (inclusive) of the current segment + * @param l the target left endpoint (inclusive) for the range query + * @param r the target right endpoint (inclusive) for the range query + */ + private Long rangeQuery1(int i, int tl, int tr, int l, int r) { + if (l > r) { + return null; + } + propagate1(i, tl, tr); + if (tl == l && tr == r) { + return t[i]; + } + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case return the + // default value for invalid intervals. + return sumFunction( + rangeQuery1(2 * i + 1, tl, tm, l, Math.min(tm, r)), + rangeQuery1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r)); + } + + public void rangeUpdate1(int l, int r, long x) { + rangeUpdate1(0, 0, n - 1, l, r, x); + } + + private void propagateLazy(int i, int tl, int tr, long val) { + // Ignore leaf segments + if (tl == tr) return; + lazy[2 * i + 1] = sumFunction(lazy[2 * i + 1], val); + lazy[2 * i + 2] = sumFunction(lazy[2 * i + 2], val); + } + + private void propagate1(int i, int tl, int tr) { + // Check for default value because you don't want to assign to the lazy + // value if it's the default value. + if (lazy[i] != null) { + long rangeSum = (tr - tl + 1) * lazy[i]; + t[i] = sumFunction(t[i], rangeSum); + // Push delta to left/right segments for non-leaf nodes + propagateLazy(i, tl, tr, lazy[i]); + lazy[i] = null; + } + } + + private void rangeUpdate1(int i, int tl, int tr, int l, int r, long x) { + propagate1(i, tl, tr); + if (l > r) { + return; + } + + if (tl == l && tr == r) { + long rangeSum = (tr - tl + 1) * x; + t[i] = sumFunction(t[i], rangeSum); + propagateLazy(i, tl, tr, x); + } else { + int tm = (tl + tr) / 2; + // Instead of checking if [tl, tm] overlaps [l, r] and [tm+1, tr] overlaps + // [l, r], simply recurse on both segments and let the base case disregard + // invalid intervals. + rangeUpdate1(2 * i + 1, tl, tm, l, Math.min(tm, r), x); + rangeUpdate1(2 * i + 2, tm + 1, tr, Math.max(l, tm + 1), r, x); + + t[i] = sumFunction(t[2 * i + 1], t[2 * i + 2]); + } + } + + public void printDebugInfo() { + printDebugInfo(0, 0, n - 1); + System.out.println(); + } + + private void printDebugInfo(int i, int tl, int tr) { + System.out.printf("[%d, %d], t[i] = %d, lazy[i] = %d\n", tl, tr, t[i], lazy[i]); + if (tl == tr) { + return; + } + int tm = (tl + tr) / 2; + printDebugInfo(2 * i + 1, tl, tm); + printDebugInfo(2 * i + 2, tm + 1, tr); + } + + //////////////////////////////////////////////////// + // Example usage: // + //////////////////////////////////////////////////// + + public static void main(String[] args) { + // 0, 1, 2, 3, 4 + long[] v = {2, 1, 3, 4, -1}; + SumQuerySumUpdateSegmentTree st = new SumQuerySumUpdateSegmentTree(v); + + int l = 1; + int r = 3; + st.printDebugInfo(); + System.out.printf("The sum between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, r)); + st.rangeUpdate1(l, r, 3); + st.printDebugInfo(); + System.out.printf("The sum between indeces [%d, %d] is: %d\n", l, r, st.rangeQuery1(l, r)); + } +} diff --git a/com/williamfiset/algorithms/datastructures/set/HSet.java b/src/main/java/com/williamfiset/algorithms/datastructures/set/HSet.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/set/HSet.java rename to src/main/java/com/williamfiset/algorithms/datastructures/set/HSet.java diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/skiplist/SkipList.java b/src/main/java/com/williamfiset/algorithms/datastructures/skiplist/SkipList.java new file mode 100644 index 000000000..df3838d23 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/skiplist/SkipList.java @@ -0,0 +1,207 @@ +/** + * SkipList is a data structure that is useful for dealing with dynamic sorted data. In particular + * it gives O(log(n)) average complexity of insertion, removal, and find operations. This + * implementation has been augmented with a method for determining the index of an element in the + * SkipList. Finding the index of an element is also O(log(n)) on average. The complexities are + * average complexities since this algorithm is dependent on randomisation to achieve nice balanced + * properties. To make this efficient, instantiate the SkipList with a height equal to, or just + * greater than, log(n) where n is the number of elements that will be in the list. On average, this + * data structure uses O(n) space. Worst case space is O(nlog(n)), and worst case for all other + * operations is O(n) + * + * @author Finn Lidbetter + *

Refactored and rewritten by: + * @author Daniel Gustafsson + * @author Timmy Lindholm + * @author Anja Studic + * @author Christian Stjernberg + */ +package com.williamfiset.algorithms.datastructures.skiplist; + +import java.util.Random; + +class SkipList { + private Random rand = new Random(); + private int height; + private Node head; + private Node tail; + + /** + * This function creates a new skip list with the specified height and minimum and maximum values. + */ + public SkipList(int height, int minValue, int maxValue) { + // Height goes from 0 to height-1 + this.height = height; + head = new Node(minValue); + tail = new Node(maxValue); + Node currLeft = head; + Node currRight = tail; + // Setup links between all levels of head and tail nodes + for (int i = 1; i < height; i++) { + setHeadTail(height - i, currLeft, currRight); + // Create and setup down node + currLeft.down = new Node(currLeft.value); + currLeft.down.up = currLeft; + currRight.down = new Node(currRight.value); + currRight.down.up = currRight; + currLeft = currLeft.down; + currRight = currRight.down; + } + // Set last level + setHeadTail(0, currLeft, currRight); + } + + private static void setHeadTail(int height, Node nLeft, Node nRight) { + nLeft.right = nRight; + nRight.left = nLeft; + nLeft.index = 0; + nRight.index = 1; + nLeft.height = height; + nRight.height = height; + } + + public int size() { + return this.tail.index + 1; + } + + /** Return true if the number is in the list. False otherwise. */ + public boolean find(int num) { + return (search(num).value == num); + } + + // Search for closest node by number + private Node search(int num) { + return search(num, this.head); + } + + // Helper method for search + private Node search(int num, Node node) { + // Check if the next right node is still less than num + if (node.compareTo(num) < 0) { + if (node.down != null) return search(num, node.down); + else if (node.right != null && node.right.compareTo(num) <= 0) return search(num, node.right); + } + return node; + } + + /** Inserts the number into the list. */ + public boolean insert(int num) { + if (num < head.value || num > tail.value) return false; + Node node = search(num); + if (node.value == num) return false; + int nodeHeight = 0; + // 0.5 probability of having height 1, 0.25 for height 2 + while (rand.nextBoolean() && nodeHeight < (height - 1)) { + nodeHeight++; + } + insert(node, new Node(num), null, nodeHeight, node.index + 1); + return true; + } + + /** Helper method for insert */ + private void insert(Node startNode, Node insertNode, Node lower, int insertHeight, int distance) { + if (startNode.height <= insertHeight) { + insertNode.left = startNode; + insertNode.right = startNode.right; + // If not at lowest level + if (lower != null) { + lower.up = insertNode; + insertNode.down = lower; + } + // If at lowest level, update ranks of following + else if (startNode.height == 0) { + increaseRank(insertNode.right); + } + startNode.right.left = insertNode; + startNode.right = insertNode; + insertNode.height = startNode.height; + insertNode.index = distance; + Node curr = startNode; + while (curr.up == null && curr.left != null) { + curr = curr.left; + } + if (curr.up != null) { + curr = curr.up; + insert(curr, new Node(insertNode.value), insertNode, insertHeight, distance); + } + } + } + + /** + * Remove a the number from the list with the given value. Returns true if value was successfully + * removed. False otherwise. + */ + public boolean remove(int num) { + if (num == head.value || num == tail.value) return false; + // Get the node to remove + Node node = search(num); + if (node.value != num) return false; + // Re-number all nodes to the right + decreaseRank(node.right); + + // Connect the left and right nodes to each other + while (node.up != null) { + node.left.right = node.right; + node.right.left = node.left; + node = node.up; + } + node.left.right = node.right; + node.right.left = node.left; + + return true; + } + + // Decrease the index for all nodes to the right of the start node + private void decreaseRank(Node startNode) { + modifyRank(startNode, -1); + } + + // Increase the index for all nodes to the right of the start node + private void increaseRank(Node startNode) { + modifyRank(startNode, 1); + } + + // Modify the index for all nodes to the right of the start node + private void modifyRank(Node startNode, int change) { + Node node = startNode; + // Check all nodes to the right + while (startNode != null) { + node = startNode; + // Check all nodes upwards + while (node.up != null) { + node.index += change; + node = node.up; + } + node.index += change; + startNode = startNode.right; + } + } + + /** Returns the index of the number in the list or -1 if not found. */ + public int getIndex(int num) { + Node node = search(num); + return num == node.value ? node.index : -1; + } + + class Node implements Comparable { + Node left; + Node right; + Node up; + Node down; + int height; + int index; + int value; + + public Node(int value) { + this.value = value; + } + + public int compareTo(Node n2) { + return Integer.compare(this.value, n2.value); + } + + public int compareTo(int num) { + return Integer.compare(this.value, num); + } + } +} diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/sparsetable/SparseTable.java b/src/main/java/com/williamfiset/algorithms/datastructures/sparsetable/SparseTable.java new file mode 100644 index 000000000..7f6926378 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/sparsetable/SparseTable.java @@ -0,0 +1,259 @@ +/** + * Implementation of a sparse table which is a data structure that can very quickly query a range on + * a static array in O(1) for overlap friendly functions (idempotent functions) like min, max and + * gcd using O(n*logn) memory + * + *

Main inspiration: https://cp-algorithms.com/data_structures/sparse-table.html + * + *

Tested against: https://www.spoj.com/problems/RMQSQ + * + *

To run this file: + * + *

./gradlew run -Pmain=com.williamfiset.algorithms.datastructures.sparsetable.SparseTable + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.datastructures.sparsetable; + +import java.util.function.BinaryOperator; + +public class SparseTable { + + // The number of elements in the original input array. + private int n; + + // The maximum power of 2 needed. This value is floor(log2(n)) + private int P; + + // Fast log base 2 logarithm lookup table for i, 1 <= i <= n + private int[] log2; + + // The sparse table values. + private long[][] dp; + + // Index Table (IT) associated with the values in the sparse table. + private int[][] it; + + // The various supported query operations on this sparse table. + public enum Operation { + MIN, + MAX, + SUM, + MULT, + GCD + }; + + private Operation op; + + // All functions must be associative, e.g: a * (b * c) = (a * b) * c for some operation '*' + private BinaryOperator sumFn = (a, b) -> a + b; + private BinaryOperator minFn = (a, b) -> Math.min(a, b); + private BinaryOperator maxFn = (a, b) -> Math.max(a, b); + private BinaryOperator multFn = (a, b) -> a * b; + private BinaryOperator gcdFn = + (a, b) -> { + long gcd = a; + while (b != 0) { + gcd = b; + b = a % b; + a = gcd; + } + return Math.abs(gcd); + }; + + public SparseTable(long[] values, Operation op) { + // TODO(william): Lazily call init in query methods instead of initializing in constructor? + this.op = op; + init(values); + } + + private void init(long[] v) { + n = v.length; + + // Tip: to get the floor of the logarithm base 2 in Java you can also do: + // Integer.numberOfTrailingZeros(Integer.highestOneBit(n)). + P = (int) (Math.log(n) / Math.log(2)); + dp = new long[P + 1][n]; + it = new int[P + 1][n]; + + for (int i = 0; i < n; i++) { + dp[0][i] = v[i]; + it[0][i] = i; + } + + log2 = new int[n + 1]; + for (int i = 2; i <= n; i++) { + log2[i] = log2[i / 2] + 1; + } + + // Build sparse table combining the values of the previous intervals. + for (int i = 1; i <= P; i++) { + for (int j = 0; j + (1 << i) <= n; j++) { + long leftInterval = dp[i - 1][j]; + long rightInterval = dp[i - 1][j + (1 << (i - 1))]; + if (op == Operation.MIN) { + dp[i][j] = minFn.apply(leftInterval, rightInterval); + // Propagate the index of the best value + if (leftInterval <= rightInterval) { + it[i][j] = it[i - 1][j]; + } else { + it[i][j] = it[i - 1][j + (1 << (i - 1))]; + } + } else if (op == Operation.MAX) { + dp[i][j] = maxFn.apply(leftInterval, rightInterval); + // Propagate the index of the best value + if (leftInterval >= rightInterval) { + it[i][j] = it[i - 1][j]; + } else { + it[i][j] = it[i - 1][j + (1 << (i - 1))]; + } + } else if (op == Operation.SUM) { + dp[i][j] = sumFn.apply(leftInterval, rightInterval); + } else if (op == Operation.MULT) { + dp[i][j] = multFn.apply(leftInterval, rightInterval); + } else if (op == Operation.GCD) { + dp[i][j] = gcdFn.apply(leftInterval, rightInterval); + } + } + } + // Uncomment for debugging + // printTable(); + } + + // For debugging, testing and slides. + private void printTable() { + for (long[] r : dp) { + for (int i = 0; i < r.length; i++) { + System.out.printf("%02d, ", r[i]); + } + System.out.println(); + } + } + + // Queries [l, r] for the operation set on this sparse table. + public long query(int l, int r) { + // Fast queries types, O(1) + if (op == Operation.MIN) { + return query(l, r, minFn); + } else if (op == Operation.MAX) { + return query(l, r, maxFn); + } else if (op == Operation.GCD) { + return query(l, r, gcdFn); + } + + // Slower query types, O(log2(n)) + if (op == Operation.SUM) { + return sumQuery(l, r); + } else { + return multQuery(l, r); + } + } + + public int queryIndex(int l, int r) { + if (op == Operation.MIN) { + return minQueryIndex(l, r); + } else if (op == Operation.MAX) { + return maxQueryIndex(l, r); + } + throw new UnsupportedOperationException( + "Operation type: " + op + " doesn't support index queries :/"); + } + + private int minQueryIndex(int l, int r) { + int len = r - l + 1; + int p = log2[len]; + long leftInterval = dp[p][l]; + long rightInterval = dp[p][r - (1 << p) + 1]; + if (leftInterval <= rightInterval) { + return it[p][l]; + } else { + return it[p][r - (1 << p) + 1]; + } + } + + private int maxQueryIndex(int l, int r) { + int len = r - l + 1; + int p = log2[len]; + long leftInterval = dp[p][l]; + long rightInterval = dp[p][r - (1 << p) + 1]; + if (leftInterval >= rightInterval) { + return it[p][l]; + } else { + return it[p][r - (1 << p) + 1]; + } + } + + // Do sum query [l, r] in O(log2(n)). + // + // Perform a cascading query which shrinks the left endpoint while summing over all the intervals + // which are powers of 2 between [l, r]. + // + // WARNING: This method can easily produces values that overflow. + // + // NOTE: You can achieve a faster time complexity and use less memory with a simple prefix sum + // array. This method is here more as a proof of concept than for its usefulness. + private long sumQuery(int l, int r) { + long sum = 0; + for (int p = log2[r - l + 1]; l <= r; p = log2[r - l + 1]) { + sum += dp[p][l]; + l += (1 << p); + } + return sum; + } + + private long multQuery(int l, int r) { + long result = 1; + for (int p = log2[r - l + 1]; l <= r; p = log2[r - l + 1]) { + result *= dp[p][l]; + l += (1 << p); + } + return result; + } + + // Do either a min, max or gcd query on the interval [l, r] in O(1). + // + // We can get O(1) query by finding the smallest power of 2 that fits within the interval length + // which we'll call k. Then we can query the intervals [l, l+k] and [r-k+1, r] (which likely + // overlap) and apply the function again. Some functions (like min and max) don't care about + // overlapping intervals so this trick works, but for a function like sum this would return the + // wrong result since it is not an idempotent binary function. + private long query(int l, int r, BinaryOperator fn) { + int len = r - l + 1; + int p = log2[len]; + return fn.apply(dp[p][l], dp[p][r - (1 << p) + 1]); + } + + /* Example usage: */ + + public static void main(String[] args) { + // example1(); + // example2(); + example3(); + } + + private static void example1() { + long[] values = {1, 2, -3, 2, 4, -1, 5}; + + // Initialize sparse table to do range minimum queries. + SparseTable sparseTable = new SparseTable(values, SparseTable.Operation.MULT); + + System.out.println(sparseTable.query(2, 3)); + } + + private static void exampleFromSlides() { + long[] values = {4, 2, 3, 7, 1, 5, 3, 3, 9, 6, 7, -1, 4}; + + // Initialize sparse table to do range minimum queries. + SparseTable sparseTable = new SparseTable(values, SparseTable.Operation.MIN); + + System.out.printf("Min value between [2, 7] = %d\n", sparseTable.query(2, 7)); + } + + private static void example3() { + long[] values = {4, 4, 4, 4, 4, 4}; + // Initialize sparse table to do range minimum queries. + SparseTable sparseTable = new SparseTable(values, SparseTable.Operation.SUM); + + System.out.printf("%d\n", sparseTable.query(0, values.length - 1)); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/sparsetable/examples/MinSparseTable.java b/src/main/java/com/williamfiset/algorithms/datastructures/sparsetable/examples/MinSparseTable.java new file mode 100644 index 000000000..46dbcfbb6 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/sparsetable/examples/MinSparseTable.java @@ -0,0 +1,115 @@ +/** + * Min sparse table example + * + *

Download the code:
+ * $ git clone https://github.com/williamfiset/algorithms + * + *

Run:
+ * $ ./gradlew run -Palgorithm=datastructures.sparsetable.examples.MinSparseTable + * + *

Construction complexity: O(nlogn), query complexity: O(1) + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.datastructures.sparsetable.examples; + +// Sparse table for efficient minimum range queries in O(1) with O(nlogn) space +public class MinSparseTable { + + // Example usage: + public static void main(String[] args) { + // index values: 0, 1, 2, 3, 4, 5, 6 + long[] values = {1, 2, -3, 2, 4, -1, 5}; + MinSparseTable sparseTable = new MinSparseTable(values); + + System.out.println(sparseTable.queryMin(1, 5)); // prints -3 + System.out.println(sparseTable.queryMinIndex(1, 5)); // prints 2 + + System.out.println(sparseTable.queryMin(3, 3)); // prints 2 + System.out.println(sparseTable.queryMinIndex(3, 3)); // prints 3 + + System.out.println(sparseTable.queryMin(3, 6)); // prints -1 + System.out.println(sparseTable.queryMinIndex(3, 6)); // prints 5 + } + + // The number of elements in the original input array. + private int n; + + // The maximum power of 2 needed. This value is floor(log2(n)) + private int P; + + // Fast log base 2 logarithm lookup table, 1 <= i <= n + private int[] log2; + + // The sparse table values. + private long[][] dp; + + // Index Table (IT) associated with the values in the sparse table. This table + // is only useful when we want to query the index of the min (or max) element + // in the range [l, r] rather than the value itself. The index table doesn’t + // make sense for most other range query types like gcd or sum. + private int[][] it; + + public MinSparseTable(long[] values) { + n = values.length; + P = (int) (Math.log(n) / Math.log(2)); + dp = new long[P + 1][n]; + it = new int[P + 1][n]; + + for (int i = 0; i < n; i++) { + dp[0][i] = values[i]; + it[0][i] = i; + } + + log2 = new int[n + 1]; + for (int i = 2; i <= n; i++) { + log2[i] = log2[i / 2] + 1; + } + + // Build sparse table combining the values of the previous intervals. + for (int p = 1; p <= P; p++) { + for (int i = 0; i + (1 << p) <= n; i++) { + long leftInterval = dp[p - 1][i]; + long rightInterval = dp[p - 1][i + (1 << (p - 1))]; + dp[p][i] = Math.min(leftInterval, rightInterval); + + // Propagate the index of the best value + if (leftInterval <= rightInterval) { + it[p][i] = it[p - 1][i]; + } else { + it[p][i] = it[p - 1][i + (1 << (p - 1))]; + } + } + } + } + + // Do a min query on the interval [l, r] in O(1). + // + // We can get O(1) query by finding the smallest power of 2 that fits within + // the interval length which we'll call k. Then we can query the intervals + // [l, l+k] and [r-k+1, r] (which likely overlap) and apply the function + // again. Some functions (like min and max) don't care about overlapping + // intervals so this trick works, but for a function like sum this would + // return the wrong result since it is not an idempotent binary function + // (aka an overlap friendly function). + private long queryMin(int l, int r) { + int length = r - l + 1; + int p = log2[length]; + int k = 1 << p; // 2 to the power of p + return Math.min(dp[p][l], dp[p][r - k + 1]); + } + + // Returns the index of the minimum element in the range [l, r]. + public int queryMinIndex(int l, int r) { + int length = r - l + 1; + int p = log2[length]; + int k = 1 << p; // 2 to the power of p + long leftInterval = dp[p][l]; + long rightInterval = dp[p][r - k + 1]; + if (leftInterval <= rightInterval) { + return it[p][l]; + } else { + return it[p][r - k + 1]; + } + } +} diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/stack/ArrayStack.java b/src/main/java/com/williamfiset/algorithms/datastructures/stack/ArrayStack.java new file mode 100644 index 000000000..a831c4ca8 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/stack/ArrayStack.java @@ -0,0 +1,58 @@ +package com.williamfiset.algorithms.datastructures.stack; + +import java.util.Arrays; +import java.util.EmptyStackException; + +/** + * @author liujingkun + */ +public class ArrayStack implements Stack { + private int size; + private int capacity; + private Object[] data; + + public ArrayStack() { + capacity = 16; + data = new Object[capacity]; + } + + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public void push(T elem) { + if (size == capacity) { + increaseCapacity(); + } + data[size++] = elem; + } + + // Increase the capacity to store more elements. + private void increaseCapacity() { + capacity *= 2; + data = Arrays.copyOf(data, capacity); + } + + @Override + @SuppressWarnings("unchecked") + public T pop() { + if (isEmpty()) throw new EmptyStackException(); + T elem = (T) data[--size]; + data[size] = null; + return elem; + } + + @Override + @SuppressWarnings("unchecked") + public T peek() { + if (isEmpty()) throw new EmptyStackException(); + return (T) data[size - 1]; + } +} diff --git a/com/williamfiset/algorithms/datastructures/stack/IntStack.java b/src/main/java/com/williamfiset/algorithms/datastructures/stack/IntStack.java similarity index 72% rename from com/williamfiset/algorithms/datastructures/stack/IntStack.java rename to src/main/java/com/williamfiset/algorithms/datastructures/stack/IntStack.java index 0e8b6e026..42c661c6a 100644 --- a/com/williamfiset/algorithms/datastructures/stack/IntStack.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/stack/IntStack.java @@ -9,7 +9,7 @@ */ package com.williamfiset.algorithms.datastructures.stack; -public class IntStack { +public class IntStack implements Stack { private int[] ar; private int pos = 0; @@ -31,17 +31,20 @@ public boolean isEmpty() { } // Returns the element at the top of the stack - public int peek() { + @Override + public Integer peek() { return ar[pos - 1]; } // Add an element to the top of the stack - public void push(int value) { + @Override + public void push(Integer value) { ar[pos++] = value; } // Make sure you check that the stack is not empty before calling pop! - public int pop() { + @Override + public Integer pop() { return ar[--pos]; } @@ -83,13 +86,30 @@ private static void benchMarkTest() { System.out.println("IntStack Time: " + (end - start) / 1e9); // ArrayDeque times at around 1.438 seconds - java.util.ArrayDeque arrayDeque = new java.util.ArrayDeque<>(); - // java.util.ArrayDeque arrayDeque = new java.util.ArrayDeque<>(n); // strangely the + // java.util.ArrayDeque arrayDeque = new java.util.ArrayDeque<>(); + // java.util.Stack arrayDeque = new java.util.Stack<>(); + java.util.ArrayDeque arrayDeque = new java.util.ArrayDeque<>(n); // strangely the // ArrayQueue is slower when you give it an initial capacity. start = System.nanoTime(); for (int i = 0; i < n; i++) arrayDeque.push(i); for (int i = 0; i < n; i++) arrayDeque.pop(); end = System.nanoTime(); System.out.println("ArrayDeque Time: " + (end - start) / 1e9); + + Stack listStack = new ListStack<>(); + + start = System.nanoTime(); + for (int i = 0; i < n; i++) listStack.push(i); + for (int i = 0; i < n; i++) listStack.pop(); + end = System.nanoTime(); + System.out.println("ListStack Time: " + (end - start) / 1e9); + + Stack arrayStack = new ArrayStack<>(); + + start = System.nanoTime(); + for (int i = 0; i < n; i++) arrayStack.push(i); + for (int i = 0; i < n; i++) arrayStack.pop(); + end = System.nanoTime(); + System.out.println("ArrayStack Time: " + (end - start) / 1e9); } } diff --git a/com/williamfiset/algorithms/datastructures/stack/Stack.java b/src/main/java/com/williamfiset/algorithms/datastructures/stack/ListStack.java similarity index 79% rename from com/williamfiset/algorithms/datastructures/stack/Stack.java rename to src/main/java/com/williamfiset/algorithms/datastructures/stack/ListStack.java index a9b5fe669..244d8e185 100644 --- a/com/williamfiset/algorithms/datastructures/stack/Stack.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/stack/ListStack.java @@ -5,15 +5,15 @@ */ package com.williamfiset.algorithms.datastructures.stack; -public class Stack implements Iterable { +public class ListStack implements Iterable, Stack { private java.util.LinkedList list = new java.util.LinkedList(); // Create an empty stack - public Stack() {} + public ListStack() {} // Create a Stack with an initial element - public Stack(T firstElem) { + public ListStack(T firstElem) { push(firstElem); } @@ -46,6 +46,12 @@ public T peek() { return list.peekLast(); } + // Searches for the element starting from top of the stack + // Returns -1 if the element is not present in the stack + public int search(T elem) { + return list.lastIndexOf(elem); + } + // Allow users to iterate through the stack using an iterator @Override public java.util.Iterator iterator() { diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/stack/Stack.java b/src/main/java/com/williamfiset/algorithms/datastructures/stack/Stack.java new file mode 100644 index 000000000..61cacfcb5 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/datastructures/stack/Stack.java @@ -0,0 +1,20 @@ +package com.williamfiset.algorithms.datastructures.stack; + +/** + * @author liujingkun + */ +public interface Stack { + // return the number of elements in the stack + public int size(); + + // return if the stack is empty + public boolean isEmpty(); + + // push the element on the stack + public void push(T elem); + + // pop the element off the stack + public T pop(); + + public T peek(); +} diff --git a/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArray.java b/src/main/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArray.java similarity index 98% rename from com/williamfiset/algorithms/datastructures/suffixarray/SuffixArray.java rename to src/main/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArray.java index 77b5c498a..ac8702f12 100644 --- a/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArray.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArray.java @@ -88,6 +88,7 @@ private void kasai() { @Override public String toString() { + if (!constructedLcpArray) buildLcpArray(); StringBuilder sb = new StringBuilder(); sb.append("-----i-----SA-----LCP---Suffix\n"); diff --git a/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayFast.java b/src/main/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayFast.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayFast.java rename to src/main/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayFast.java diff --git a/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayMed.java b/src/main/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayMed.java similarity index 99% rename from com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayMed.java rename to src/main/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayMed.java index cdf45f910..23e4e9002 100644 --- a/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayMed.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayMed.java @@ -5,8 +5,6 @@ */ package com.williamfiset.algorithms.datastructures.suffixarray; -import java.util.*; - public class SuffixArrayMed extends SuffixArray { // Wrapper class to help sort suffix ranks diff --git a/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArraySlow.java b/src/main/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArraySlow.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/suffixarray/SuffixArraySlow.java rename to src/main/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArraySlow.java diff --git a/com/williamfiset/algorithms/datastructures/trie/Trie.java b/src/main/java/com/williamfiset/algorithms/datastructures/trie/Trie.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/trie/Trie.java rename to src/main/java/com/williamfiset/algorithms/datastructures/trie/Trie.java diff --git a/com/williamfiset/algorithms/datastructures/unionfind/UnionFind.java b/src/main/java/com/williamfiset/algorithms/datastructures/unionfind/UnionFind.java similarity index 97% rename from com/williamfiset/algorithms/datastructures/unionfind/UnionFind.java rename to src/main/java/com/williamfiset/algorithms/datastructures/unionfind/UnionFind.java index 5369ab385..a52227eaa 100644 --- a/com/williamfiset/algorithms/datastructures/unionfind/UnionFind.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/unionfind/UnionFind.java @@ -83,19 +83,21 @@ public int components() { // Unify the components/sets containing elements 'p' and 'q' public void unify(int p, int q) { + // These elements are already in the same group! + if (connected(p, q)) return; + int root1 = find(p); int root2 = find(q); - // These elements are already in the same group! - if (root1 == root2) return; - // Merge smaller component/set into the larger one. if (sz[root1] < sz[root2]) { sz[root2] += sz[root1]; id[root1] = root2; + sz[root1] = 0; } else { sz[root1] += sz[root2]; id[root2] = root1; + sz[root2] = 0; } // Since the roots found are different we know that the diff --git a/com/williamfiset/algorithms/datastructures/utils/TestUtils.java b/src/main/java/com/williamfiset/algorithms/datastructures/utils/TestUtils.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/utils/TestUtils.java rename to src/main/java/com/williamfiset/algorithms/datastructures/utils/TestUtils.java diff --git a/com/williamfiset/algorithms/datastructures/utils/TreePrinter.java b/src/main/java/com/williamfiset/algorithms/datastructures/utils/TreePrinter.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/utils/TreePrinter.java rename to src/main/java/com/williamfiset/algorithms/datastructures/utils/TreePrinter.java diff --git a/src/main/java/com/williamfiset/algorithms/dp/CoinChange.java b/src/main/java/com/williamfiset/algorithms/dp/CoinChange.java new file mode 100644 index 000000000..5daa688d7 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/CoinChange.java @@ -0,0 +1,218 @@ +/** + * The coin change problem is an unbounded knapsack problem variant. The problem asks you to find + * the minimum number of coins required for a certain amount of change given the coin denominations. + * You may use each coin denomination as many times as you please. + * + *

Tested against: https://leetcode.com/problems/coin-change + * + *

Run locally: + * + *

./gradlew run -Palgorithm=dp.CoinChange + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.dp; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class CoinChange { + + public static class Solution { + // Contains the minimum number of coins to make a certain amount, if a solution exists. + Optional minCoins = Optional.empty(); + + // The coins selected as part of the optimal solution. + List selectedCoins = new ArrayList(); + } + + // TODO(william): setting an explicit infinity could lead to a wrong answer for + // very large values. Prefer to use null instead. + private static final int INF = Integer.MAX_VALUE / 2; + + public static Solution coinChange(int[] coins, final int n) { + if (coins == null) throw new IllegalArgumentException("Coins array is null"); + if (coins.length == 0) throw new IllegalArgumentException("No coin values :/"); + for (int coin : coins) { + if (coin <= 0) { + throw new IllegalArgumentException("Coin with value `" + coin + "` is not allowed."); + } + } + + final int m = coins.length; + // Initialize table and set first row to be infinity + int[][] dp = new int[m + 1][n + 1]; + java.util.Arrays.fill(dp[0], INF); + dp[1][0] = 0; + + // Iterate through all the coins + for (int i = 1; i <= m; i++) { + int coinValue = coins[i - 1]; + for (int j = 1; j <= n; j++) { + + // Consider not selecting this coin + dp[i][j] = dp[i - 1][j]; + + // Try selecting this coin if it's better + if (j - coinValue >= 0 && dp[i][j - coinValue] + 1 < dp[i][j]) { + dp[i][j] = dp[i][j - coinValue] + 1; + } + } + } + + // p(dp); + + Solution solution = new Solution(); + + if (dp[m][n] != INF) { + solution.minCoins = Optional.of(dp[m][n]); + } else { + return solution; + } + + for (int change = n, coinIndex = m; coinIndex > 0; ) { + int coinValue = coins[coinIndex - 1]; + boolean canSelectCoin = change - coinValue >= 0; + if (canSelectCoin && dp[coinIndex][change - coinValue] < dp[coinIndex][change]) { + solution.selectedCoins.add(coinValue); + change -= coinValue; + } else { + coinIndex--; + } + } + + return solution; + } + + public static Solution coinChangeSpaceEfficient(int[] coins, int n) { + if (coins == null) throw new IllegalArgumentException("Coins array is null"); + + // Initialize table and set everything to infinity except first cell + int[] dp = new int[n + 1]; + java.util.Arrays.fill(dp, INF); + dp[0] = 0; + + for (int i = 1; i <= n; i++) { + for (int coin : coins) { + if (i - coin < 0) { + continue; + } + if (dp[i - coin] + 1 < dp[i]) { + dp[i] = dp[i - coin] + 1; + } + } + } + + Solution solution = new Solution(); + if (dp[n] != INF) { + solution.minCoins = Optional.of(dp[n]); + } else { + return solution; + } + + for (int i = n; i > 0; ) { + int selectedCoinValue = INF; + int cellWithFewestCoins = dp[i]; + for (int coin : coins) { + if (i - coin < 0) { + continue; + } + if (dp[i - coin] < cellWithFewestCoins) { + cellWithFewestCoins = dp[i - coin]; + selectedCoinValue = coin; + } + } + solution.selectedCoins.add(selectedCoinValue); + i -= selectedCoinValue; + } + + // Return the minimum number of coins needed + return solution; + } + + // The recursive approach has the advantage that it does not have to visit + // all possible states like the tabular approach does. This can speedup + // things especially if the coin denominations are large. + public static int coinChangeRecursive(int[] coins, int n) { + if (coins == null) throw new IllegalArgumentException("Coins array is null"); + if (n < 0) return -1; + + int[] dp = new int[n + 1]; + return coinChangeRecursive(n, coins, dp); + } + + // Private helper method to actually go the recursion + private static int coinChangeRecursive(int n, int[] coins, int[] dp) { + if (n < 0) return -1; + if (n == 0) return 0; + if (dp[n] != 0) return dp[n]; + + int minCoins = INF; + for (int coinValue : coins) { + int value = coinChangeRecursive(n - coinValue, coins, dp); + if (value != -1 && value < minCoins) minCoins = value + 1; + } + + // If we weren't able to find some coins to make our + // amount then cache -1 as the answer. + return dp[n] = (minCoins == INF) ? -1 : minCoins; + } + + // DP table print function. Used for debugging. + private static void p(int[][] dp) { + for (int[] r : dp) { + for (int v : r) { + System.out.printf("%4d, ", v == INF ? -1 : v); + } + System.out.println(); + } + } + + private static void p(int[] dp) { + for (int v : dp) { + System.out.printf("%4d, ", v == INF ? -1 : v); + } + System.out.println(); + } + + public static void main(String[] args) { + // example1(); + // example2(); + // example3(); + example4(); + } + + private static void example4() { + int n = 11; + int[] coins = {2, 4, 1}; + // System.out.println(coinChange(coins, n).minCoins); + System.out.println(coinChangeSpaceEfficient(coins, n)); + // System.out.println(coinChangeRecursive(coins, n)); + // System.out.println(coinChange(coins, n).selectedCoins); + } + + private static void example1() { + int[] coins = {2, 6, 1}; + System.out.println(coinChange(coins, 17).minCoins); + System.out.println(coinChange(coins, 17).selectedCoins); + System.out.println(coinChangeSpaceEfficient(coins, 17)); + System.out.println(coinChangeRecursive(coins, 17)); + } + + private static void example2() { + int[] coins = {2, 3, 5}; + System.out.println(coinChange(coins, 12).minCoins); + System.out.println(coinChange(coins, 12).selectedCoins); + System.out.println(coinChangeSpaceEfficient(coins, 12)); + System.out.println(coinChangeRecursive(coins, 12)); + } + + private static void example3() { + int[] coins = {3, 4, 7}; + System.out.println(coinChange(coins, 17).minCoins); + System.out.println(coinChange(coins, 17).selectedCoins); + System.out.println(coinChangeSpaceEfficient(coins, 17)); + System.out.println(coinChangeRecursive(coins, 17)); + } +} diff --git a/com/williamfiset/algorithms/dp/EditDistance.java b/src/main/java/com/williamfiset/algorithms/dp/EditDistanceIterative.java similarity index 61% rename from com/williamfiset/algorithms/dp/EditDistance.java rename to src/main/java/com/williamfiset/algorithms/dp/EditDistanceIterative.java index 70ebc7a1a..25dfeea42 100644 --- a/com/williamfiset/algorithms/dp/EditDistance.java +++ b/src/main/java/com/williamfiset/algorithms/dp/EditDistanceIterative.java @@ -7,7 +7,7 @@ */ package com.williamfiset.algorithms.dp; -public class EditDistance { +public class EditDistanceIterative { // Computes the cost to convert a string 'a' into a string 'b' using dynamic // programming given the insertionCost, deletionCost and substitutionCost, O(nm) @@ -15,7 +15,7 @@ public static int editDistance( String a, String b, int insertionCost, int deletionCost, int substitutionCost) { final int AL = a.length(), BL = b.length(); - int[][] arr = new int[AL + 1][BL + 1]; + int[][] dp = new int[AL + 1][BL + 1]; for (int i = 0; i <= AL; i++) { for (int j = (i == 0 ? 1 : 0); j <= BL; j++) { @@ -24,19 +24,19 @@ public static int editDistance( // Substitution if (i > 0 && j > 0) - min = arr[i - 1][j - 1] + (a.charAt(i - 1) == b.charAt(j - 1) ? 0 : substitutionCost); + min = dp[i - 1][j - 1] + (a.charAt(i - 1) == b.charAt(j - 1) ? 0 : substitutionCost); // Deletion - if (i > 0) min = Math.min(min, arr[i - 1][j] + deletionCost); + if (i > 0) min = Math.min(min, dp[i - 1][j] + deletionCost); // Insertion - if (j > 0) min = Math.min(min, arr[i][j - 1] + insertionCost); + if (j > 0) min = Math.min(min, dp[i][j - 1] + insertionCost); - arr[i][j] = min; + dp[i][j] = min; } } - return arr[AL][BL]; + return dp[AL][BL]; } public static void main(String[] args) { @@ -45,25 +45,30 @@ public static void main(String[] args) { String b = "abcdefg"; // The strings are the same so the cost is zero - System.out.println(editDistance(a, b, 10, 10, 10)); + System.out.println(EditDistanceIterative.editDistance(a, b, 10, 10, 10)); a = "aaa"; b = "aaabbb"; // 10*3 = 30 because of three insertions - System.out.println(editDistance(a, b, 10, 2, 3)); + System.out.println(EditDistanceIterative.editDistance(a, b, 10, 2, 3)); a = "1023"; b = "10101010"; // Outputs 2*2 + 4*5 = 24 for 2 substitutions and 4 insertions - System.out.println(editDistance(a, b, 5, 7, 2)); + System.out.println(EditDistanceIterative.editDistance(a, b, 5, 7, 2)); a = "923456789"; b = "12345"; // Outputs 4*4 + 1 = 16 because we need to delete 4 // characters and perform one substitution - System.out.println(editDistance(a, b, 2, 4, 1)); + System.out.println(EditDistanceIterative.editDistance(a, b, 2, 4, 1)); + + a = "aaaaa"; + b = "aabaa"; + + System.out.println(EditDistanceIterative.editDistance(a, b, 2, 3, 10)); } } diff --git a/src/main/java/com/williamfiset/algorithms/dp/EditDistanceRecursive.java b/src/main/java/com/williamfiset/algorithms/dp/EditDistanceRecursive.java new file mode 100644 index 000000000..bb79e33b5 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/EditDistanceRecursive.java @@ -0,0 +1,93 @@ +/** + * A solution to the edit distance problem + * + *

Tested against: https://leetcode.com/problems/edit-distance + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.dp; + +public class EditDistanceRecursive { + + final char[] a, b; + final int insertionCost, deletionCost, substitutionCost; + + public EditDistanceRecursive( + String a, String b, int insertionCost, int deletionCost, int substitutionCost) { + if (a == null || b == null) { + throw new IllegalArgumentException("Input string must not be null"); + } + this.a = a.toCharArray(); + this.b = b.toCharArray(); + this.insertionCost = insertionCost; + this.deletionCost = deletionCost; + this.substitutionCost = substitutionCost; + } + + private static int min(int... values) { + int m = Integer.MAX_VALUE; + for (int v : values) { + if (v < m) { + m = v; + } + } + return m; + } + + // Returns the Levenshtein distance to transform string `a` into string `b`. + public int editDistance() { + Integer[][] dp = new Integer[a.length + 1][b.length + 1]; + return f(dp, 0, 0); + } + + private int f(Integer[][] dp, int i, int j) { + if (i == a.length && j == b.length) { + return 0; + } + if (i == a.length) { + return (b.length - j) * insertionCost; + } + if (j == b.length) { + return (a.length - i) * deletionCost; + } + if (dp[i][j] != null) { + return dp[i][j]; + } + int substituteOrSkip = f(dp, i + 1, j + 1) + (a[i] == b[j] ? 0 : substitutionCost); + int delete = f(dp, i + 1, j) + deletionCost; + int insert = f(dp, i, j + 1) + insertionCost; + return dp[i][j] = min(substituteOrSkip, delete, insert); + } + + public static void main(String[] args) { + String a = "923456789"; + String b = "12345"; + EditDistanceRecursive solver = new EditDistanceRecursive(a, b, 100, 4, 2); + System.out.println(solver.editDistance()); + + a = "12345"; + b = "923456789"; + solver = new EditDistanceRecursive(a, b, 100, 4, 2); + System.out.println(solver.editDistance()); + + a = "aaa"; + b = "aaabbb"; + solver = new EditDistanceRecursive(a, b, 10, 2, 3); + System.out.println(solver.editDistance()); + + a = "1023"; + b = "10101010"; + solver = new EditDistanceRecursive(a, b, 5, 7, 2); + System.out.println(solver.editDistance()); + + a = "923456789"; + b = "12345"; + EditDistanceRecursive solver2 = new EditDistanceRecursive(a, b, 100, 4, 2); + System.out.println(solver2.editDistance()); + + a = "aaaaa"; + b = "aabaa"; + solver = new EditDistanceRecursive(a, b, 2, 3, 10); + System.out.println(solver.editDistance()); + } +} diff --git a/com/williamfiset/algorithms/dp/JosephusProblem.java b/src/main/java/com/williamfiset/algorithms/dp/JosephusProblem.java similarity index 100% rename from com/williamfiset/algorithms/dp/JosephusProblem.java rename to src/main/java/com/williamfiset/algorithms/dp/JosephusProblem.java diff --git a/com/williamfiset/algorithms/dp/KnapsackUnbounded.java b/src/main/java/com/williamfiset/algorithms/dp/KnapsackUnbounded.java similarity index 100% rename from com/williamfiset/algorithms/dp/KnapsackUnbounded.java rename to src/main/java/com/williamfiset/algorithms/dp/KnapsackUnbounded.java diff --git a/com/williamfiset/algorithms/dp/Knapsack_01.java b/src/main/java/com/williamfiset/algorithms/dp/Knapsack_01.java similarity index 96% rename from com/williamfiset/algorithms/dp/Knapsack_01.java rename to src/main/java/com/williamfiset/algorithms/dp/Knapsack_01.java index aa001f1bb..8cea8b26a 100644 --- a/com/williamfiset/algorithms/dp/Knapsack_01.java +++ b/src/main/java/com/williamfiset/algorithms/dp/Knapsack_01.java @@ -11,6 +11,9 @@ */ package com.williamfiset.algorithms.dp; +import java.util.ArrayList; +import java.util.List; + public class Knapsack_01 { /** @@ -48,7 +51,7 @@ public static int knapsack(int capacity, int[] W, int[] V) { } int sz = capacity; - java.util.List itemsSelected = new java.util.ArrayList<>(); + List itemsSelected = new ArrayList<>(); // Using the information inside the table we can backtrack and determine // which items were selected during the dynamic programming phase. The idea diff --git a/com/williamfiset/algorithms/dp/LongestCommonSubsequence.java b/src/main/java/com/williamfiset/algorithms/dp/LongestCommonSubsequence.java similarity index 100% rename from com/williamfiset/algorithms/dp/LongestCommonSubsequence.java rename to src/main/java/com/williamfiset/algorithms/dp/LongestCommonSubsequence.java diff --git a/com/williamfiset/algorithms/dp/LongestCommonSubstring.java b/src/main/java/com/williamfiset/algorithms/dp/LongestCommonSubstring.java similarity index 100% rename from com/williamfiset/algorithms/dp/LongestCommonSubstring.java rename to src/main/java/com/williamfiset/algorithms/dp/LongestCommonSubstring.java diff --git a/com/williamfiset/algorithms/dp/LongestIncreasingSubsequence.java b/src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequence.java similarity index 100% rename from com/williamfiset/algorithms/dp/LongestIncreasingSubsequence.java rename to src/main/java/com/williamfiset/algorithms/dp/LongestIncreasingSubsequence.java diff --git a/com/williamfiset/algorithms/dp/LongestPalindromeSubsequence.java b/src/main/java/com/williamfiset/algorithms/dp/LongestPalindromeSubsequence.java similarity index 100% rename from com/williamfiset/algorithms/dp/LongestPalindromeSubsequence.java rename to src/main/java/com/williamfiset/algorithms/dp/LongestPalindromeSubsequence.java diff --git a/com/williamfiset/algorithms/dp/MaximumSubarray.java b/src/main/java/com/williamfiset/algorithms/dp/MaximumSubarray.java similarity index 100% rename from com/williamfiset/algorithms/dp/MaximumSubarray.java rename to src/main/java/com/williamfiset/algorithms/dp/MaximumSubarray.java diff --git a/com/williamfiset/algorithms/dp/MinimumWeightPerfectMatching.java b/src/main/java/com/williamfiset/algorithms/dp/MinimumWeightPerfectMatching.java similarity index 53% rename from com/williamfiset/algorithms/dp/MinimumWeightPerfectMatching.java rename to src/main/java/com/williamfiset/algorithms/dp/MinimumWeightPerfectMatching.java index 3653cea81..6d3ed625e 100644 --- a/com/williamfiset/algorithms/dp/MinimumWeightPerfectMatching.java +++ b/src/main/java/com/williamfiset/algorithms/dp/MinimumWeightPerfectMatching.java @@ -3,7 +3,11 @@ * given a distance matrix which gives the distance from each node to every other node, and you want * to pair up all the nodes to one another minimizing the overall cost. * - *

Time Complexity: O(n^2 * 2^n) + *

Tested against: UVA 10911 - Forming Quiz Teams + * + *

To Run: ./gradlew run -Palgorithm=dp.MinimumWeightPerfectMatching + * + *

Time Complexity: O(n * 2^n) * * @author William Fiset */ @@ -19,6 +23,7 @@ public class MinimumWeightPerfectMatching { private double[][] cost; // Internal + private final int END_STATE; private boolean solved; // Outputs @@ -36,11 +41,12 @@ public MinimumWeightPerfectMatching(double[][] cost) { throw new IllegalArgumentException( "Matrix too large! A matrix that size for the MWPM problem with a time complexity of" + "O(n^2*2^n) requires way too much computation and memory for a modern home computer."); + END_STATE = (1 << n) - 1; this.cost = cost; } public double getMinWeightCost() { - solve(); + solveRecursive(); return minWeightCost; } @@ -62,44 +68,94 @@ public double getMinWeightCost() { * } */ public int[] getMinWeightCostMatching() { - solve(); + solveRecursive(); return matching; } - public void solve() { + // Recursive impl + public void solveRecursive() { if (solved) return; + Double[] dp = new Double[1 << n]; + int[] history = new int[1 << n]; + minWeightCost = f(END_STATE, dp, history); + reconstructMatching(history); + solved = true; + } - final int END_STATE = (1 << n) - 1; + private double f(int state, Double[] dp, int[] history) { + if (dp[state] != null) { + return dp[state]; + } + if (state == 0) { + return 0; + } + int p1, p2; + // Seek to find active bit position (p1) + for (p1 = 0; p1 < n; p1++) { + if ((state & (1 << p1)) > 0) { + break; + } + } + int bestState = -1; + double minimum = Double.MAX_VALUE; + + for (p2 = p1 + 1; p2 < n; p2++) { + // Position `p2` is on. Try matching the pair (p1, p2) together. + if ((state & (1 << p2)) > 0) { + int reducedState = state ^ (1 << p1) ^ (1 << p2); + double matchCost = f(reducedState, dp, history) + cost[p1][p2]; + if (matchCost < minimum) { + minimum = matchCost; + bestState = reducedState; + } + } + } + history[state] = bestState; + return dp[state] = minimum; + } + + public void solve() { + if (solved) return; // The DP state is encoded as a bitmask where the i'th bit is flipped on if the i'th node is // included in the state. Encoding the state this way allows us to compactly represent selecting // a subset of the nodes present in the matching. Furthermore, it allows using the '&' binary // operator to compare states to see if they overlap and the '|' operator to combine states. + // + // dp[i] contains the optimal cost of the MWPM for the nodes captured in the binary + // representation of `i`. The dp table is always half empty because all states with an odd + // number of nodes do not have a MWPM. Double[] dp = new Double[1 << n]; // Memo table to save the history of the chosen states. This table is used to reconstruct the // chosen pairs of nodes after the algorithm has executed. int[] history = new int[1 << n]; - // Singleton pair states with only two nodes are the building blocks of this algorithm. Every - // iteration, we try to add singleton pairs to previous states to construct a larger matching. - final int numPairs = (n * (n + 1)) / 2; + // All the states consisting of pairs of nodes are the building blocks of this algorithm. + // In every iteration, we try to add a pair of nodes to previous state to construct a larger + // matching. + final int numPairs = (n * (n - 1)) / 2; int[] pairStates = new int[numPairs]; double[] pairCost = new double[numPairs]; - for (int i = 0, k = 0; i < n; i++) { - for (int j = i + 1; j < n; j++, k++) { + int k = 0; + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { int state = (1 << i) | (1 << j); dp[state] = cost[i][j]; pairStates[k] = state; pairCost[k] = cost[i][j]; + k++; } } - for (int state = 0; state < (1 << n); state++) { - // A cost of null means the previous state does not exist. - if (dp[state] == null) continue; - for (int i = 0; i < numPairs; i++) { + for (int state = 0b11; state < (1 << n); state++) { // O(2^n) + // Skip states with an odd number of bits (nodes). It's easier (and faster) to + // check dp[state] instead of calling `Integer.bitCount` for the bit count. + if (dp[state] == null) { + continue; + } + for (int i = 0; i < numPairs; i++) { // O(n^2) int pair = pairStates[i]; // Ignore states which overlap if ((state & pair) != 0) continue; @@ -116,19 +172,46 @@ public void solve() { } } - // Reconstruct the matching of pairs of nodes. - matching = new int[n]; + reconstructMatching(history); + + minWeightCost = dp[END_STATE]; + solved = true; + } + + // Populates the `matching` array with a sorted deterministic matching sorted by lowest node + // index. For example, if the perfect matching consists of the pairs (3, 4), (1, 5), (0, 2). + // The matching is sorted such that the pairs appear in the ordering: (0, 2), (1, 5), (3, 4). + // Furthermore, it is guaranteed that for any pair (a, b) that a < b. + private void reconstructMatching(int[] history) { + // A map between pairs of nodes that were matched together. + int[] map = new int[n]; + int[] leftNodes = new int[n / 2]; + + // Reconstruct the matching of pairs of nodes working backwards through computed states. for (int i = 0, state = END_STATE; state != 0; state = history[state]) { + // Isolate the pair used by xoring the state with the state used to generate it. int pairUsed = state ^ history[state]; - matching[i++] = getBitPosition(Integer.lowestOneBit(pairUsed)); - matching[i++] = getBitPosition(Integer.highestOneBit(pairUsed)); + + int leftNode = getBitPosition(Integer.lowestOneBit(pairUsed)); + int rightNode = getBitPosition(Integer.highestOneBit(pairUsed)); + + leftNodes[i++] = leftNode; + map[leftNode] = rightNode; } - minWeightCost = dp[END_STATE]; - solved = true; + // Sort the left nodes in ascending order. + java.util.Arrays.sort(leftNodes); + + matching = new int[n]; + for (int i = 0; i < n / 2; i++) { + matching[2 * i] = leftNodes[i]; + int rightNode = map[leftNodes[i]]; + matching[2 * i + 1] = rightNode; + } } - // Gets the zero base index position of the 1 bit in 'k' + // Gets the zero base index position of the 1 bit in `k`. `k` must be a power of 2, so there is + // only ever 1 bit in the binary representation of k. private int getBitPosition(int k) { int count = -1; while (k > 0) { @@ -141,7 +224,20 @@ private int getBitPosition(int k) { /* Example */ public static void main(String[] args) { - int n = 18; + // test1(); + // for (int i = 0; i < 50; i++) { + // if (include(i)) System.out.printf("%2d %7s\n", i, Integer.toBinaryString(i)); + // } + } + + private static boolean include(int i) { + boolean toInclude = Integer.bitCount(i) >= 2 && Integer.bitCount(i) % 2 == 0; + return toInclude; + } + + private static void test1() { + // int n = 18; + int n = 6; List pts = new ArrayList<>(); // Generate points on a 2D plane which will produce a unique answer @@ -178,4 +274,22 @@ public static void main(String[] args) { (int) pts.get(jj).getY()); } } + + private static void test2() { + double[][] costMatrix = { + {0, 2, 1, 2}, + {2, 0, 2, 1}, + {1, 2, 0, 2}, + {2, 1, 2, 0}, + }; + + MinimumWeightPerfectMatching mwpm = new MinimumWeightPerfectMatching(costMatrix); + double cost = mwpm.getMinWeightCost(); + if (cost != 2.0) { + System.out.println("error cost not 2"); + } + System.out.println(cost); + // System.out.println(mwpm.solve2()); + + } } diff --git a/src/main/java/com/williamfiset/algorithms/dp/MwpmInterface.java b/src/main/java/com/williamfiset/algorithms/dp/MwpmInterface.java new file mode 100644 index 000000000..af84da354 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/MwpmInterface.java @@ -0,0 +1,10 @@ +package com.williamfiset.algorithms.dp; + +// Simple interface for MinimumWeightPerfectMatching (MWPM) solutions to simplify testing. +public interface MwpmInterface { + // Gets the minimum weight matching cost + public double getMinWeightCost(); + + // Returns an optimal matching. + public int[] getMatching(); +} diff --git a/src/main/java/com/williamfiset/algorithms/dp/WeightedMaximumCardinalityMatchingIterative.java b/src/main/java/com/williamfiset/algorithms/dp/WeightedMaximumCardinalityMatchingIterative.java new file mode 100644 index 000000000..2198e1fba --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/WeightedMaximumCardinalityMatchingIterative.java @@ -0,0 +1,225 @@ +/** + * Implementation of the Minimum Weight Perfect Matching (MWPM) problem. In this problem you are + * given a distance matrix which gives the distance from each node to every other node, and you want + * to pair up all the nodes to one another minimizing the overall cost. + * + *

Tested against: UVA 10911 - Forming Quiz Teams + * + *

To Run: ./gradlew run -Palgorithm=dp.WeightedMaximumCardinalityMatchingIterative + * + *

Time Complexity: O(n^2 * 2^n) + * + * @author William Fiset + */ +package com.williamfiset.algorithms.dp; + +import java.awt.geom.*; +import java.util.*; + +// NOTE: This class does not support WMCM generally. It assumes a complete graph structure. +public class WeightedMaximumCardinalityMatchingIterative implements MwpmInterface { + + // Inputs + private final int n; + private double[][] cost; + + // Internal + private final int END_STATE; + private boolean solved; + + // Outputs + private double minWeightCost; + private int[] matching; + + // The cost matrix should be a symmetric (i.e cost[i][j] = cost[j][i]) + public WeightedMaximumCardinalityMatchingIterative(double[][] cost) { + if (cost == null) throw new IllegalArgumentException("Input cannot be null"); + n = cost.length; + if (n == 0) throw new IllegalArgumentException("Matrix size is zero"); + if (n % 2 != 0) + throw new IllegalArgumentException("Matrix has an odd size, no perfect matching exists."); + if (n > 32) + throw new IllegalArgumentException( + "Matrix too large! A matrix that size for the MWPM problem with a time complexity of" + + "O(n^2*2^n) requires way too much computation and memory for a modern home computer."); + END_STATE = (1 << n) - 1; + this.cost = cost; + } + + public double getMinWeightCost() { + solve(); + return minWeightCost; + } + + /** + * Get the minimum weight cost matching. The matching is returned as an array where the nodes at + * index 2*i and 2*i+1 form a matched pair. For example, nodes at indexes (0, 1) are a pair, (2, + * 3) are another pair, etc... + * + *

How to iterate over the pairs: + * + *

{@code
+   * WeightedMaximumCardinalityMatchingIterative mwpm = ...
+   * int[] matching = mwpm.getMatching();
+   * for (int i = 0; i < matching.length / 2; i++) {
+   *   int node1 = matching[2*i];
+   *   int node2 = matching[2*i+1];
+   *   // Do something with the matched pair (node1, node2)
+   * }
+   * }
+ */ + public int[] getMatching() { + solve(); + return matching; + } + + private void solve() { + if (solved) return; + + // The DP state is encoded as a bitmask where the i'th bit is flipped on if the i'th node is + // included in the state. Encoding the state this way allows us to compactly represent selecting + // a subset of the nodes present in the matching. Furthermore, it allows using the '&' binary + // operator to compare states to see if they overlap and the '|' operator to combine states. + // + // dp[i] contains the optimal cost of the MWPM for the nodes captured in the binary + // representation of `i`. The dp table is always half empty because all states with an odd + // number of nodes do not have a MWPM. + Double[] dp = new Double[1 << n]; + + // Memo table to save the history of the chosen states. This table is used to reconstruct the + // chosen pairs of nodes after the algorithm has executed. + int[] history = new int[1 << n]; + + // All the states consisting of pairs of nodes are the building blocks of this algorithm. + // In every iteration, we try to add a pair of nodes to previous state to construct a larger + // matching. + final int numPairs = (n * (n - 1)) / 2; + int[] pairStates = new int[numPairs]; + double[] pairCost = new double[numPairs]; + + int k = 0; + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + int state = (1 << i) | (1 << j); + dp[state] = cost[i][j]; + pairStates[k] = state; + pairCost[k] = cost[i][j]; + k++; + } + } + + for (int state = 0b11; state < (1 << n); state++) { // O(2^n) + // Skip states with an odd number of bits (nodes). It's easier (and faster) to + // check dp[state] instead of calling `Integer.bitCount` for the bit count. + if (dp[state] == null) { + continue; + } + for (int i = 0; i < numPairs; i++) { // O(n^2) + int pair = pairStates[i]; + // Ignore states which overlap + if ((state & pair) != 0) continue; + + int newState = state | pair; + double newCost = dp[state] + pairCost[i]; + if (dp[newState] == null || newCost < dp[newState]) { + dp[newState] = newCost; + // Save the fact that we went from 'state' -> 'newState'. From this we will be able to + // reconstruct which pairs of nodes were taken by looking at 'state' xor 'newState' which + // should give us the binary representation (state) of the pair used. + history[newState] = state; + } + } + } + + reconstructMatching(history); + + minWeightCost = dp[END_STATE]; + solved = true; + } + + // Populates the `matching` array with a sorted deterministic matching sorted by lowest node + // index. For example, if the perfect matching consists of the pairs (3, 4), (1, 5), (0, 2). + // The matching is sorted such that the pairs appear in the ordering: (0, 2), (1, 5), (3, 4). + // Furthermore, it is guaranteed that for any pair (a, b) that a < b. + private void reconstructMatching(int[] history) { + // A map between pairs of nodes that were matched together. + int[] map = new int[n]; + int[] leftNodes = new int[n / 2]; + + // Reconstruct the matching of pairs of nodes working backwards through computed states. + for (int i = 0, state = END_STATE; state != 0; state = history[state]) { + // Isolate the pair used by xoring the state with the state used to generate it. + int pairUsed = state ^ history[state]; + + int leftNode = getBitPosition(Integer.lowestOneBit(pairUsed)); + int rightNode = getBitPosition(Integer.highestOneBit(pairUsed)); + + leftNodes[i++] = leftNode; + map[leftNode] = rightNode; + } + + // Sort the left nodes in ascending order. + java.util.Arrays.sort(leftNodes); + + matching = new int[n]; + for (int i = 0; i < n / 2; i++) { + matching[2 * i] = leftNodes[i]; + int rightNode = map[leftNodes[i]]; + matching[2 * i + 1] = rightNode; + } + } + + // Gets the zero base index position of the 1 bit in `k`. `k` must be a power of 2, so there is + // only ever 1 bit in the binary representation of k. + private int getBitPosition(int k) { + int count = -1; + while (k > 0) { + count++; + k >>= 1; + } + return count; + } + + /* Example */ + + public static void main(String[] args) { + test(); + } + + private static void test() { + // mwpm is expected to be between nodes: 0 & 5, 1 & 2, 3 & 4 + double[][] costMatrix = { + {0, 9, 9, 9, 9, 1}, + {9, 0, 1, 9, 9, 9}, + {9, 1, 0, 9, 9, 9}, + {9, 9, 9, 0, 1, 9}, + {9, 9, 9, 1, 0, 9}, + {1, 9, 9, 9, 9, 0}, + }; + + WeightedMaximumCardinalityMatchingIterative mwpm = + new WeightedMaximumCardinalityMatchingIterative(costMatrix); + + // Print minimum weight perfect matching cost + double cost = mwpm.getMinWeightCost(); + if (cost != 3.0) { + System.out.println("error cost not 3"); + } else { + System.out.printf("Found MWPM of: %.3f\n", cost); + } + + // Print matching + int[] matching = mwpm.getMatching(); + for (int i = 0; i < matching.length / 2; i++) { + int node1 = matching[2 * i]; + int node2 = matching[2 * i + 1]; + System.out.printf("Matched node %d with node %d\n", node1, node2); + } + + // Prints: + // Found MWPM of: 3.000 + // Matched node 0 with node 5 + // Matched node 1 with node 2 + // Matched node 3 with node 4 + } +} diff --git a/src/main/java/com/williamfiset/algorithms/dp/WeightedMaximumCardinalityMatchingRecursive.java b/src/main/java/com/williamfiset/algorithms/dp/WeightedMaximumCardinalityMatchingRecursive.java new file mode 100644 index 000000000..d3dc1a1d7 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/WeightedMaximumCardinalityMatchingRecursive.java @@ -0,0 +1,307 @@ +/** + * Implementation of the Minimum Weight Perfect Matching (MWPM) problem. In this problem you are + * given a distance matrix which gives the distance from each node to every other node, and you want + * to pair up all the nodes to one another minimizing the overall cost. + * + *

Tested against: UVA 10911 - Forming Quiz Teams + * + *

To Run: ./gradlew run -Palgorithm=dp.WeightedMaximumCardinalityMatchingRecursive + * + *

Time Complexity: O(n * 2^n) + * + * @author William Fiset + */ +package com.williamfiset.algorithms.dp; + +import java.awt.geom.*; +import java.util.*; + +public class WeightedMaximumCardinalityMatchingRecursive implements MwpmInterface { + + // A `MatchingCost` object captures the cost of a matching. Because we allow matching nodes which + // do not have edges between them we define `impossibleEdgeMatches` to track the number of times + // this happens. When comparing two MatchingCost objects, the one with the fewest number of + // impossible edges matches is ranked higher and ties broken based on cost, see the + // `isBetterMatchingCost` method below. + private static class MatchingCost { + double cost = 0; + int impossibleEdgeMatches = 0; + + public MatchingCost() {} + + public MatchingCost(double cost, int iem) { + this.cost = cost; + this.impossibleEdgeMatches = iem; + } + + public MatchingCost(MatchingCost mc) { + this.cost = mc.cost; + this.impossibleEdgeMatches = mc.impossibleEdgeMatches; + } + + public static MatchingCost createInfiniteValueMatchingCost() { + return new MatchingCost(Double.MAX_VALUE, Integer.MAX_VALUE / 2); + } + + // Updates the MatchingCost with the value of a particular edge. If the specified edge value is + // `null`, then the edge doesn't actually exist in the graph. + public void updateMatchingCost(Double edgeCost) { + if (edgeCost == null) { + impossibleEdgeMatches++; + } else { + cost += edgeCost; + } + } + + // Checks if the current matching cost is better than `mc` + public boolean isBetterMatchingCost(MatchingCost mc) { + if (impossibleEdgeMatches < mc.impossibleEdgeMatches) { + return true; + } + if (impossibleEdgeMatches == mc.impossibleEdgeMatches) { + return cost < mc.cost; + } + return false; + } + + @Override + public String toString() { + return cost + " " + impossibleEdgeMatches; + } + } + + // Inputs + private int n; + private Double[][] cost; + + // Internal + private final int FULL_STATE; + private int artificialNodeId = -1; + private boolean isOdd; + private boolean solved; + + // Outputs + private double minWeightCost; + private int[] matching; + + // The cost matrix should be a symmetric (i.e cost[i][j] = cost[j][i]) and have a cost of `null` + // between nodes i and j if no edge exists between those two nodes. + public WeightedMaximumCardinalityMatchingRecursive(Double[][] cost) { + if (cost == null) throw new IllegalArgumentException("Input cannot be null"); + n = cost.length; + if (n <= 1) throw new IllegalArgumentException("Invalid matrix size: " + n); + setCostMatrix(cost); + FULL_STATE = (1 << n) - 1; + } + + // Sets the cost matrix. If the number of nodes in the graph is odd, add an artificial + // node that connects to every other node with a cost of infinity. This will make it easy + // to find a perfect matching and remove in the artificial node in the end. + private void setCostMatrix(Double[][] inputMatrix) { + isOdd = (n % 2 == 0) ? false : true; + Double[][] newCostMatrix = null; + + if (isOdd) { + newCostMatrix = new Double[n + 1][n + 1]; + } else { + newCostMatrix = new Double[n][n]; + } + + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + newCostMatrix[i][j] = inputMatrix[i][j]; + } + } + + if (isOdd) { + for (int i = 0; i < n; i++) { + newCostMatrix[n][i] = null; + newCostMatrix[i][n] = null; + } + newCostMatrix[n][n] = 0.0; + artificialNodeId = n; + n++; + } + + this.cost = newCostMatrix; + } + + public double getMinWeightCost() { + solve(); + return minWeightCost; + } + + /** + * Get the minimum weight cost matching. The matching is returned as an array where the nodes at + * index 2*i and 2*i+1 form a matched pair. For example, nodes at indexes (0, 1) are a pair, (2, + * 3) are another pair, etc... + * + *

How to iterate over the pairs: + * + *

{@code
+   * WeightedMaximumCardinalityMatchingRecursive mwpm = ...
+   * int[] matching = mwpm.getMatching();
+   * for (int i = 0; i < matching.length / 2; i++) {
+   *   int node1 = matching[2*i];
+   *   int node2 = matching[2*i+1];
+   *   // Do something with the matched pair (node1, node2)
+   * }
+   * }
+ */ + public int[] getMatching() { + solve(); + return matching; + } + + private void solve() { + if (solved) return; + MatchingCost[] dp = new MatchingCost[1 << n]; + int[] history = new int[1 << n]; + + MatchingCost matchingCost = f(FULL_STATE, dp, history); + minWeightCost = matchingCost.cost; + + reconstructMatching(history); + solved = true; + } + + private MatchingCost f(int state, MatchingCost[] dp, int[] history) { + if (dp[state] != null) { + return dp[state]; + } + if (state == 0) { + return new MatchingCost(); + } + int p1, p2; + // Seek to find active bit position (p1) + for (p1 = 0; p1 < n; p1++) { + if ((state & (1 << p1)) > 0) { + break; + } + } + + int bestState = -1; + MatchingCost bestMatchingCost = MatchingCost.createInfiniteValueMatchingCost(); + + for (p2 = p1 + 1; p2 < n; p2++) { + // Position `p2` is on. Try matching the pair (p1, p2) together. + if ((state & (1 << p2)) > 0) { + int reducedState = state ^ (1 << p1) ^ (1 << p2); + + MatchingCost matchCost = new MatchingCost(f(reducedState, dp, history)); + matchCost.updateMatchingCost(cost[p1][p2]); + + if (matchCost.isBetterMatchingCost(bestMatchingCost)) { + bestMatchingCost = matchCost; + bestState = reducedState; + } + } + } + + history[state] = bestState; + return dp[state] = bestMatchingCost; + } + + // Populates the `matching` array with a sorted deterministic matching sorted by lowest node + // index. For example, if the perfect matching consists of the pairs (3, 4), (1, 5), (0, 2). + // The matching is sorted such that the pairs appear in the ordering: (0, 2), (1, 5), (3, 4). + // Furthermore, it is guaranteed that for any pair (a, b) that a < b. + private void reconstructMatching(int[] history) { + // A map between pairs of nodes that were matched together. + int[] map = new int[n]; + int[] leftNodes = new int[n / 2]; + + int matchingSize = 0; + + // Reconstruct the matching of pairs of nodes working backwards through computed states. + for (int i = 0, state = FULL_STATE; state != 0; state = history[state]) { + // Isolate the pair used by xoring the state with the state used to generate it. + int pairUsed = state ^ history[state]; + + int leftNode = getBitPosition(Integer.lowestOneBit(pairUsed)); + int rightNode = getBitPosition(Integer.highestOneBit(pairUsed)); + + leftNodes[i++] = leftNode; + map[leftNode] = rightNode; + + if (cost[leftNode][rightNode] != null) { + matchingSize++; + } + } + + // Sort the left nodes in ascending order. + java.util.Arrays.sort(leftNodes); + + matchingSize = matchingSize * 2; + matching = new int[matchingSize]; + + for (int i = 0, j = 0; i < n / 2; i++) { + int leftNode = leftNodes[i]; + int rightNode = map[leftNodes[i]]; + // Ignore the artificial node when there is an odd number of nodes. + if (isOdd && (leftNode == artificialNodeId || rightNode == artificialNodeId)) { + continue; + } + // Only match edges which actually exist + if (cost[leftNode][rightNode] != null) { + matching[2 * j] = leftNode; + matching[2 * j + 1] = rightNode; + j++; + } + } + } + + // Gets the zero base index position of the 1 bit in `k`. `k` must be a power of 2, so there is + // only ever 1 bit in the binary representation of k. + private int getBitPosition(int k) { + int count = -1; + while (k > 0) { + count++; + k >>= 1; + } + return count; + } + + /* Example */ + + public static void main(String[] args) { + test(); + } + + private static void test() { + // mwpm is expected to be between nodes: 0 & 5, 1 & 2, 3 & 4 + Double[][] costMatrix = { + {0.0, 9.0, 9.0, 9.0, 9.0, 1.0}, + {9.0, 0.0, 1.0, 9.0, 9.0, 9.0}, + {9.0, 1.0, 0.0, 9.0, 9.0, 9.0}, + {9.0, 9.0, 9.0, 0.0, 1.0, 9.0}, + {9.0, 9.0, 9.0, 1.0, 0.0, 9.0}, + {1.0, 9.0, 9.0, 9.0, 9.0, 0.0}, + }; + + WeightedMaximumCardinalityMatchingRecursive mwpm = + new WeightedMaximumCardinalityMatchingRecursive(costMatrix); + + // Print minimum weight perfect matching cost + double cost = mwpm.getMinWeightCost(); + if (cost != 3.0) { + System.out.println("error cost not 3"); + } else { + System.out.printf("Found MWPM of: %.3f\n", cost); + } + + // Print matching + int[] matching = mwpm.getMatching(); + for (int i = 0; i < matching.length / 2; i++) { + int node1 = matching[2 * i]; + int node2 = matching[2 * i + 1]; + System.out.printf("Matched node %d with node %d\n", node1, node2); + } + + // Prints: + // Found MWPM of: 3.000 + // Matched node 0 with node 5 + // Matched node 1 with node 2 + // Matched node 3 with node 4 + } +} diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/boardtilings/BoardTilingsSolver.java b/src/main/java/com/williamfiset/algorithms/dp/examples/boardtilings/BoardTilingsSolver.java new file mode 100644 index 000000000..bda49423c --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/boardtilings/BoardTilingsSolver.java @@ -0,0 +1,88 @@ +package com.williamfiset.algorithms.dp.examples.boardtilings; + +import java.util.*; + +public class BoardTilingsSolver { + + private int[] tiles; + + private Map tileFrequency = new HashMap<>(); + + public BoardTilingsSolver(int[] tiles) { + this.tiles = tiles; + init(); + } + + private void init() { + // Calculate the tile frequencies + for (int tile : tiles) { + tileFrequency.put(tile, tileFrequency.getOrDefault(tile, 0) + 1); + } + } + + public long iterativeSolution(int n) { + long[] dp = new long[n + 1]; + dp[0] = 1; + for (int i = 1; i <= n; i++) { + for (int tile : tileFrequency.keySet()) { + if (i - tile < 0) continue; + dp[i] += dp[i - tile] * tileFrequency.get(tile); + } + } + return dp[n]; + } + + // Works well when there is a low number of (large) tiles and the recursion + // depth isn't too deep, otherwise you may encounter a stack overflow. + public long recursiveSolution(int n) { + // Use a Map instead of a List in the hope that the recursion is sparse + // and that we can save memory by avoiding a large allocation. + Map dp = new HashMap<>(); + return f(n, dp); + } + + private long f(int n, Map dp) { + // Base case + if (n == 0) { + return 1; + } + // Check cache for an answer + Long count = dp.get(n); + if (count != null) { + return count; + } + count = 0L; + for (int tile : tileFrequency.keySet()) { + if (n - tile < 0) continue; + count += f(n - tile, dp) * tileFrequency.get(tile); + } + // Cache (memorize) the solution + dp.put(n, count); + return count; + } + + public static void main(String[] args) { + int n = 25; + int[] tiles = {1, 1, 2, 4}; + BoardTilingsSolver solver = new BoardTilingsSolver(tiles); + System.out.printf("f(%d) = %d\n", n, solver.iterativeSolution(n)); + System.out.printf("f(%d) = %d\n", n, solver.iterativeSolution(n)); + System.out.println(); + for (int i = 0; i < 10; i++) { + System.out.printf("f(%d) = %d (iterative soln)\n", i, solver.iterativeSolution(i)); + System.out.printf("f(%d) = %d (recursive soln)\n", i, solver.recursiveSolution(i)); + } + + // long startTime = System.currentTimeMillis(); + // int n = 60000000; + // int[] tiles = {100000, 10000000, 20000000, 30000000}; + // System.out.println(f(n, tiles)); + // long endTime = System.currentTimeMillis(); + // System.out.println("f(n) total execution time: " + (endTime - startTime)); + + // startTime = System.currentTimeMillis(); + // System.out.println(f2(n, tiles)); + // endTime = System.currentTimeMillis(); + // System.out.println("f2(n) total execution time: " + (endTime - startTime)); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/domino-and-tromino-tiling/Solution.java b/src/main/java/com/williamfiset/algorithms/dp/examples/domino-and-tromino-tiling/Solution.java new file mode 100644 index 000000000..ec94159c1 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/domino-and-tromino-tiling/Solution.java @@ -0,0 +1,95 @@ +class Solution { + static int n; + static Long[][] dp; + static long MOD = 1_000_000_007; + + public int numTilings(int N) { + n = N; + dp = new Long[n + 1][1 << 2]; + long ans = f(0, true, true); + return (int) ans; + } + + // t1 - whether tile 1 is available + // t2 - whether tile 2 is available + static long f(int i, boolean t1, boolean t2) { + // Finished fully tiling the board. + if (i == n) { + return 1; + } + int state = makeState(t1, t2); + if (dp[i][state] != null) { + return dp[i][state]; + } + + // Zones that define which regions are free. For the surrounding 4 tiles: + // t1 t3 + // t2 t4 + boolean t3 = i + 1 < n; + boolean t4 = i + 1 < n; + + long count = 0; + + // Placing: + // XX + // X + if (t1 && t2 && t3) count += f(i + 1, false, true); + + // Placing: + // X + // XX + if (t1 && t2 && t4) count += f(i + 1, true, false); + + // Placing: + // XX + // #X + if (t1 && !t2 && t3 && t4) count += f(i + 1, false, false); + + // Placing: + // #X + // XX + if (!t1 && t2 && t3 && t4) count += f(i + 1, false, false); + + // Placing + // X + // X + if (t1 && t2) count += f(i + 1, true, true); + + // Placing two horizontals. We don't place 2 verticals because + // that's accounted for with the single vertical tile: + // XX + // XX + if (t1 && t2 && t3 && t4) count += f(i + 1, false, false); + + // Placing: + // XX + // # + if (t1 && !t2 && t3) count += f(i + 1, false, true); + + // Placing: + // # + // XX + if (!t1 && t2 && t4) count += f(i + 1, true, false); + + // Current column is already fully tiled, so move to next column + // # + // # + if (!t1 && !t2) count += f(i + 1, true, true); + + return dp[i][state] = count % MOD; + } + + static int makeState(boolean row1, boolean row2) { + int state = 0; + if (row1) state |= 0b01; + if (row2) state |= 0b10; + return state; + } + + public static void main(String[] args) { + Solution s = new Solution(); + for (int n = 1; n <= 10; n++) { + System.out.printf("n = %d, ans = %d\n", n, s.numTilings(n)); + } + } +} diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/editdistance/EditDistance.java b/src/main/java/com/williamfiset/algorithms/dp/examples/editdistance/EditDistance.java new file mode 100644 index 000000000..c018f9bb3 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/editdistance/EditDistance.java @@ -0,0 +1,127 @@ +package com.williamfiset.algorithms.dp.examples.editdistance; + +import java.io.*; +import java.util.*; + +public class EditDistance { + + final char[] a, b; + final int insertionCost, deletionCost, substitutionCost; + + public EditDistance( + String a, String b, int insertionCost, int deletionCost, int substitutionCost) { + if (a == null || b == null) { + throw new IllegalArgumentException("Input string must not be null"); + } + this.a = a.toCharArray(); + this.b = b.toCharArray(); + this.insertionCost = insertionCost; + this.deletionCost = deletionCost; + this.substitutionCost = substitutionCost; + } + + public static int min(int... values) { + int m = Integer.MAX_VALUE; + for (int v : values) { + if (v < m) { + m = v; + } + } + return m; + } + + // TODO(william): Define whether we're transforming a into b, or b into b. That should matter, + // right?? + // Count the edit distance to transform `a` into `b` + public int editDistance() { + Integer[][] dp = new Integer[a.length + 1][b.length + 1]; + return f(dp, 0, 0); + } + + private int f(Integer[][] dp, int i, int j) { + if (i == a.length && j == b.length) { + return 0; + } + if (i == a.length) { + return (b.length - j) * insertionCost; + } + if (j == b.length) { + return (a.length - i) * deletionCost; + } + if (dp[i][j] != null) { + return dp[i][j]; + } + int substitute = f(dp, i + 1, j + 1) + (a[i] == b[j] ? 0 : substitutionCost); + int delete = f(dp, i + 1, j) + deletionCost; + int insert = f(dp, i, j + 1) + insertionCost; + return dp[i][j] = min(substitute, delete, insert); + } + + // Computes the cost to convert a string 'a' into a string 'b' using dynamic + // programming given the insertionCost, deletionCost and substitutionCost, O(nm) + public static int micahEditDistance( + String a, String b, int insertionCost, int deletionCost, int substitutionCost) { + + final int AL = a.length(), BL = b.length(); + int[][] arr = new int[AL + 1][BL + 1]; + + for (int i = 0; i <= AL; i++) { + for (int j = (i == 0 ? 1 : 0); j <= BL; j++) { + + int min = Integer.MAX_VALUE; + + // Substitution + if (i > 0 && j > 0) + min = arr[i - 1][j - 1] + (a.charAt(i - 1) == b.charAt(j - 1) ? 0 : substitutionCost); + + // Deletion + if (i > 0) min = Math.min(min, arr[i - 1][j] + deletionCost); + + // Insertion + if (j > 0) min = Math.min(min, arr[i][j - 1] + insertionCost); + + arr[i][j] = min; + } + } + + return arr[AL][BL]; + } + + public static void main(String[] args) { + String a = "923456789"; + String b = "12345"; + EditDistance solver = new EditDistance(a, b, 100, 4, 2); + System.out.println(solver.editDistance()); + System.out.println(micahEditDistance(a, b, 100, 4, 2)); + + a = "12345"; + b = "923456789"; + solver = new EditDistance(a, b, 100, 4, 2); + System.out.println(solver.editDistance()); + System.out.println(micahEditDistance(a, b, 100, 4, 2)); + + a = "aaa"; + b = "aaabbb"; + solver = new EditDistance(a, b, 10, 2, 3); + System.out.println(solver.editDistance()); + System.out.println(micahEditDistance(a, b, 10, 2, 3)); + + a = "1023"; + b = "10101010"; + solver = new EditDistance(a, b, 5, 7, 2); + System.out.println(solver.editDistance()); + System.out.println(micahEditDistance(a, b, 5, 7, 2)); + + a = "923456789"; + b = "12345"; + EditDistance solver2 = new EditDistance(a, b, 100, 4, 2); + System.out.println(solver2.editDistance()); + System.out.println(micahEditDistance(a, b, 100, 4, 2)); + + a = "aaaaa"; + b = "aabaa"; + solver = new EditDistance(a, b, 2, 3, 10); + System.out.println(solver.editDistance()); + System.out.println(micahEditDistance(a, b, 2, 3, 10)); + } +} diff --git a/com/williamfiset/algorithms/dp/examples/HouseRobber.java b/src/main/java/com/williamfiset/algorithms/dp/examples/houserobber/HouseRobber.java similarity index 78% rename from com/williamfiset/algorithms/dp/examples/HouseRobber.java rename to src/main/java/com/williamfiset/algorithms/dp/examples/houserobber/HouseRobber.java index 7e7feba25..b2258ce45 100644 --- a/com/williamfiset/algorithms/dp/examples/HouseRobber.java +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/houserobber/HouseRobber.java @@ -1,17 +1,20 @@ /** * Problem: https://leetcode.com/problems/house-robber * - *

Time Complexity: O(n) Space Complexity: O(n) + *

Time Complexity: O(n), space Complexity: O(n) * *

Download the code: $ git clone https://github.com/williamfiset/Algorithms * - *

Change directory to the root of the Algorithms directory: $ cd Algorithms + *

Change directory to the root of the Algorithms directory: * - *

Build: $ javac com/williamfiset/algorithms/dp/examples/HouseRobber.java + *

$ cd Algorithms * - *

Run: $ java com/williamfiset/algorithms/dp/examples/HouseRobber + *

Build: $ javac -d src/main/java + * src/main/java/com/williamfiset/algorithms/dp/examples/HouseRobber.java + * + *

Run: $ java -cp src/main/java com/williamfiset/algorithms/dp/examples/HouseRobber */ -package com.williamfiset.algorithms.dp.examples; +package com.williamfiset.algorithms.dp.examples.houserobber; import java.util.*; diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/01.ans b/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/01.ans new file mode 100644 index 000000000..00f9be8de --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/01.ans @@ -0,0 +1,5 @@ +5 +10 +20 +40 +80 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/01.in b/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/01.in new file mode 100644 index 000000000..bbd2ff4a9 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/01.in @@ -0,0 +1,11 @@ +1 5 5 +1 +1 +1 +1 +1 +0 +1 +2 +3 +4 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/02.ans b/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/02.ans new file mode 100644 index 000000000..00334bec7 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/02.ans @@ -0,0 +1,3 @@ +5 +7 +14 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/02.in b/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/02.in new file mode 100644 index 000000000..e4580a3e6 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/02.in @@ -0,0 +1,9 @@ +2 5 3 +1 +2 +1 +2 +1 +0 +1 +2 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/03.in b/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/03.in new file mode 100644 index 000000000..0a128c3cd --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/03.in @@ -0,0 +1,10 @@ +8 4 5 +1 +3 +2 +1 +0 +1 +2 +3 +4 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/MagicalCows.java b/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/MagicalCows.java new file mode 100644 index 000000000..4805c4813 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/magicalcows/MagicalCows.java @@ -0,0 +1,79 @@ +package com.williamfiset.algorithms.dp.examples.magicalcows; + +/** + * Solution to Magical Cows (https://open.kattis.com/problems/magicalcows) + * + *

Problem author: Graeme Zinck + * + *

Solution by: William Fiset + * + *

The main thing to realize with magical cows is that the total number of cows allowed on each + * farm is bounded by C which is less than or equal to 1000, so you can keep track of all cows in a + * frequency table for each farm size. + * + *

NOTE: You can ignore taking the floor/ceiling of the number of cows on a split since when you + * double the number of cows you always get an even number. + */ +import java.io.*; +import java.util.*; + +public class MagicalCows { + + static BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + + // The maximum number of days. + static final int MAX_DAYS = 50; + + public static void main(String[] args) throws IOException { + String[] line = br.readLine().split(" "); + + // The maximum number of cows on a farm + final int C = Integer.parseInt(line[0]); + + // The initial number of farms + final int N = Integer.parseInt(line[1]); + + // The number of queries + final int M = Integer.parseInt(line[2]); + + // The dp table. + long[][] dp = new long[MAX_DAYS + 1][C + 1]; + + // Count the initial frequency of farms of different sizes + for (int i = 0; i < N; i++) { + int cows = Integer.parseInt(br.readLine()); + dp[0][cows]++; + } + + for (int day = 0; day < MAX_DAYS; day++) { + // For all farm sizes between 1 and `C`, double the number of cows. + for (int i = 1; i <= C; i++) { + if (i <= C / 2) { + // Cow count on farm with size `i` doubled, but the number of farms did not. + dp[day + 1][i * 2] += dp[day][i]; + } else { + // The number of cows per farm on the farm with size `i` exceeds the + // permitted limit, so double the number of farms. + dp[day + 1][i] += 2 * dp[day][i]; + } + } + } + + // Answer each query + for (int i = 0; i < M; i++) { + int day = Integer.parseInt(br.readLine()); + System.out.println(query(dp, day)); + } + } + + // Find the number of farms on a particular day by summing all farms + // of every frequency for that day. + private static long query(long[][] dp, int day) { + long farms = 0; + long[] frequencies = dp[day]; + for (int i = 0; i < frequencies.length; i++) { + farms += frequencies[i]; + } + return farms; + } +} diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/NarrowArtGalleryRecursive.java b/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/NarrowArtGalleryRecursive.java new file mode 100644 index 000000000..17af39dfb --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/NarrowArtGalleryRecursive.java @@ -0,0 +1,206 @@ +package com.williamfiset.algorithms.dp.examples.narrowartgallery; + +/** + * Solution to the Narrow Art Gallery problem from the 2014 ICPC North America Qualifier + * + *

Problem: https://open.kattis.com/problems/narrowartgallery + * + *

Problem Author: Robert Hochberg + * + *

Solution by: William Fiset + */ +import java.util.Scanner; + +public class NarrowArtGalleryRecursive { + + // A large enough infinity value for this problem + static final int INF = 1000000; + + static int[][] gallery; + static Integer[][][] dp; + + static final int LEFT = 0; + static final int RIGHT = 1; + + static int min(int... values) { + int m = Integer.MAX_VALUE; + for (int v : values) if (v < m) m = v; + return m; + } + + public static int f(int k, int r) { + // Compute the optimal value of ending at the top LEFT and the top RIGHT of + // the gallery and return the minimum. + return min(f(k, r, LEFT), f(k, r, RIGHT)); + } + + // f(k,r,c) Computes the minimum value you can save by closing off `k` rooms + // in a gallery with `r` levels starting on side `c`. + // + // k = The number of rooms the curator needs to close + // r = The gallery row index + // c = The column, either LEFT (= 0) or RIGHT (= 1) + public static int f(int k, int r, int c) { + // We finished closing all K rooms + if (k == 0) { + return 0; + } + if (r < 0) { + return INF; + } + // Return the value of this subproblem, if it's already been computed. + if (dp[k][r][c] != null) { + return dp[k][r][c]; + } + // Get the value of the current room at row `r` and column `c`. + int roomValue = gallery[r][c]; + return dp[k][r][c] = + min( + // Close the current room, and take the best value from the partial + // state directly below the current room. + f(k - 1, r - 1, c) + roomValue, + // Don't include the current room. Instead, take the last best value from + // the previously calculated partial state which includes `k` rooms closed. + f(k, r - 1)); + } + + static void mainProgram() { + Scanner sc = new Scanner(System.in); + while (true) { + int N = sc.nextInt(); + int K = sc.nextInt(); + + if (N == 0 && K == 0) break; + + gallery = new int[N][2]; + dp = new Integer[K + 1][N][2]; + + int sum = 0; + for (int i = 0; i < N; i++) { + // Input the gallery values in reverse to simulate walking from the + // bottom to the top of the gallery. This makes debugging easier and + // shouldn't affect the final result. + int index = N - i - 1; + gallery[index][LEFT] = sc.nextInt(); + gallery[index][RIGHT] = sc.nextInt(); + sum += gallery[index][LEFT] + gallery[index][RIGHT]; + } + + System.out.printf("%d\n", sum - f(K, N - 1)); + } + } + + public static void main(String[] Fiset) { + mainProgram(); + // test2(); + } + + static void test2() { + int N = 5; + int K = 4; + dp = new Integer[K + 1][N][2]; + + // 3, 2 + // 5, 9 + // 0, 1 + // 4, 3 + // 8, 10 + // + // Gallery has been flipped like it would when inputted: + gallery = + new int[][] { + {8, 10}, + {4, 3}, + {0, 1}, + {5, 9}, + {3, 2}, + }; + System.out.println(f(4, 3, LEFT)); + System.out.println(f(4, 3, RIGHT)); + System.out.println(f(3, 3, LEFT)); + System.out.println(f(3, 3, RIGHT)); + } + + static void test1() { + int N = 6; + int K = 4; + dp = new Integer[K + 1][N][2]; + + // 3, 1 + // 2, 1 + // 1, 2 + // 1, 3 + // 3, 3 + // 1, 0 + // + // Gallery has been flipped like it would when inputted: + gallery = + new int[][] { + {1, 0}, + {3, 3}, + {1, 3}, + {1, 2}, + {2, 1}, + {3, 1}, + }; + f(N - 1, K); + + ok(f(0, 4, LEFT), INF); + ok(f(0, 4, RIGHT), INF); + ok(f(1, 4, LEFT), INF); + ok(f(1, 4, RIGHT), INF); + ok(f(2, 4, LEFT), INF); + ok(f(2, 4, RIGHT), INF); + ok(f(3, 4, LEFT), 6); + ok(f(3, 4, RIGHT), 8); + ok(f(4, 4, LEFT), 4); + ok(f(4, 4, RIGHT), 6); + ok(f(5, 4, LEFT), 4); + ok(f(5, 4, RIGHT), 3); + + ok(f(0, 3, LEFT), INF); + ok(f(0, 3, RIGHT), INF); + ok(f(1, 3, LEFT), INF); + ok(f(1, 3, RIGHT), INF); + ok(f(2, 3, LEFT), 5); + ok(f(2, 3, RIGHT), 6); + ok(f(3, 3, LEFT), 2); + ok(f(3, 3, RIGHT), 5); + ok(f(4, 3, LEFT), 2); + ok(f(4, 3, RIGHT), 2); + ok(f(5, 3, LEFT), 2); + ok(f(5, 3, RIGHT), 2); + + ok(f(0, 2, LEFT), INF); + ok(f(0, 2, RIGHT), INF); + ok(f(1, 2, LEFT), 4); + ok(f(1, 2, RIGHT), 3); + ok(f(2, 2, LEFT), 1); + ok(f(2, 2, RIGHT), 3); + ok(f(3, 2, LEFT), 1); + ok(f(3, 2, RIGHT), 1); + ok(f(4, 2, LEFT), 1); + ok(f(4, 2, RIGHT), 1); + ok(f(5, 2, LEFT), 1); + ok(f(5, 2, RIGHT), 1); + + ok(f(0, 1, LEFT), 1); + ok(f(0, 1, RIGHT), 0); + ok(f(1, 1, LEFT), 0); + ok(f(1, 1, RIGHT), 0); + ok(f(2, 1, LEFT), 0); + ok(f(2, 1, RIGHT), 0); + ok(f(3, 1, LEFT), 0); + ok(f(3, 1, RIGHT), 0); + ok(f(4, 1, LEFT), 0); + ok(f(4, 1, RIGHT), 0); + ok(f(5, 1, LEFT), 0); + ok(f(5, 1, RIGHT), 0); + } + + static void ok(int a, int b) { + if (a != b) { + System.out.println("Error: " + a + " != " + b); + } + } +} diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/gallery1.txt b/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/gallery1.txt new file mode 100644 index 000000000..7c953edae --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/gallery1.txt @@ -0,0 +1,7 @@ +5 2 +3 2 +5 9 +0 1 +4 3 +8 10 +0 0 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/gallery2.txt b/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/gallery2.txt new file mode 100644 index 000000000..b6e385554 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/gallery2.txt @@ -0,0 +1,7 @@ +5 3 +3 2 +5 9 +1 2 +4 3 +8 10 +0 0 diff --git a/com/williamfiset/algorithms/dp/examples/sample1.ans b/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/sample1.ans similarity index 100% rename from com/williamfiset/algorithms/dp/examples/sample1.ans rename to src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/sample1.ans diff --git a/com/williamfiset/algorithms/dp/examples/sample1.in b/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/sample1.in similarity index 100% rename from com/williamfiset/algorithms/dp/examples/sample1.in rename to src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/sample1.in diff --git a/com/williamfiset/algorithms/dp/examples/sample2.ans b/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/sample2.ans similarity index 100% rename from com/williamfiset/algorithms/dp/examples/sample2.ans rename to src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/sample2.ans diff --git a/com/williamfiset/algorithms/dp/examples/sample2.in b/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/sample2.in similarity index 100% rename from com/williamfiset/algorithms/dp/examples/sample2.in rename to src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/sample2.in diff --git a/com/williamfiset/algorithms/dp/examples/sample3.ans b/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/sample3.ans similarity index 100% rename from com/williamfiset/algorithms/dp/examples/sample3.ans rename to src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/sample3.ans diff --git a/com/williamfiset/algorithms/dp/examples/sample3.in b/src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/sample3.in similarity index 100% rename from com/williamfiset/algorithms/dp/examples/sample3.in rename to src/main/java/com/williamfiset/algorithms/dp/examples/narrowartgallery/sample3.in diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/100_10_10.in b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/100_10_10.in new file mode 100644 index 000000000..69e27d9fc --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/100_10_10.in @@ -0,0 +1 @@ +100 10 10 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/4_3_2.in b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/4_3_2.in new file mode 100644 index 000000000..8745e6f89 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/4_3_2.in @@ -0,0 +1 @@ +4 3 2 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/5_3_2.in b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/5_3_2.in new file mode 100644 index 000000000..ad0d61ad0 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/5_3_2.in @@ -0,0 +1 @@ +5 3 2 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/5_3_3.in b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/5_3_3.in new file mode 100644 index 000000000..87422f187 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/5_3_3.in @@ -0,0 +1 @@ +5 3 3 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/Scenes.java b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/Scenes.java new file mode 100644 index 000000000..1f2faf413 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/Scenes.java @@ -0,0 +1,63 @@ +package com.williamfiset.algorithms.dp.examples.scenes; + +/** + * Solution to the Mountain Scenes problem (https://open.kattis.com/problems/scenes) + * + *

Solution by: William Fiset + */ +import java.io.*; +import java.util.*; + +public class Scenes { + + static Long[][] dp; + static int N, W, H; + static long MOD = 1_000_000_007; + + static BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + + public static void main(String[] args) throws IOException { + String[] ln = br.readLine().split(" "); + N = Integer.parseInt(ln[0]); // Total ribbon length + W = Integer.parseInt(ln[1]); // Width + H = Integer.parseInt(ln[2]); // Height + + solution1(); + } + + // Recursive solution implementation + static void solution1() { + // Count the number of plain mountains. Be sure to account for having more + // ribbon than can possible be placed. + int ribbonSquares = Math.min(W * H, N); + int plains = (ribbonSquares / W) + 1; + + dp = new Long[W + 1][N + 1]; + long ans = ((f(1, N) - plains) + MOD) % MOD; + System.out.println(ans); + } + + static long f(int w, int ribbon) { + // We're trying to create a mountain scene for which we don't have enough ribbon material for. + if (ribbon < 0) { + return 0; + } + // When the width value exceeds the frame width, we know we’ve finished creating a unique + // mountain scene! + if (w > W) { + return 1; + } + if (dp[w][ribbon] != null) { + return dp[w][ribbon]; + } + long scenes = 0L; + // Try placing all possible ribbon lengths at this column/width + for (int len = 0; len <= H; len++) { + scenes = (scenes + f(w + 1, ribbon - len)); + } + // Store and return the number of scenes for this sub-problem. + // Applying the MOD after the loop is safe since H is at most 100 and + // the MOD is 10^9+7 which cannot overflow a signed 64bit integer. + return dp[w][ribbon] = scenes % MOD; + } +} diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/big.in b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/big.in new file mode 100644 index 000000000..17a5b1a5f --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/big.in @@ -0,0 +1 @@ +10000 100 100 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/in1 b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/in1 new file mode 100644 index 000000000..43cebf1df --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/in1 @@ -0,0 +1 @@ +1 2 2 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/in2 b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/in2 new file mode 100644 index 000000000..7f7a4f7e8 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/in2 @@ -0,0 +1 @@ +1 5 5 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0000.ans b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0000.ans new file mode 100644 index 000000000..4faba4413 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0000.ans @@ -0,0 +1 @@ +7770 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0000.in b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0000.in new file mode 100644 index 000000000..69535f8b9 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0000.in @@ -0,0 +1 @@ +25 5 5 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0001.ans b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0001.ans new file mode 100644 index 000000000..550c8b9aa --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0001.ans @@ -0,0 +1 @@ +6050 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0001.in b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0001.in new file mode 100644 index 000000000..dad80c3ab --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0001.in @@ -0,0 +1 @@ +15 5 5 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0002.ans b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0002.ans new file mode 100644 index 000000000..c7781419a --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0002.ans @@ -0,0 +1 @@ +1022 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0002.in b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0002.in new file mode 100644 index 000000000..eb0c46304 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0002.in @@ -0,0 +1 @@ +10 10 1 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0003.ans b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0003.ans new file mode 100644 index 000000000..1e8b31496 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0003.ans @@ -0,0 +1 @@ +6 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0003.in b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0003.in new file mode 100644 index 000000000..b8c77475f --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes-0003.in @@ -0,0 +1 @@ +4 2 2 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes.zip b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes.zip new file mode 100644 index 000000000..a43b850ab Binary files /dev/null and b/src/main/java/com/williamfiset/algorithms/dp/examples/scenes/scenes.zip differ diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/tilingdominoes/TilingDominoes.java b/src/main/java/com/williamfiset/algorithms/dp/examples/tilingdominoes/TilingDominoes.java new file mode 100644 index 000000000..491bee341 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/tilingdominoes/TilingDominoes.java @@ -0,0 +1,86 @@ +package com.williamfiset.algorithms.dp.examples.tilingdominoes; + +/** + * Solution to Tri Tiling (https://open.kattis.com/problems/tritiling) + * + *

Explanation video: https://www.youtube.com/watch?v=yn2jnmlepY8 + * + *

Solution by: William Fiset + */ +import java.util.*; + +public class TilingDominoes { + static Scanner sc = new Scanner(System.in); + + public static void main(String[] args) { + while (true) { + int n = sc.nextInt(); + if (n == -1) break; + // Solution1: + System.out.println(solution1(n)); + + // Alternative solution: + // System.out.println(solution2(n)); + } + } + + // Tile states 0...7 representations: + // + // 0: 0 1: 1 2: 0 3: 1 + // 0 0 1 1 + // 0 0 0 0 + // + // 4: 0 5: 1 6: 0 7: 1 + // 0 0 1 1 + // 1 1 1 1 + private static int solution1(int n) { + int[][] dp = new int[n + 1][1 << 3]; + dp[0][7] = 1; + for (int i = 1; i < n + 1; i++) { + // The number of empty states for this column is the number + // of full states in the previous column. + dp[i][0] += dp[i - 1][7]; + + dp[i][1] += dp[i - 1][6]; + + // State 2 doesn't contribute to the number of tilings + // dp[i][2] += dp[i-1][5]; + + dp[i][3] += dp[i - 1][7]; + dp[i][3] += dp[i - 1][4]; + + dp[i][4] += dp[i - 1][3]; + + // State 5 doesn't contribute to the number of tilings + // dp[i][5] += dp[i-1][2]; + + dp[i][6] += dp[i - 1][7]; + dp[i][6] += dp[i - 1][1]; + + dp[i][7] += dp[i - 1][3]; + dp[i][7] += dp[i - 1][6]; + dp[i][7] += dp[i - 1][0]; + } + // printMatrix(dp); + return dp[n][7]; + } + + private static void printMatrix(int[][] dp) { + for (int i = 0; i < dp.length; i++) { + for (int j = 0; j < 1 << 3; j++) { + System.out.printf("% 5d,", dp[i][j]); + } + System.out.println(); + } + } + + private static int solution2(int n) { + int[] dp = new int[n + 4]; + dp[0] = 1; + dp[2] = 3; + for (int i = 4; i < n + 4; i += 2) { + dp[i] = 4 * dp[i - 2] - dp[i - 4]; + } + return dp[n]; + } +} diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/tilingdominoes/all.in b/src/main/java/com/williamfiset/algorithms/dp/examples/tilingdominoes/all.in new file mode 100644 index 000000000..ca7697f19 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/tilingdominoes/all.in @@ -0,0 +1,32 @@ +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +-1 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/tilingdominoes/sample.ans b/src/main/java/com/williamfiset/algorithms/dp/examples/tilingdominoes/sample.ans new file mode 100644 index 000000000..2dde930e2 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/tilingdominoes/sample.ans @@ -0,0 +1,3 @@ +3 +153 +2131 diff --git a/src/main/java/com/williamfiset/algorithms/dp/examples/tilingdominoes/sample.in b/src/main/java/com/williamfiset/algorithms/dp/examples/tilingdominoes/sample.in new file mode 100644 index 000000000..70957a002 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/dp/examples/tilingdominoes/sample.in @@ -0,0 +1,4 @@ +2 +8 +12 +-1 diff --git a/com/williamfiset/algorithms/geometry/AngleBetweenVectors2D.java b/src/main/java/com/williamfiset/algorithms/geometry/AngleBetweenVectors2D.java similarity index 100% rename from com/williamfiset/algorithms/geometry/AngleBetweenVectors2D.java rename to src/main/java/com/williamfiset/algorithms/geometry/AngleBetweenVectors2D.java diff --git a/com/williamfiset/algorithms/geometry/AngleBetweenVectors3D.java b/src/main/java/com/williamfiset/algorithms/geometry/AngleBetweenVectors3D.java similarity index 100% rename from com/williamfiset/algorithms/geometry/AngleBetweenVectors3D.java rename to src/main/java/com/williamfiset/algorithms/geometry/AngleBetweenVectors3D.java diff --git a/com/williamfiset/algorithms/geometry/CircleCircleIntersectionArea.java b/src/main/java/com/williamfiset/algorithms/geometry/CircleCircleIntersectionArea.java similarity index 100% rename from com/williamfiset/algorithms/geometry/CircleCircleIntersectionArea.java rename to src/main/java/com/williamfiset/algorithms/geometry/CircleCircleIntersectionArea.java diff --git a/com/williamfiset/algorithms/geometry/CircleCircleIntersectionPoints.js b/src/main/java/com/williamfiset/algorithms/geometry/CircleCircleIntersectionPoints.js similarity index 100% rename from com/williamfiset/algorithms/geometry/CircleCircleIntersectionPoints.js rename to src/main/java/com/williamfiset/algorithms/geometry/CircleCircleIntersectionPoints.js diff --git a/com/williamfiset/algorithms/geometry/CircularSegmentArea.java b/src/main/java/com/williamfiset/algorithms/geometry/CircularSegmentArea.java similarity index 100% rename from com/williamfiset/algorithms/geometry/CircularSegmentArea.java rename to src/main/java/com/williamfiset/algorithms/geometry/CircularSegmentArea.java diff --git a/com/williamfiset/algorithms/geometry/ClosestPairOfPoints.java b/src/main/java/com/williamfiset/algorithms/geometry/ClosestPairOfPoints.java similarity index 100% rename from com/williamfiset/algorithms/geometry/ClosestPairOfPoints.java rename to src/main/java/com/williamfiset/algorithms/geometry/ClosestPairOfPoints.java diff --git a/com/williamfiset/algorithms/geometry/CollinearPoints.java b/src/main/java/com/williamfiset/algorithms/geometry/CollinearPoints.java similarity index 100% rename from com/williamfiset/algorithms/geometry/CollinearPoints.java rename to src/main/java/com/williamfiset/algorithms/geometry/CollinearPoints.java diff --git a/com/williamfiset/algorithms/geometry/ConvexHullGrahamScan.java b/src/main/java/com/williamfiset/algorithms/geometry/ConvexHullGrahamScan.java similarity index 100% rename from com/williamfiset/algorithms/geometry/ConvexHullGrahamScan.java rename to src/main/java/com/williamfiset/algorithms/geometry/ConvexHullGrahamScan.java diff --git a/com/williamfiset/algorithms/geometry/ConvexHullMonotoneChainsAlgorithm.java b/src/main/java/com/williamfiset/algorithms/geometry/ConvexHullMonotoneChainsAlgorithm.java similarity index 100% rename from com/williamfiset/algorithms/geometry/ConvexHullMonotoneChainsAlgorithm.java rename to src/main/java/com/williamfiset/algorithms/geometry/ConvexHullMonotoneChainsAlgorithm.java diff --git a/com/williamfiset/algorithms/geometry/ConvexPolygonArea.java b/src/main/java/com/williamfiset/algorithms/geometry/ConvexPolygonArea.java similarity index 100% rename from com/williamfiset/algorithms/geometry/ConvexPolygonArea.java rename to src/main/java/com/williamfiset/algorithms/geometry/ConvexPolygonArea.java diff --git a/com/williamfiset/algorithms/geometry/ConvexPolygonContainsPoint.java b/src/main/java/com/williamfiset/algorithms/geometry/ConvexPolygonContainsPoint.java similarity index 100% rename from com/williamfiset/algorithms/geometry/ConvexPolygonContainsPoint.java rename to src/main/java/com/williamfiset/algorithms/geometry/ConvexPolygonContainsPoint.java diff --git a/com/williamfiset/algorithms/geometry/ConvexPolygonCutWithLineSegment.java b/src/main/java/com/williamfiset/algorithms/geometry/ConvexPolygonCutWithLineSegment.java similarity index 61% rename from com/williamfiset/algorithms/geometry/ConvexPolygonCutWithLineSegment.java rename to src/main/java/com/williamfiset/algorithms/geometry/ConvexPolygonCutWithLineSegment.java index cc75a61ad..9460bc8da 100644 --- a/com/williamfiset/algorithms/geometry/ConvexPolygonCutWithLineSegment.java +++ b/src/main/java/com/williamfiset/algorithms/geometry/ConvexPolygonCutWithLineSegment.java @@ -1,7 +1,8 @@ /** - * This algorithm cuts a convex polygon with a line segment and returns the two resulting pieces. + * This algorithm cuts a ordered convex polygon with a line segment and returns the two resulting + * pieces. * - *

Time Complexity: O(n) + *

Time Complexity: O(nlogn) * * @author Finn Lidbetter */ @@ -28,6 +29,30 @@ public String toString() { } } + // sorts the points in CW direction. + public static List sortCW(List poly) { + + int l = poly.size(); + double centroidX = 0; + double centroidY = 0; + for (int i = 0; i < l; i++) { + centroidX += poly.get(i).x; + centroidY += poly.get(i).y; + } + centroidX = centroidX / l; + centroidY = centroidY / l; + Pt center = new Pt(centroidX, centroidY); + + Collections.sort( + poly, + (a, b) -> { + double a1 = (Math.toDegrees(Math.atan2(a.x - center.x, a.y - center.y)) + 360) % 360; + double a2 = (Math.toDegrees(Math.atan2(b.x - center.x, b.y - center.y)) + 360) % 360; + return (int) (a1 - a2); + }); + return poly; + } + // Cuts a convex polygon by a specified line and returns one part // of the polygon (swapping the endpoints p1 and p2 of the line // will return the other part of the polygon). @@ -62,13 +87,36 @@ private static int orientation(double ax, double ay, double bx, double by, doubl return cross < -EPS ? -1 : cross > EPS ? 1 : 0; } + // takes Pt[] as an argument and returns List + public static List makeList(Pt[] squarePolygon) { + List list = new ArrayList(); + for (int i = 0; i < squarePolygon.length; i++) { + list.add(squarePolygon[i]); + } + return list; + } + + // takes List as an argument and returns Pt[] + public static Pt[] makeArray(List list) { + int l = list.size(); + Pt[] temp = new Pt[l]; + for (int i = 0; i < l; i++) { + temp[i] = list.get(i); + } + return temp; + } + // Example usage public static void main(String[] args) { - Pt[] squarePolygon = {new Pt(0, 0), new Pt(0, 4), new Pt(4, 0), new Pt(4, 4)}; + Pt[] squarePolygon = {new Pt(0, 0), new Pt(0, 4), new Pt(4, 0), new Pt(4, 4), new Pt(0, 2)}; Pt p1 = new Pt(-1, -1); Pt p2 = new Pt(5, 5); + List list = makeList(squarePolygon); + list = sortCW(list); + squarePolygon = makeArray(list); + Pt[] poly1 = cut(squarePolygon, p1, p2); Pt[] poly2 = cut(squarePolygon, p2, p1); @@ -76,18 +124,18 @@ public static void main(String[] args) { for (Pt pt : poly1) System.out.println(pt); // Prints: // First polygon: + // (0.0,4.0) // (4.0,4.0) // (0.0,0.0) - // (0.0,4.0) - // (2.0,2.0) <-- Probably should not be here? + // (0.0,2.0) System.out.println("\nSecond polygon:"); for (Pt pt : poly2) System.out.println(pt); + // Prints: // Second polygon: // (4.0,4.0) - // (0.0,0.0) - // (2.0,2.0) <-- Probably should not be here? // (4.0,0.0) + // (0.0,0.0) } } diff --git a/com/williamfiset/algorithms/geometry/CoplanarPoints.java b/src/main/java/com/williamfiset/algorithms/geometry/CoplanarPoints.java similarity index 100% rename from com/williamfiset/algorithms/geometry/CoplanarPoints.java rename to src/main/java/com/williamfiset/algorithms/geometry/CoplanarPoints.java diff --git a/com/williamfiset/algorithms/geometry/Line.java b/src/main/java/com/williamfiset/algorithms/geometry/Line.java similarity index 100% rename from com/williamfiset/algorithms/geometry/Line.java rename to src/main/java/com/williamfiset/algorithms/geometry/Line.java diff --git a/com/williamfiset/algorithms/geometry/LineCircleIntersection.java b/src/main/java/com/williamfiset/algorithms/geometry/LineCircleIntersection.java similarity index 100% rename from com/williamfiset/algorithms/geometry/LineCircleIntersection.java rename to src/main/java/com/williamfiset/algorithms/geometry/LineCircleIntersection.java diff --git a/com/williamfiset/algorithms/geometry/LineCircleIntersection.js b/src/main/java/com/williamfiset/algorithms/geometry/LineCircleIntersection.js similarity index 100% rename from com/williamfiset/algorithms/geometry/LineCircleIntersection.js rename to src/main/java/com/williamfiset/algorithms/geometry/LineCircleIntersection.js diff --git a/com/williamfiset/algorithms/geometry/LineSegmentCircleIntersection.js b/src/main/java/com/williamfiset/algorithms/geometry/LineSegmentCircleIntersection.js similarity index 100% rename from com/williamfiset/algorithms/geometry/LineSegmentCircleIntersection.js rename to src/main/java/com/williamfiset/algorithms/geometry/LineSegmentCircleIntersection.js diff --git a/com/williamfiset/algorithms/geometry/LineSegmentLineSegmentIntersection.java b/src/main/java/com/williamfiset/algorithms/geometry/LineSegmentLineSegmentIntersection.java similarity index 100% rename from com/williamfiset/algorithms/geometry/LineSegmentLineSegmentIntersection.java rename to src/main/java/com/williamfiset/algorithms/geometry/LineSegmentLineSegmentIntersection.java diff --git a/com/williamfiset/algorithms/geometry/LineSegmentToGeneralForm.java b/src/main/java/com/williamfiset/algorithms/geometry/LineSegmentToGeneralForm.java similarity index 100% rename from com/williamfiset/algorithms/geometry/LineSegmentToGeneralForm.java rename to src/main/java/com/williamfiset/algorithms/geometry/LineSegmentToGeneralForm.java diff --git a/com/williamfiset/algorithms/geometry/LongitudeLatitudeGeographicDistance.java b/src/main/java/com/williamfiset/algorithms/geometry/LongitudeLatitudeGeographicDistance.java similarity index 100% rename from com/williamfiset/algorithms/geometry/LongitudeLatitudeGeographicDistance.java rename to src/main/java/com/williamfiset/algorithms/geometry/LongitudeLatitudeGeographicDistance.java diff --git a/src/main/java/com/williamfiset/algorithms/geometry/MinimumCostConvexPolygonTriangulation.java b/src/main/java/com/williamfiset/algorithms/geometry/MinimumCostConvexPolygonTriangulation.java new file mode 100644 index 000000000..19ef2ce80 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/geometry/MinimumCostConvexPolygonTriangulation.java @@ -0,0 +1,40 @@ +/** + * This file shows you how to find the minimum cost convex polygon triangulation of a set of points. + * Points must be in either clockwise or counterclockwise order. + * + *

Time Complexity: O(n^3) + * + * @author Bryan Bowles + */ +package com.williamfiset.algorithms.geometry; + +import java.awt.geom.Point2D; + +// Problem explanation: https://www.geeksforgeeks.org/minimum-cost-polygon-triangulation/ +public class MinimumCostConvexPolygonTriangulation { + + // Returns the perimeter (cost) of the triangle + private static double cost(Point2D i, Point2D j, Point2D k) { + return i.distance(j) + i.distance(k) + j.distance(k); + } + + // Input must be a convex polygon with points in CW or CCW order. + public static double minimumCostTriangulation(Point2D[] polygon) { + int len = polygon.length; + if (len < 3) return 0; + + double[][] dp = new double[len][len]; + for (int i = 2; i < len; i++) { + for (int j = 0; j + i < len; j++) { + dp[j][j + i] = Integer.MAX_VALUE; + for (int k = j + 1; k < j + i; k++) { + dp[j][j + i] = + Math.min( + dp[j][j + i], + dp[j][k] + dp[k][j + i] + cost(polygon[j], polygon[j + i], polygon[k])); + } + } + } + return dp[0][len - 1]; + } +} diff --git a/com/williamfiset/algorithms/geometry/PointCircleTangent.java b/src/main/java/com/williamfiset/algorithms/geometry/PointCircleTangent.java similarity index 100% rename from com/williamfiset/algorithms/geometry/PointCircleTangent.java rename to src/main/java/com/williamfiset/algorithms/geometry/PointCircleTangent.java diff --git a/com/williamfiset/algorithms/geometry/PointInsideTriangle.java b/src/main/java/com/williamfiset/algorithms/geometry/PointInsideTriangle.java similarity index 100% rename from com/williamfiset/algorithms/geometry/PointInsideTriangle.java rename to src/main/java/com/williamfiset/algorithms/geometry/PointInsideTriangle.java diff --git a/com/williamfiset/algorithms/geometry/PointRotation.java b/src/main/java/com/williamfiset/algorithms/geometry/PointRotation.java similarity index 100% rename from com/williamfiset/algorithms/geometry/PointRotation.java rename to src/main/java/com/williamfiset/algorithms/geometry/PointRotation.java diff --git a/com/williamfiset/algorithms/geometry/TriangleArea.java b/src/main/java/com/williamfiset/algorithms/geometry/TriangleArea.java similarity index 100% rename from com/williamfiset/algorithms/geometry/TriangleArea.java rename to src/main/java/com/williamfiset/algorithms/geometry/TriangleArea.java diff --git a/com/williamfiset/algorithms/graphtheory/AStar_GridHeuristic.java b/src/main/java/com/williamfiset/algorithms/graphtheory/AStar_GridHeuristic.java similarity index 99% rename from com/williamfiset/algorithms/graphtheory/AStar_GridHeuristic.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/AStar_GridHeuristic.java index 84fb4d41d..55ab0931c 100644 --- a/com/williamfiset/algorithms/graphtheory/AStar_GridHeuristic.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/AStar_GridHeuristic.java @@ -96,7 +96,6 @@ public static double astar( double g = node.g + edge.cost; double h = heuristic(X, Y, edge.to, end); - double f = g + h; if (g < G[edge.to] || !openSet.contains(edge.to)) { diff --git a/com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyList.java diff --git a/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyList.java similarity index 99% rename from com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyList.java index d49ddb492..9fe18b61c 100644 --- a/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyList.java @@ -23,6 +23,7 @@ public Edge(int from, int to, double cost) { } // Create a graph with V vertices + @SuppressWarnings("unchecked") public static List[] createGraph(final int V) { List[] graph = new List[V]; for (int i = 0; i < V; i++) graph[i] = new ArrayList<>(); diff --git a/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyMatrix.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyMatrix.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyMatrix.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordAdjacencyMatrix.java diff --git a/com/williamfiset/algorithms/graphtheory/BellmanFordEdgeList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordEdgeList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/BellmanFordEdgeList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/BellmanFordEdgeList.java diff --git a/com/williamfiset/algorithms/graphtheory/Boruvkas.java b/src/main/java/com/williamfiset/algorithms/graphtheory/Boruvkas.java similarity index 99% rename from com/williamfiset/algorithms/graphtheory/Boruvkas.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/Boruvkas.java index 40dbfa84e..1be3a5f7d 100644 --- a/com/williamfiset/algorithms/graphtheory/Boruvkas.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/Boruvkas.java @@ -17,6 +17,7 @@ public Edge(int u, int v, int cost) { public String toString() { return String.format("%d %d, cost: %d", u, v, cost); } + // @Override public int compareTo(Edge other) { int cmp = cost - other.cost; diff --git a/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java similarity index 98% rename from com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java index 0df50c0f6..3ca3d1898 100644 --- a/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterative.java @@ -130,7 +130,7 @@ public static void main(String[] args) { solver = new BreadthFirstSearchAdjacencyListIterative(graph); int start = 10, end = 5; - List path = solver.reconstructPath(10, 5); + List path = solver.reconstructPath(start, end); System.out.printf("The shortest path from %d to %d is: [%s]\n", start, end, formatPath(path)); // Prints: // The shortest path from 10 to 5 is: [10 -> 9 -> 0 -> 7 -> 6 -> 5] diff --git a/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeFastQueue.java diff --git a/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchRecursive.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchRecursive.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/BreadthFirstSearchRecursive.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchRecursive.java diff --git a/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/BridgesAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyList.java diff --git a/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterative.java b/src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterative.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterative.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterative.java diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/ChinesePostmanProblem.java b/src/main/java/com/williamfiset/algorithms/graphtheory/ChinesePostmanProblem.java new file mode 100644 index 000000000..341888441 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/ChinesePostmanProblem.java @@ -0,0 +1,541 @@ +/** + * This file is still a WIP + * + *

Still need to: - Implemented undirected edge eulerain path algo + * + *

./gradlew run -Palgorithm=graphtheory.ChinesePostmanProblem + */ +package com.williamfiset.algorithms.graphtheory; + +import java.util.*; + +public class ChinesePostmanProblem { + + // An edge class to represent a directed edge + // between two nodes with a certain cost. + public static class Edge { + double cost; + int from, to; + + public Edge(int from, int to, double cost) { + this.from = from; + this.to = to; + this.cost = cost; + } + + @Override + public String toString() { + return "(" + from + ", " + to + ", " + cost + ")"; + } + } + + private int n; + private int[] inDegree; + + public ChinesePostmanProblem(List> g) { + this.n = g.size(); + // TODO(william): Make a copy of this graph. + + // TODO(william): check if graph has an Eulerian path + + inDegree = new int[n]; + + for (List edges : g) { + for (Edge edge : edges) { + inDegree[edge.from]++; + } + } + + Map mapping = new HashMap<>(); + Map invMapping = new HashMap<>(); + + int id = 0; + int oddDegreeNodeCount = 0; + for (int i = 0; i < n; i++) { + if (inDegree[i] % 2 != 0) { // odd degree node + oddDegreeNodeCount++; + + System.out.printf("%d -> %d\n", i, id); + + // node `i` maps to node with `id` in the sub graph + mapping.put(i, id); + invMapping.put(id, i); + + id++; + } + } + System.out.println(mapping); + // Odd node cost matrix. + Double[][] matrix = new Double[oddDegreeNodeCount][oddDegreeNodeCount]; + + for (int i = 0; i < n; i++) { + + // Skip even degree nodes + if (inDegree[i] % 2 == 0) { + continue; + } + + int fromNodeId = mapping.get(i); + for (Edge edge : g.get(i)) { + // Skip nodes which are not odd. Only connect odd node to odd node + if (inDegree[edge.to] % 2 == 0) { + continue; + } + + // System.out.printf("edge.to = %d, inDegree[edge.to] = %d\n", edge.to, inDegree[edge.to]); + int toNodeId = mapping.get(edge.to); + // System.out.printf("%d -> %d | %d -> %d\n", edge.from, edge.to, fromNodeId, toNodeId); + matrix[fromNodeId][toNodeId] = edge.cost; + } + } + + WeightedMaximumCardinalityMatchingRecursive wmcm = + new WeightedMaximumCardinalityMatchingRecursive(matrix); + int[] matching = wmcm.getMatching(); + for (int i = 0; i < matching.length / 2; i++) { + int node1 = matching[2 * i]; + int node2 = matching[2 * i + 1]; + + if (matrix[node1][node2] != null) { // edge exists + // Add a new edge in the original graph with the same value as this edge + + // Map node1 and node2 back to the original graph nodes + int from = invMapping.get(node1); + int to = invMapping.get(node2); + + // Seek time can be made into a lookup, but these graphs are generally quite small. + Edge edge = findEdge(g, from, to); + System.out.printf("%d -> %d | %d -> %d | cost = %f\n", node1, node2, from, to, edge.cost); + + Edge e1 = new Edge(from, to, edge.cost); + Edge e2 = new Edge(to, from, edge.cost); + + // // Augment existing graph with new edges + g.get(from).add(e1); + g.get(to).add(e2); + } + } + + // Print augmented graph + for (int i = 0; i < n; i++) { + System.out.printf("%d -> [", i); + for (Edge e : g.get(i)) { + System.out.print(e + ", "); + } + System.out.print("]\n"); + } + + EulerianPathDirectedEdgesAdjacencyList eulerPathSolver = + new EulerianPathDirectedEdgesAdjacencyList(g); + List cppTour = eulerPathSolver.getEulerianPath(); + + double tourTotal = 0; + for (Edge edge : cppTour) { + System.out.printf("%d -> %d with cost: %f\n", edge.from, edge.to, edge.cost); + tourTotal += edge.cost; + } + System.out.println(tourTotal); + System.out.println(tourTotal / 2.0); + } + + private static Edge findEdge(List> g, int to, int from) { + for (Edge e : g.get(from)) { + if (e.to == to) { + return e; + } + } + return null; + } + + public List getTour() { + return null; + } + + private static class WeightedMaximumCardinalityMatchingRecursive { + + // A `MatchingCost` object captures the cost of a matching. Because we allow matching nodes + // which + // do not have edges between them we define `impossibleEdgeMatches` to track the number of times + // this happens. When comparing two MatchingCost objects, the one with the fewest number of + // impossible edges matches is ranked higher and ties broken based on cost, see the + // `isBetterMatchingCost` method below. + private static class MatchingCost { + double cost = 0; + int impossibleEdgeMatches = 0; + + public MatchingCost() {} + + public MatchingCost(double cost, int iem) { + this.cost = cost; + this.impossibleEdgeMatches = iem; + } + + public MatchingCost(MatchingCost mc) { + this.cost = mc.cost; + this.impossibleEdgeMatches = mc.impossibleEdgeMatches; + } + + public static MatchingCost createInfiniteValueMatchingCost() { + return new MatchingCost(Double.MAX_VALUE, Integer.MAX_VALUE / 2); + } + + // Updates the MatchingCost with the value of a particular edge. If the specified edge value + // is + // `null`, then the edge doesn't actually exist in the graph. + public void updateMatchingCost(Double edgeCost) { + if (edgeCost == null) { + impossibleEdgeMatches++; + } else { + cost += edgeCost; + } + } + + // Checks if the current matching cost is better than `mc` + public boolean isBetterMatchingCost(MatchingCost mc) { + if (impossibleEdgeMatches < mc.impossibleEdgeMatches) { + return true; + } + if (impossibleEdgeMatches == mc.impossibleEdgeMatches) { + return cost < mc.cost; + } + return false; + } + + @Override + public String toString() { + return cost + " " + impossibleEdgeMatches; + } + } + + // Inputs + private int n; + private Double[][] cost; + + // Internal + private final int FULL_STATE; + private int artificialNodeId = -1; + private boolean isOdd; + private boolean solved; + + // Outputs + private double minWeightCost; + private int[] matching; + + // The cost matrix should be a symmetric (i.e cost[i][j] = cost[j][i]) and have a cost of `null` + // between nodes i and j if no edge exists between those two nodes. + public WeightedMaximumCardinalityMatchingRecursive(Double[][] cost) { + if (cost == null) throw new IllegalArgumentException("Input cannot be null"); + n = cost.length; + if (n <= 1) throw new IllegalArgumentException("Invalid matrix size: " + n); + setCostMatrix(cost); + FULL_STATE = (1 << n) - 1; + } + + // Sets the cost matrix. If the number of nodes in the graph is odd, add an artificial + // node that connects to every other node with a cost of infinity. This will make it easy + // to find a perfect matching and remove in the artificial node in the end. + private void setCostMatrix(Double[][] inputMatrix) { + isOdd = (n % 2 == 0) ? false : true; + Double[][] newCostMatrix = null; + + if (isOdd) { + newCostMatrix = new Double[n + 1][n + 1]; + } else { + newCostMatrix = new Double[n][n]; + } + + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + newCostMatrix[i][j] = inputMatrix[i][j]; + } + } + + if (isOdd) { + for (int i = 0; i < n; i++) { + newCostMatrix[n][i] = null; + newCostMatrix[i][n] = null; + } + newCostMatrix[n][n] = 0.0; + artificialNodeId = n; + n++; + } + + this.cost = newCostMatrix; + } + + public double getMinWeightCost() { + solve(); + return minWeightCost; + } + + /** + * Get the minimum weight cost matching. The matching is returned as an array where the nodes at + * index 2*i and 2*i+1 form a matched pair. For example, nodes at indexes (0, 1) are a pair, (2, + * 3) are another pair, etc... + * + *

How to iterate over the pairs: + * + *

{@code
+     * WeightedMaximumCardinalityMatchingRecursive mwpm = ...
+     * int[] matching = mwpm.getMatching();
+     * for (int i = 0; i < matching.length / 2; i++) {
+     *   int node1 = matching[2*i];
+     *   int node2 = matching[2*i+1];
+     *   // Do something with the matched pair (node1, node2)
+     * }
+     * }
+ */ + public int[] getMatching() { + solve(); + return matching; + } + + private void solve() { + if (solved) return; + MatchingCost[] dp = new MatchingCost[1 << n]; + int[] history = new int[1 << n]; + + MatchingCost matchingCost = f(FULL_STATE, dp, history); + minWeightCost = matchingCost.cost; + + reconstructMatching(history); + solved = true; + } + + private MatchingCost f(int state, MatchingCost[] dp, int[] history) { + if (dp[state] != null) { + return dp[state]; + } + if (state == 0) { + return new MatchingCost(); + } + int p1, p2; + // Seek to find active bit position (p1) + for (p1 = 0; p1 < n; p1++) { + if ((state & (1 << p1)) > 0) { + break; + } + } + + int bestState = -1; + MatchingCost bestMatchingCost = MatchingCost.createInfiniteValueMatchingCost(); + + for (p2 = p1 + 1; p2 < n; p2++) { + // Position `p2` is on. Try matching the pair (p1, p2) together. + if ((state & (1 << p2)) > 0) { + int reducedState = state ^ (1 << p1) ^ (1 << p2); + + MatchingCost matchCost = new MatchingCost(f(reducedState, dp, history)); + matchCost.updateMatchingCost(cost[p1][p2]); + + if (matchCost.isBetterMatchingCost(bestMatchingCost)) { + bestMatchingCost = matchCost; + bestState = reducedState; + } + } + } + + history[state] = bestState; + return dp[state] = bestMatchingCost; + } + + // Populates the `matching` array with a sorted deterministic matching sorted by lowest node + // index. For example, if the perfect matching consists of the pairs (3, 4), (1, 5), (0, 2). + // The matching is sorted such that the pairs appear in the ordering: (0, 2), (1, 5), (3, 4). + // Furthermore, it is guaranteed that for any pair (a, b) that a < b. + private void reconstructMatching(int[] history) { + // A map between pairs of nodes that were matched together. + int[] map = new int[n]; + int[] leftNodes = new int[n / 2]; + + int matchingSize = 0; + + // Reconstruct the matching of pairs of nodes working backwards through computed states. + for (int i = 0, state = FULL_STATE; state != 0; state = history[state]) { + // Isolate the pair used by xoring the state with the state used to generate it. + int pairUsed = state ^ history[state]; + + int leftNode = getBitPosition(Integer.lowestOneBit(pairUsed)); + int rightNode = getBitPosition(Integer.highestOneBit(pairUsed)); + + leftNodes[i++] = leftNode; + map[leftNode] = rightNode; + + if (cost[leftNode][rightNode] != null) { + matchingSize++; + } + } + + // Sort the left nodes in ascending order. + java.util.Arrays.sort(leftNodes); + + matchingSize = matchingSize * 2; + matching = new int[matchingSize]; + + for (int i = 0, j = 0; i < n / 2; i++) { + int leftNode = leftNodes[i]; + int rightNode = map[leftNodes[i]]; + // Ignore the artificial node when there is an odd number of nodes. + if (isOdd && (leftNode == artificialNodeId || rightNode == artificialNodeId)) { + continue; + } + // Only match edges which actually exist + if (cost[leftNode][rightNode] != null) { + matching[2 * j] = leftNode; + matching[2 * j + 1] = rightNode; + j++; + } + } + } + + // Gets the zero base index position of the 1 bit in `k`. `k` must be a power of 2, so there is + // only ever 1 bit in the binary representation of k. + private int getBitPosition(int k) { + int count = -1; + while (k > 0) { + count++; + k >>= 1; + } + return count; + } + } // WeightedMaximumCardinalityMatchingRecursive + + // TODO(william): implement tests for this modified Eulerian path algo. It should be able to + // return + // the correct edges from a multi graph. The other impl in this repo bans multigraphs. + private static class EulerianPathDirectedEdgesAdjacencyList { + + private final int n; + private int edgeCount; + private int[] in, out; + private LinkedList path; + private List> graph; + + public EulerianPathDirectedEdgesAdjacencyList(List> graph) { + if (graph == null) throw new IllegalArgumentException("Graph cannot be null"); + n = graph.size(); + this.graph = graph; + path = new LinkedList<>(); + } + + // Returns a list of edgeCount + 1 node ids that give the Eulerian path or + // null if no path exists or the graph is disconnected. + public List getEulerianPath() { + setUp(); + + if (!graphHasEulerianPath()) { + System.out.println("Graph has no Eulerian path"); + return null; + } + dfs(findStartNode()); + + // Instead of returning the 'path' as a linked list return + // the solution as a primitive array for convenience. + List soln = new ArrayList<>(); + for (int i = 0; !path.isEmpty(); i++) soln.add(path.removeFirst()); + + return soln; + } + + private void setUp() { + // Arrays that track the in degree and out degree of each node. + in = new int[n]; + out = new int[n]; + + edgeCount = 0; + + // Compute in and out node degrees. + for (int from = 0; from < n; from++) { + for (Edge e : graph.get(from)) { + in[e.to]++; + out[e.from]++; + edgeCount++; + } + } + } + + private boolean graphHasEulerianPath() { + if (edgeCount == 0) return false; + int startNodes = 0, endNodes = 0; + for (int i = 0; i < n; i++) { + if (out[i] - in[i] > 1 || in[i] - out[i] > 1) return false; + else if (out[i] - in[i] == 1) startNodes++; + else if (in[i] - out[i] == 1) endNodes++; + } + return (endNodes == 0 && startNodes == 0) || (endNodes == 1 && startNodes == 1); + } + + private int findStartNode() { + int start = 0; + for (int i = 0; i < n; i++) { + // Unique starting node. + if (out[i] - in[i] == 1) return i; + // Start at a node with an outgoing edge. + if (out[i] > 0) start = i; + } + return start; + } + + // Perform DFS to find Eulerian path. + private void dfs(int at) { + while (out[at] != 0) { + Edge nextEdge = graph.get(at).get(--out[at]); + dfs(nextEdge.to); + path.addFirst(nextEdge); + } + } + } // EulerianPathDirectedEdgesAdjacencyList + + public static List> createEmptyGraph(int n) { + List> g = new ArrayList<>(); + for (int i = 0; i < n; i++) { + g.add(new ArrayList<>()); + } + return g; + } + + public static void addDirectedEdge(List> g, int from, int to, double cost) { + g.get(from).add(new Edge(from, to, cost)); + } + + public static void addUndirectedEdge(List> g, int from, int to, double cost) { + addDirectedEdge(g, from, to, cost); + addDirectedEdge(g, to, from, cost); + } + + public static void main(String[] args) { + cppTest1(); + // eulerTest1(); + } + + private static void eulerTest1() { + int n = 2; + List> g = createEmptyGraph(n); + addDirectedEdge(g, 0, 0, 0); + addDirectedEdge(g, 0, 1, 1); + addDirectedEdge(g, 0, 1, 2); + addDirectedEdge(g, 1, 0, 3); + addDirectedEdge(g, 1, 0, 4); + addDirectedEdge(g, 1, 1, 5); + EulerianPathDirectedEdgesAdjacencyList solver = new EulerianPathDirectedEdgesAdjacencyList(g); + List path = solver.getEulerianPath(); + for (Edge edge : path) { + System.out.printf("%d -> %d with cost: %f\n", edge.from, edge.to, edge.cost); + } + } + + private static void cppTest1() { + int n = 6; + List> g = createEmptyGraph(n); + addUndirectedEdge(g, 0, 1, 5); + addUndirectedEdge(g, 0, 2, 3); + addUndirectedEdge(g, 0, 3, 2); + addUndirectedEdge(g, 1, 4, 3); + addUndirectedEdge(g, 1, 5, 6); + addUndirectedEdge(g, 2, 3, 1); + addUndirectedEdge(g, 3, 4, 1); + addUndirectedEdge(g, 4, 5, 8); + + ChinesePostmanProblem cpp = new ChinesePostmanProblem(g); + } +} diff --git a/com/williamfiset/algorithms/graphtheory/ConnectedComponentsAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsAdjacencyList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/ConnectedComponentsAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsAdjacencyList.java diff --git a/com/williamfiset/algorithms/graphtheory/ConnectedComponentsDfsSolverAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsDfsSolverAdjacencyList.java similarity index 97% rename from com/williamfiset/algorithms/graphtheory/ConnectedComponentsDfsSolverAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsDfsSolverAdjacencyList.java index 7ee545ada..827945121 100644 --- a/com/williamfiset/algorithms/graphtheory/ConnectedComponentsDfsSolverAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/ConnectedComponentsDfsSolverAdjacencyList.java @@ -19,7 +19,9 @@ public class ConnectedComponentsDfsSolverAdjacencyList { private boolean[] visited; private List> graph; - /** @param graph - An undirected graph as an adjacency list. */ + /** + * @param graph - An undirected graph as an adjacency list. + */ public ConnectedComponentsDfsSolverAdjacencyList(List> graph) { if (graph == null) throw new NullPointerException(); this.n = graph.size(); diff --git a/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterative.java b/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterative.java similarity index 80% rename from com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterative.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterative.java index 9da362002..a9ba446b5 100644 --- a/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterative.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterative.java @@ -30,20 +30,18 @@ static int dfs(Map> graph, int start, int n) { // Start by visiting the starting node stack.push(start); + visited[start] = true; while (!stack.isEmpty()) { int node = stack.pop(); - if (!visited[node]) { - - count++; - visited[node] = true; - List edges = graph.get(node); - - if (edges != null) { - for (Edge edge : edges) { - if (!visited[edge.to]) { - stack.push(edge.to); - } + count++; + List edges = graph.get(node); + + if (edges != null) { + for (Edge edge : edges) { + if (!visited[edge.to]) { + stack.push(edge.to); + visited[edge.to] = true; } } } @@ -56,6 +54,17 @@ static int dfs(Map> graph, int start, int n) { public static void main(String[] args) { // Create a fully connected graph + // (0) + // / \ + // 5 / \ 4 + // / \ + // 10 < -2 > + // +->(2)<------(1) (4) + // +--- \ / + // \ / + // 1 \ / 6 + // > < + // (3) int numNodes = 5; Map> graph = new HashMap<>(); addDirectedEdge(graph, 0, 1, 4); diff --git a/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java b/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java similarity index 92% rename from com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java index 27c3322dc..3914181b7 100644 --- a/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListIterativeFastStack.java @@ -93,6 +93,17 @@ static int dfs(Map> graph, int start, int n) { public static void main(String[] args) { // Create a fully connected graph + // (0) + // / \ + // 5 / \ 4 + // / \ + // 10 < -2 > + // +->(2)<------(1) (4) + // +--- \ / + // \ / + // 1 \ / 6 + // > < + // (3) int numNodes = 5; Map> graph = new HashMap<>(); addDirectedEdge(graph, 0, 1, 4); diff --git a/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListRecursive.java b/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListRecursive.java similarity index 89% rename from com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListRecursive.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListRecursive.java index 66325cf1b..4c50679d0 100644 --- a/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListRecursive.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/DepthFirstSearchAdjacencyListRecursive.java @@ -45,6 +45,17 @@ static long dfs(int at, boolean[] visited, Map> graph) { public static void main(String[] args) { // Create a fully connected graph + // (0) + // / \ + // 5 / \ 4 + // / \ + // 10 < -2 > + // +->(2)<------(1) (4) + // +--- \ / + // \ / + // 1 \ / 6 + // > < + // (3) int numNodes = 5; Map> graph = new HashMap<>(); addDirectedEdge(graph, 0, 1, 4); diff --git a/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyList.java diff --git a/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyListWithDHeap.java b/src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyListWithDHeap.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyListWithDHeap.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/DijkstrasShortestPathAdjacencyListWithDHeap.java diff --git a/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/EagerPrimsAdjacencyList.java diff --git a/com/williamfiset/algorithms/graphtheory/EulerianPathDirectedEdgesAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/EulerianPathDirectedEdgesAdjacencyList.java similarity index 96% rename from com/williamfiset/algorithms/graphtheory/EulerianPathDirectedEdgesAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/EulerianPathDirectedEdgesAdjacencyList.java index 04edf0760..12fd97dd8 100644 --- a/com/williamfiset/algorithms/graphtheory/EulerianPathDirectedEdgesAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/EulerianPathDirectedEdgesAdjacencyList.java @@ -5,6 +5,8 @@ *

Test against: https://open.kattis.com/problems/eulerianpath * http://codeforces.com/contest/508/problem/D * + *

Run: ./gradlew run -Palgorithm=graphtheory.EulerianPathDirectedEdgesAdjacencyList + * *

Time Complexity: O(E) * * @author William Fiset, william.alexandre.fiset@gmail.com @@ -115,7 +117,7 @@ public static void addDirectedEdge(List> g, int from, int to) { public static void main(String[] args) { exampleFromSlides(); - // smallExample(); + smallExample(); } private static void exampleFromSlides() { @@ -156,7 +158,7 @@ private static void smallExample() { EulerianPathDirectedEdgesAdjacencyList solver; solver = new EulerianPathDirectedEdgesAdjacencyList(graph); - // Outputs path: [0, 1, 2, 1, 4, 1, 3] + // Outputs path: [0, 1, 4, 1, 2, 1, 3] System.out.println(Arrays.toString(solver.getEulerianPath())); } } diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/EulerianPathUndirectedEdgesAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/EulerianPathUndirectedEdgesAdjacencyList.java new file mode 100644 index 000000000..f7f14bf72 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/EulerianPathUndirectedEdgesAdjacencyList.java @@ -0,0 +1,137 @@ +/** + * Implementation of finding an Eulerian Path on a n undirected graph. + * + *

This impl is still a WIP + * + *

Run: ./gradlew run -Palgorithm=graphtheory.EulerianPathUndirectedEdgesAdjacencyList + * + *

Time Complexity: O(E) + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.graphtheory; + +public class EulerianPathUndirectedEdgesAdjacencyList { + + /* + private static class EulerUndirectedEdge { + // `u` and `v` are the ids of the two nodes which connect this edge. + int u, v; + double cost; + + // Whether or not this edge has been included in the Eulerian path + boolean used; + + public EulerUndirectedEdge(int u, int v, double cost) { + this.u = u; this.v = v; this.cost = cost; + } + + @Override + public String toString() { + return "(" + u + ", " + v + ", " + used + "," + cost + ")"; + } + } // EulerUndirectedEdge + + public static List> createEmptyGraph(int n) { + List> g = new ArrayList<>(); + for (int i = 0; i < n; i++) { + g.add(new ArrayList<>()); + } + return g; + } + + + public static void addEulerUndirectedEdge(List> g, int u, int v, double cost) { + Edge edge = new Edge(u, v, cost); + // Intentionally add the same edge reference + g.get(u).add(edge); + g.get(v).add(edge); + } + + private final int n; + private int edgeCount; + private int[] degree; + private LinkedList path; + private List> graph; + + public EulerianPathUndirectedEdgesAdjacencyList(List> graph) { + if (graph == null) throw new IllegalArgumentException("Graph cannot be null"); + n = graph.size(); + this.graph = graph; + path = new LinkedList<>(); + } + + // Returns a list of edgeCount + 1 node ids that give the Eulerian path or + // null if no path exists or the graph is disconnected. + public List getEulerianPath() { + setUp(); + + if (!graphHasEulerianPath()) { + System.out.println("Graph has no Eulerian path"); + return null; + } + dfs(findStartNode()); + + // Instead of returning the 'path' as a linked list return + // the solution as a primitive array for convenience. + List soln = new ArrayList<>(); + for (int i = 0; !path.isEmpty(); i++) soln.add(path.removeFirst()); + + return soln; + } + + private static void resetUsed(List> g) { + for (int from = 0; from < n; from++) { + for (EulerUndirectedEdge e : graph.get(from)) { + e.used = false; + } + } + } + + private void setUp() { + degree = new int[n]; + edgeCount = 0; + + for (int from = 0; from < n; from++) { + for (EulerUndirectedEdge e : graph.get(from)) { + if (e.used) continue; + degree[e.u]++; + // Because edges have duplicate refs in the graph skip the opposing edge. + e.used = true; + edgeCount++; + } + } + resetUsed(graph); + } + + private boolean graphHasEulerianPath() { + int oddNodes = 0; + for (int i = 0; i < n; i++) { + if (degree[i] % 2 != 0) { + oddNodes++; + } + } + // Either every vertex has even degree, or exactly two vertices have an odd degree. + return (oddNodes == 0) || (oddNodes == 2); + } + + private int findStartNode() { + int start = 0; + // TODO(william): handle the case where `0` starts on an island + for (int i = 0; i < n; i++) { + // Start at an odd node if one exists. + if (degree[i] % 2 != 0) return i; + } + return start; + } + + // Perform DFS to find Eulerian path. + private void dfs(int at) { + while (out[at] != 0) { + EulerUndirectedEdge nextEdge = graph.get(at).get(--out[at]); + dfs(nextEdge.to); + path.addFirst(nextEdge); + } + } + */ +} diff --git a/com/williamfiset/algorithms/graphtheory/FloydWarshallSolver.java b/src/main/java/com/williamfiset/algorithms/graphtheory/FloydWarshallSolver.java similarity index 98% rename from com/williamfiset/algorithms/graphtheory/FloydWarshallSolver.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/FloydWarshallSolver.java index 1ffc0d2c2..7bf794f18 100644 --- a/com/williamfiset/algorithms/graphtheory/FloydWarshallSolver.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/FloydWarshallSolver.java @@ -80,7 +80,7 @@ public void solve() { for (int k = 0; k < n; k++) for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) - if (dp[i][k] + dp[k][j] < dp[i][j]) { + if (dp[i][k] != POSITIVE_INFINITY && dp[k][j] != POSITIVE_INFINITY && dp[k][k] < 0) { dp[i][j] = NEGATIVE_INFINITY; next[i][j] = REACHES_NEGATIVE_CYCLE; } diff --git a/com/williamfiset/algorithms/graphtheory/GraphDiameter.java b/src/main/java/com/williamfiset/algorithms/graphtheory/GraphDiameter.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/GraphDiameter.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/GraphDiameter.java diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/Kahns.java b/src/main/java/com/williamfiset/algorithms/graphtheory/Kahns.java new file mode 100644 index 000000000..6a0bc57ce --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/Kahns.java @@ -0,0 +1,140 @@ +/** + * Implementation of Kahn's algorithm to find a topological ordering + * + *

Kahn's algorithm finds a topological ordering by iteratively removing nodes in the graph which + * have no incoming edges. When a node is removed from the graph, it is added to the topological + * ordering and all its edges are removed allowing for the next set of nodes with no incoming edges + * to be selected. + * + *

Verified against: https://open.kattis.com/problems/builddeps + * + *

./gradlew run -Palgorithm=graphtheory.Kahns + * + *

Time complexity: O(V+E) + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.graphtheory; + +import static com.williamfiset.algorithms.utils.graphutils.Utils.addDirectedEdge; +import static com.williamfiset.algorithms.utils.graphutils.Utils.createEmptyAdjacencyList; + +import java.util.*; + +public class Kahns { + + // Given a an acyclic graph `g` represented as a adjacency list, return a + // topological ordering on the nodes of the graph. + public int[] kahns(List> g) { + int n = g.size(); + + // Calculate the in-degree of each node. + int[] inDegree = new int[n]; + for (List edges : g) { + for (int to : edges) { + inDegree[to]++; + } + } + + // q always contains the set nodes with no incoming edges. + Queue q = new ArrayDeque<>(); + + // Find all start nodes. + for (int i = 0; i < n; i++) { + if (inDegree[i] == 0) { + q.offer(i); + } + } + + int index = 0; + int[] order = new int[n]; + while (!q.isEmpty()) { + int at = q.poll(); + order[index++] = at; + for (int to : g.get(at)) { + inDegree[to]--; + if (inDegree[to] == 0) { + q.offer(to); + } + } + } + if (index != n) { + throw new IllegalArgumentException("Graph is not acyclic! Detected a cycle."); + } + return order; + } + + // Example usage: + public static void main(String[] args) { + exampleFromSlides(); + // test1(); + // test2(); + // cycleTest(); + } + + private static void exampleFromSlides() { + List> g = createEmptyAdjacencyList(14); + addDirectedEdge(g, 0, 2); + addDirectedEdge(g, 0, 3); + addDirectedEdge(g, 0, 6); + addDirectedEdge(g, 1, 4); + addDirectedEdge(g, 2, 6); + addDirectedEdge(g, 3, 1); + addDirectedEdge(g, 3, 4); + addDirectedEdge(g, 4, 5); + addDirectedEdge(g, 4, 8); + addDirectedEdge(g, 6, 7); + addDirectedEdge(g, 6, 11); + addDirectedEdge(g, 7, 4); + addDirectedEdge(g, 7, 12); + addDirectedEdge(g, 9, 2); + addDirectedEdge(g, 9, 10); + addDirectedEdge(g, 10, 6); + addDirectedEdge(g, 11, 12); + addDirectedEdge(g, 12, 8); + + Kahns solver = new Kahns(); + int[] ordering = solver.kahns(g); + + // Prints: [0, 9, 13, 3, 2, 10, 1, 6, 7, 11, 4, 12, 5, 8] + System.out.println(java.util.Arrays.toString(ordering)); + } + + private static void test1() { + List> g = createEmptyAdjacencyList(6); + addDirectedEdge(g, 0, 1); + addDirectedEdge(g, 0, 2); + addDirectedEdge(g, 1, 2); + addDirectedEdge(g, 3, 1); + addDirectedEdge(g, 3, 2); + addDirectedEdge(g, 2, 4); + addDirectedEdge(g, 4, 5); + Kahns solver = new Kahns(); + System.out.println(java.util.Arrays.toString(solver.kahns(g))); + } + + private static void test2() { + List> g = createEmptyAdjacencyList(6); + addDirectedEdge(g, 0, 1); + addDirectedEdge(g, 0, 2); + addDirectedEdge(g, 0, 5); + addDirectedEdge(g, 1, 2); + addDirectedEdge(g, 1, 3); + addDirectedEdge(g, 2, 3); + addDirectedEdge(g, 2, 4); + addDirectedEdge(g, 3, 4); + addDirectedEdge(g, 5, 4); + Kahns solver = new Kahns(); + System.out.println(java.util.Arrays.toString(solver.kahns(g))); + } + + private static void cycleTest() { + List> g = createEmptyAdjacencyList(4); + addDirectedEdge(g, 0, 1); + addDirectedEdge(g, 1, 2); + addDirectedEdge(g, 2, 3); + addDirectedEdge(g, 3, 0); + Kahns solver = new Kahns(); + System.out.println(java.util.Arrays.toString(solver.kahns(g))); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/Kosaraju.java b/src/main/java/com/williamfiset/algorithms/graphtheory/Kosaraju.java new file mode 100644 index 000000000..33471a126 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/Kosaraju.java @@ -0,0 +1,248 @@ +/** + * Implementation of Kosaraju's SCC algorithm + * + *

Verified against: + * + *

    + *
  • https://open.kattis.com/problems/equivalences + *
  • https://open.kattis.com/problems/runningmom + *
+ * + *

./gradlew run -Palgorithm=graphtheory.Kosaraju + */ +package com.williamfiset.algorithms.graphtheory; + +import java.util.*; + +public class Kosaraju { + + private int n; + private int sccCount; + private boolean solved; + private int[] sccs; + private boolean[] visited; + + // The post order forest traversal of the original graph resulting from the first DFS. + private List postOrderTraversal; + + private List> graph; + private List> transposeGraph; + + public Kosaraju(List> graph) { + if (graph == null) throw new IllegalArgumentException("Graph cannot be null."); + this.graph = graph; + n = graph.size(); + } + + // Returns the number of strongly connected components in the graph. + public int sccCount() { + if (!solved) solve(); + return sccCount; + } + + // Get the connected components of this graph. If two indexes + // have the same value then they're in the same SCC. + public int[] getSccs() { + if (!solved) solve(); + return sccs; + } + + private void solve() { + sccCount = 0; + sccs = new int[n]; + visited = new boolean[n]; + postOrderTraversal = new ArrayList<>(); + + for (int i = 0; i < n; i++) { + dfs1(i); + } + + Arrays.fill(visited, false); + createTransposeGraph(); + + // Reverse the post order traversal to make iterating through it + // in the next step more intuitive. + Collections.reverse(postOrderTraversal); + + for (int node : postOrderTraversal) { + if (!visited[node]) { + dfs2(node); + sccCount++; + } + } + + solved = true; + } + + // Traverse the original graph and add nodes to the post order traversal on the callback. + private void dfs1(int from) { + if (visited[from]) { + return; + } + visited[from] = true; + for (int to : graph.get(from)) { + dfs1(to); + } + postOrderTraversal.add(from); + } + + // Traverse the transverse graph and label all the encountered nodes as part of the sane SCC. + private void dfs2(int from) { + if (visited[from]) { + return; + } + visited[from] = true; + for (int to : transposeGraph.get(from)) { + dfs2(to); + } + sccs[from] = sccCount; + } + + private void createTransposeGraph() { + transposeGraph = createGraph(n); + for (int u = 0; u < n; u++) { + for (int v : graph.get(u)) { + addEdge(transposeGraph, v, u); + } + } + } + + // Initializes adjacency list with n nodes. + public static List> createGraph(int n) { + List> graph = new ArrayList<>(n); + for (int i = 0; i < n; i++) graph.add(new ArrayList<>()); + return graph; + } + + // Adds a directed edge from node 'from' to node 'to' + public static void addEdge(List> graph, int from, int to) { + graph.get(from).add(to); + } + + public static void main(String[] args) { + example1(); + // example2(); + // example3(); + // example4(); + // exampleFromCp4(); + } + + private static void exampleFromCp4() { + int n = 8; + List> graph = createGraph(n); + + addEdge(graph, 0, 1); + addEdge(graph, 1, 3); + addEdge(graph, 2, 1); + addEdge(graph, 3, 2); + addEdge(graph, 3, 4); + addEdge(graph, 4, 5); + addEdge(graph, 5, 7); + addEdge(graph, 6, 4); + addEdge(graph, 7, 6); + + runKosaraju(graph); + } + + private static void example4() { + int n = 8; + List> graph = createGraph(n); + + // [0, 3, 2, 1, 7, 6, 5, 4] + addEdge(graph, 0, 2); + addEdge(graph, 0, 3); + addEdge(graph, 0, 5); + addEdge(graph, 1, 4); + addEdge(graph, 1, 7); + addEdge(graph, 2, 1); + addEdge(graph, 3, 0); + addEdge(graph, 3, 4); + addEdge(graph, 4, 2); + addEdge(graph, 5, 7); + addEdge(graph, 6, 5); + addEdge(graph, 7, 6); + + runKosaraju(graph); + } + + private static void example3() { + int n = 6; + List> graph = createGraph(n); + + // [4, 2, 5, 0, 3, 1] + addEdge(graph, 0, 2); + addEdge(graph, 0, 5); + addEdge(graph, 1, 0); + addEdge(graph, 1, 3); + addEdge(graph, 2, 4); + addEdge(graph, 3, 1); + addEdge(graph, 3, 5); + addEdge(graph, 4, 0); + + runKosaraju(graph); + } + + private static void example2() { + // [8, 9, 5, 4, 7, 3, 2, 6, 1, 0] + // [5, 4, 8, 9, 3, 2, 7, 1, 6, 0] + int n = 10; + List> graph = createGraph(n); + + addEdge(graph, 0, 1); + addEdge(graph, 1, 2); + addEdge(graph, 1, 6); + addEdge(graph, 2, 3); + addEdge(graph, 3, 4); + addEdge(graph, 3, 7); + addEdge(graph, 4, 5); + addEdge(graph, 5, 9); + addEdge(graph, 6, 1); + addEdge(graph, 7, 2); + addEdge(graph, 8, 4); + addEdge(graph, 9, 8); + + runKosaraju(graph); + } + + private static void example1() { + int n = 8; + List> graph = createGraph(n); + + addEdge(graph, 6, 0); + addEdge(graph, 6, 2); + addEdge(graph, 3, 4); + addEdge(graph, 6, 4); + addEdge(graph, 2, 0); + addEdge(graph, 0, 1); + addEdge(graph, 4, 5); + addEdge(graph, 5, 6); + addEdge(graph, 3, 7); + addEdge(graph, 7, 5); + addEdge(graph, 1, 2); + addEdge(graph, 7, 3); + addEdge(graph, 5, 0); + + // Prints: + // Number of Strongly Connected Components: 3 + // Nodes: [3, 7] form a Strongly Connected Component. + // Nodes: [4, 5, 6] form a Strongly Connected Component. + // Nodes: [0, 1, 2] form a Strongly Connected Component. + runKosaraju(graph); + } + + private static void runKosaraju(List> graph) { + int n = graph.size(); + Kosaraju solver = new Kosaraju(graph); + int[] sccs = solver.getSccs(); + Map> multimap = new HashMap<>(); + for (int i = 0; i < n; i++) { + if (!multimap.containsKey(sccs[i])) multimap.put(sccs[i], new ArrayList<>()); + multimap.get(sccs[i]).add(i); + } + + System.out.printf("Number of Strongly Connected Components: %d\n", solver.sccCount()); + for (List scc : multimap.values()) { + System.out.println("Nodes: " + scc + " form a Strongly Connected Component."); + } + } +} diff --git a/com/williamfiset/algorithms/graphtheory/KruskalsEdgeList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/KruskalsEdgeList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeList.java diff --git a/com/williamfiset/algorithms/graphtheory/KruskalsEdgeListPartialSortSolver.java b/src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeListPartialSortSolver.java similarity index 99% rename from com/williamfiset/algorithms/graphtheory/KruskalsEdgeListPartialSortSolver.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeListPartialSortSolver.java index e1742176c..b3a651014 100644 --- a/com/williamfiset/algorithms/graphtheory/KruskalsEdgeListPartialSortSolver.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/KruskalsEdgeListPartialSortSolver.java @@ -17,12 +17,14 @@ public class KruskalsEdgeListPartialSortSolver { static class Edge implements Comparable { int u, v, cost; + // 'u' and 'v' are nodes indexes and 'cost' is the cost of taking this edge. public Edge(int u, int v, int cost) { this.u = u; this.v = v; this.cost = cost; } + // Sort edges based on cost. @Override public int compareTo(Edge other) { diff --git a/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyList.java diff --git a/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyMatrix.java b/src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyMatrix.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyMatrix.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/LazyPrimsAdjacencyMatrix.java diff --git a/com/williamfiset/algorithms/graphtheory/SteinerTree.java b/src/main/java/com/williamfiset/algorithms/graphtheory/SteinerTree.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/SteinerTree.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/SteinerTree.java diff --git a/com/williamfiset/algorithms/graphtheory/TarjanAdjacencyMatrix.java b/src/main/java/com/williamfiset/algorithms/graphtheory/TarjanAdjacencyMatrix.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/TarjanAdjacencyMatrix.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/TarjanAdjacencyMatrix.java diff --git a/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyList.java similarity index 74% rename from com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyList.java index 74dee65f5..18f547b43 100644 --- a/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyList.java @@ -1,6 +1,14 @@ /** * An implementation of Tarjan's Strongly Connected Components algorithm using an adjacency list. * + *

Verified against: + * + *

    + *
  • https://open.kattis.com/problems/equivalences + *
  • https://open.kattis.com/problems/runningmom + *
  • https://www.hackerearth.com/practice/algorithms/graphs/strongly-connected-components/tutorial + *
+ * *

Time complexity: O(V+E) * * @author William Fiset, william.alexandre.fiset@gmail.com @@ -18,8 +26,8 @@ public class TarjanSccSolverAdjacencyList { private boolean solved; private int sccCount, id; - private boolean[] onStack; - private int[] ids, low; + private boolean[] visited; + private int[] ids, low, sccs; private Deque stack; private static final int UNVISITED = -1; @@ -40,7 +48,7 @@ public int sccCount() { // have the same value then they're in the same SCC. public int[] getSccs() { if (!solved) solve(); - return low; + return sccs; } public void solve() { @@ -48,32 +56,55 @@ public void solve() { ids = new int[n]; low = new int[n]; - onStack = new boolean[n]; + sccs = new int[n]; + visited = new boolean[n]; stack = new ArrayDeque<>(); Arrays.fill(ids, UNVISITED); - for (int i = 0; i < n; i++) if (ids[i] == UNVISITED) dfs(i); + for (int i = 0; i < n; i++) { + if (ids[i] == UNVISITED) { + dfs(i); + } + } solved = true; } private void dfs(int at) { - - stack.push(at); - onStack[at] = true; ids[at] = low[at] = id++; + stack.push(at); + visited[at] = true; for (int to : graph.get(at)) { - if (ids[to] == UNVISITED) dfs(to); - if (onStack[to]) low[at] = min(low[at], low[to]); + if (ids[to] == UNVISITED) { + dfs(to); + } + if (visited[to]) { + low[at] = min(low[at], low[to]); + } + /* + TODO(william): investigate whether the proper way to update the lowlinks + is the following bit of code. From my experience this doesn't seem to + matter if the output is placed in a separate output array, but this needs + further investigation. + + if (ids[to] == UNVISITED) { + dfs(to); + low[at] = min(low[at], low[to]); + } + if (visited[to]) { + low[at] = min(low[at], ids[to]); + } + */ + } // On recursive callback, if we're at the root node (start of SCC) // empty the seen stack until back to root. if (ids[at] == low[at]) { for (int node = stack.pop(); ; node = stack.pop()) { - onStack[node] = false; - low[node] = ids[at]; + visited[node] = false; + sccs[node] = sccCount; if (node == at) break; } sccCount++; diff --git a/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyList.java diff --git a/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyMatrix.java b/src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyMatrix.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyMatrix.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/TopologicalSortAdjacencyMatrix.java diff --git a/com/williamfiset/algorithms/graphtheory/TspBruteForce.java b/src/main/java/com/williamfiset/algorithms/graphtheory/TspBruteForce.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/TspBruteForce.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/TspBruteForce.java diff --git a/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingIterative.java b/src/main/java/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingIterative.java similarity index 95% rename from com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingIterative.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingIterative.java index 72af1ef65..a75509b8f 100644 --- a/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingIterative.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingIterative.java @@ -100,20 +100,20 @@ public void solve() { // Reconstruct TSP path from memo table. for (int i = 1; i < N; i++) { - int index = -1; + int bestIndex = -1; + double bestDist = Double.POSITIVE_INFINITY; for (int j = 0; j < N; j++) { if (j == start || notIn(j, state)) continue; - if (index == -1) index = j; - double prevDist = memo[index][state] + distance[index][lastIndex]; double newDist = memo[j][state] + distance[j][lastIndex]; - if (newDist < prevDist) { - index = j; + if (newDist < bestDist) { + bestIndex = j; + bestDist = newDist; } } - tour.add(index); - state = state ^ (1 << index); - lastIndex = index; + tour.add(bestIndex); + state = state ^ (1 << bestIndex); + lastIndex = bestIndex; } tour.add(start); diff --git a/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingRecursive.java b/src/main/java/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingRecursive.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingRecursive.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/TspDynamicProgrammingRecursive.java diff --git a/com/williamfiset/algorithms/graphtheory/TwoSatSolverAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/TwoSatSolverAdjacencyList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/TwoSatSolverAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/TwoSatSolverAdjacencyList.java diff --git a/com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.java b/src/main/java/com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.java diff --git a/com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.numbers b/src/main/java/com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.numbers similarity index 100% rename from com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.numbers rename to src/main/java/com/williamfiset/algorithms/graphtheory/analysis/PrimsGraphRepresentationAnaylsis.numbers diff --git a/com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java b/src/main/java/com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java similarity index 98% rename from com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java index 32edb7098..835070f2c 100644 --- a/com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java @@ -6,9 +6,11 @@ * *

Change directory to the root of the Algorithms directory: $ cd Algorithms * - *

Compile: $ javac com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java + *

Compile: $ javac -d src/main/java + * src/main/java/com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample.java * - *

Run: $ java com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample + *

Run: $ java -cp src/main/java + * com/williamfiset/algorithms/graphtheory/examples/EagerPrimsExample * *

Time Complexity: O(ElogV) * diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyList.java diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/CapacityScalingSolverAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/CapacityScalingSolverAdjacencyList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/networkflow/CapacityScalingSolverAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/CapacityScalingSolverAdjacencyList.java diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/Dinics.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/Dinics.java similarity index 97% rename from com/williamfiset/algorithms/graphtheory/networkflow/Dinics.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/Dinics.java index cb78d6f43..8ae448050 100644 --- a/com/williamfiset/algorithms/graphtheory/networkflow/Dinics.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/Dinics.java @@ -2,6 +2,10 @@ * Implementation of Dinic's network flow algorithm. The algorithm works by first constructing a * level graph using a BFS and then finding augmenting paths on the level graph using multiple DFSs. * + *

Run script: + * + *

$ ./gradlew run -Palgorithm=graphtheory.networkflow.Dinics + * *

Time Complexity: O(EV²) * * @author William Fiset, william.alexandre.fiset@gmail.com diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/EdmondsKarpAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/EdmondsKarpAdjacencyList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/networkflow/EdmondsKarpAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/EdmondsKarpAdjacencyList.java diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDFSAdjacencyMatrix.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDFSAdjacencyMatrix.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDFSAdjacencyMatrix.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDFSAdjacencyMatrix.java diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDfsSolverAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDfsSolverAdjacencyList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDfsSolverAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/FordFulkersonDfsSolverAdjacencyList.java diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/MaximumCardinalityBipartiteMatchingAugmentingPathAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MaximumCardinalityBipartiteMatchingAugmentingPathAdjacencyList.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/networkflow/MaximumCardinalityBipartiteMatchingAugmentingPathAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MaximumCardinalityBipartiteMatchingAugmentingPathAdjacencyList.java diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowJohnsons.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowJohnsons.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowJohnsons.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowJohnsons.java diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowWithBellmanFord.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowWithBellmanFord.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowWithBellmanFord.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowWithBellmanFord.java diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/NetworkFlowSolverBase.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/NetworkFlowSolverBase.java similarity index 98% rename from com/williamfiset/algorithms/graphtheory/networkflow/NetworkFlowSolverBase.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/NetworkFlowSolverBase.java index c8eda5db2..bba05a637 100644 --- a/com/williamfiset/algorithms/graphtheory/networkflow/NetworkFlowSolverBase.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/NetworkFlowSolverBase.java @@ -1,4 +1,6 @@ -/** @author William Fiset, william.alexandre.fiset@gmail.com */ +/** + * @author William Fiset, william.alexandre.fiset@gmail.com + */ package com.williamfiset.algorithms.graphtheory.networkflow; import java.util.ArrayList; @@ -86,6 +88,7 @@ public NetworkFlowSolverBase(int n, int s, int t) { } // Construct an empty graph with n nodes including the source and sink nodes. + @SuppressWarnings("unchecked") private void initializeGraph() { graph = new List[n]; for (int i = 0; i < n; i++) graph[i] = new ArrayList(); diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/examples/CapacityScalingExample.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/CapacityScalingExample.java similarity index 97% rename from com/williamfiset/algorithms/graphtheory/networkflow/examples/CapacityScalingExample.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/CapacityScalingExample.java index c3d47788a..e2fc0b727 100644 --- a/com/williamfiset/algorithms/graphtheory/networkflow/examples/CapacityScalingExample.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/CapacityScalingExample.java @@ -10,9 +10,9 @@ *

Change directory to the root of the Algorithms directory: $ cd Algorithms * *

Build: $ javac - * com/williamfiset/algorithms/graphtheory/networkflow/examples/CapacityScalingExample.java + * src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/CapacityScalingExample.java * - *

Run: $ java + *

Run: $ java -cp src/main/java/ * com/williamfiset/algorithms/graphtheory/networkflow/examples/CapacityScalingExample */ package com.williamfiset.algorithms.graphtheory.networkflow.examples; @@ -101,6 +101,7 @@ public NetworkFlowSolverBase(int n, int s, int t) { } // Constructs an empty graph with n nodes including s and t. + @SuppressWarnings("unchecked") private void initializeEmptyFlowGraph() { graph = new List[n]; for (int i = 0; i < n; i++) graph[i] = new ArrayList(); diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/examples/DinicsExample.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/DinicsExample.java similarity index 96% rename from com/williamfiset/algorithms/graphtheory/networkflow/examples/DinicsExample.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/DinicsExample.java index 532b008d0..5e1c1aedb 100644 --- a/com/williamfiset/algorithms/graphtheory/networkflow/examples/DinicsExample.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/DinicsExample.java @@ -8,9 +8,11 @@ * *

Change directory to the root of the Algorithms directory: $ cd Algorithms * - *

Build: $ javac com/williamfiset/algorithms/graphtheory/networkflow/examples/DinicsExample.java + *

Build: $ javac -d src/main/java + * src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/DinicsExample.java * - *

Run: $ java com/williamfiset/algorithms/graphtheory/networkflow/examples/DinicsExample + *

Run: $ java -cp src/main/java + * com/williamfiset/algorithms/graphtheory/networkflow/examples/DinicsExample */ package com.williamfiset.algorithms.graphtheory.networkflow.examples; @@ -88,6 +90,7 @@ public NetworkFlowSolverBase(int n, int s, int t) { } // Constructs an empty graph with n nodes including s and t. + @SuppressWarnings("unchecked") private void initializeEmptyFlowGraph() { graph = new List[n]; for (int i = 0; i < n; i++) graph[i] = new ArrayList(); diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/examples/EdmondsKarpExample.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/EdmondsKarpExample.java similarity index 97% rename from com/williamfiset/algorithms/graphtheory/networkflow/examples/EdmondsKarpExample.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/EdmondsKarpExample.java index f8eafb9f0..6659b13d3 100644 --- a/com/williamfiset/algorithms/graphtheory/networkflow/examples/EdmondsKarpExample.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/EdmondsKarpExample.java @@ -7,10 +7,11 @@ * *

Change directory to the root of the Algorithms directory: $ cd Algorithms * - *

Build: $ javac - * com/williamfiset/algorithms/graphtheory/networkflow/examples/EdmondsKarpExample.java + *

Build: $ javac -d src/main/java + * src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/EdmondsKarpExample.java * - *

Run: $ java com/williamfiset/algorithms/graphtheory/networkflow/examples/EdmondsKarpExample + *

Run: $ java -cp src/main/java + * com/williamfiset/algorithms/graphtheory/networkflow/examples/EdmondsKarpExample */ package com.williamfiset.algorithms.graphtheory.networkflow.examples; @@ -99,6 +100,7 @@ public NetworkFlowSolverBase(int n, int s, int t) { } // Constructs an empty graph with n nodes including s and t. + @SuppressWarnings("unchecked") private void initializeEmptyFlowGraph() { graph = new List[n]; for (int i = 0; i < n; i++) graph[i] = new ArrayList(); diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/examples/FordFulkersonExample.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/FordFulkersonExample.java similarity index 96% rename from com/williamfiset/algorithms/graphtheory/networkflow/examples/FordFulkersonExample.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/FordFulkersonExample.java index abbaf972f..64814de01 100644 --- a/com/williamfiset/algorithms/graphtheory/networkflow/examples/FordFulkersonExample.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/FordFulkersonExample.java @@ -8,10 +8,11 @@ * *

Change directory to the root of the Algorithms directory: $ cd Algorithms * - *

Build: $ javac - * com/williamfiset/algorithms/graphtheory/networkflow/examples/FordFulkersonExample.java + *

Build: $ javac -d src/main/java + * src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/FordFulkersonExample.java * - *

Run: $ java com/williamfiset/algorithms/graphtheory/networkflow/examples/FordFulkersonExample + *

Run: $ java -cp src/main/java + * com/williamfiset/algorithms/graphtheory/networkflow/examples/FordFulkersonExample */ package com.williamfiset.algorithms.graphtheory.networkflow.examples; @@ -98,6 +99,7 @@ public NetworkFlowSolverBase(int n, int s, int t) { } // Constructs an empty graph with n nodes including s and t. + @SuppressWarnings("unchecked") private void initializeEmptyFlowGraph() { graph = new List[n]; for (int i = 0; i < n; i++) graph[i] = new ArrayList(); diff --git a/com/williamfiset/algorithms/graphtheory/networkflow/examples/MiceAndOwls.java b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/MiceAndOwls.java similarity index 96% rename from com/williamfiset/algorithms/graphtheory/networkflow/examples/MiceAndOwls.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/MiceAndOwls.java index a072e022e..1f9894302 100644 --- a/com/williamfiset/algorithms/graphtheory/networkflow/examples/MiceAndOwls.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/MiceAndOwls.java @@ -3,9 +3,11 @@ * *

Change directory to the root of the Algorithms directory: $ cd Algorithms * - *

Build: $ javac com/williamfiset/algorithms/graphtheory/networkflow/examples/MiceAndOwls.java + *

Build: $ javac -d src/main/java + * src/main/java/com/williamfiset/algorithms/graphtheory/networkflow/examples/MiceAndOwls.java * - *

Run: $ java com/williamfiset/algorithms/graphtheory/networkflow/examples/MiceAndOwls + *

Run: $ java -cp src/main/java + * com/williamfiset/algorithms/graphtheory/networkflow/examples/MiceAndOwls */ package com.williamfiset.algorithms.graphtheory.networkflow.examples; @@ -162,6 +164,7 @@ public NetworkFlowSolverBase(int n, int s, int t) { } // Constructs an empty graph with n nodes including s and t. + @SuppressWarnings("unchecked") private void initializeEmptyFlowGraph() { graph = new List[n]; for (int i = 0; i < n; i++) graph[i] = new ArrayList(); diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestor.java b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestor.java new file mode 100644 index 000000000..5ed4cc500 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestor.java @@ -0,0 +1,162 @@ +package com.williamfiset.algorithms.graphtheory.treealgorithms; + +import java.util.*; + +public class LowestCommonAncestor { + + public static class TreeNode { + // Number of nodes in the subtree. Computed when tree is built. + private int n; + + private int id; + private TreeNode parent; + private List children; + + // Useful constructor for root node. + public TreeNode(int id) { + this(id, /* parent= */ null); + } + + public TreeNode(int id, TreeNode parent) { + this.id = id; + this.parent = parent; + children = new LinkedList<>(); + } + + public void addChildren(TreeNode... nodes) { + for (TreeNode node : nodes) { + children.add(node); + } + } + + public void setSize(int n) { + this.n = n; + } + + // Number of nodes in the subtree (including the node itself) + public int size() { + return n; + } + + public int id() { + return id; + } + + public TreeNode parent() { + return parent; + } + + public List children() { + return children; + } + + public static TreeNode rootTree(List> graph, int rootId) { + TreeNode root = new TreeNode(rootId); + return buildTree(graph, root); + } + + // Do dfs to construct rooted tree. + private static TreeNode buildTree(List> graph, TreeNode node) { + int subtreeNodeCount = 1; + for (int neighbor : graph.get(node.id())) { + // Ignore adding an edge pointing back to parent. + if (node.parent() != null && neighbor == node.parent().id()) { + continue; + } + + TreeNode child = new TreeNode(neighbor, node); + node.addChildren(child); + + buildTree(graph, child); + subtreeNodeCount += child.size(); + } + node.setSize(subtreeNodeCount); + return node; + } + + @Override + public String toString() { + return String.valueOf(id); + } + } + + private TreeNode lcaNode = null; + private TreeNode root; + + public LowestCommonAncestor(TreeNode root) { + this.root = root; + } + + // Finds the lowest common ancestor of the nodes with id1 and id2. + public TreeNode lca(int id1, int id2) { + lcaNode = null; + helper(root, id1, id2); + return lcaNode; + } + + private boolean helper(TreeNode node, int id1, int id2) { + if (node == null) { + return false; + } + int count = 0; + if (node.id() == id1) { + count++; + } + if (node.id() == id2) { + count++; + } + for (TreeNode child : node.children()) { + if (helper(child, id1, id2)) { + count++; + } + } + if (count == 2) { + lcaNode = node; + } + return count > 0; + } + + /* Graph/Tree creation helper methods. */ + + // Create a graph as a adjacency list with 'n' nodes. + public static List> createEmptyGraph(int n) { + List> graph = new ArrayList<>(n); + for (int i = 0; i < n; i++) graph.add(new LinkedList<>()); + return graph; + } + + public static void addUndirectedEdge(List> graph, int from, int to) { + graph.get(from).add(to); + graph.get(to).add(from); + } + + public static void main(String[] args) { + TreeNode root = createFirstTreeFromSlides(); + LowestCommonAncestor solver = new LowestCommonAncestor(root); + System.out.println(solver.lca(10, 15).id()); + } + + private static TreeNode createFirstTreeFromSlides() { + int n = 17; + List> tree = createEmptyGraph(n); + + addUndirectedEdge(tree, 0, 1); + addUndirectedEdge(tree, 0, 2); + addUndirectedEdge(tree, 1, 3); + addUndirectedEdge(tree, 1, 4); + addUndirectedEdge(tree, 2, 5); + addUndirectedEdge(tree, 2, 6); + addUndirectedEdge(tree, 2, 7); + addUndirectedEdge(tree, 3, 8); + addUndirectedEdge(tree, 3, 9); + addUndirectedEdge(tree, 5, 10); + addUndirectedEdge(tree, 5, 11); + addUndirectedEdge(tree, 7, 12); + addUndirectedEdge(tree, 7, 13); + addUndirectedEdge(tree, 11, 14); + addUndirectedEdge(tree, 11, 15); + addUndirectedEdge(tree, 11, 16); + + return TreeNode.rootTree(tree, 0); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorEulerTour.java b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorEulerTour.java new file mode 100644 index 000000000..28efcf56f --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorEulerTour.java @@ -0,0 +1,298 @@ +/** + * Implementation of finding the Lowest Common Ancestor (LCA) of a tree. This impl first finds an + * Euler tour from the root node which visits all the nodes in the tree. The node height values + * obtained from the Euler tour can then be used in combination with a sparse table to find the LCA + * in O(1). + * + *

Time Complexity: O(1) queries, O(n*log2(n)) pre-processing. + * + *

Space Complexity: O(n*log2(n)) + * + *

To run script: + * + *

./gradlew run -Palgorithm=graphtheory.treealgorithms.LowestCommonAncestorEulerTour + * + * @author William Fiset + */ +package com.williamfiset.algorithms.graphtheory.treealgorithms; + +import java.util.*; + +public class LowestCommonAncestorEulerTour { + + public static void main(String[] args) { + TreeNode root = createFirstTreeFromSlides(); + LowestCommonAncestorEulerTour solver = new LowestCommonAncestorEulerTour(root); + + // LCA of 13 and 14 = 2 + TreeNode lca = solver.lca(13, 14); + System.out.printf("LCA of 13 and 14 = %s\n", lca); + if (lca.index() != 2) { + System.out.println("Error, expected lca to be 2"); + } + + // LCA of 9 and 11 = 0 + lca = solver.lca(9, 11); + System.out.printf("LCA of 9 and 11 = %s\n", lca); + if (lca.index() != 0) { + System.out.println("Error, expected lca to be 0"); + } + + // LCA of 12 and 12 = 12 + lca = solver.lca(12, 12); + System.out.printf("LCA of 12 and 12 = %s\n", lca); + if (lca.index() != 12) { + System.out.println("Error, expected lca to be 12"); + } + } + + private static TreeNode createFirstTreeFromSlides() { + int n = 17; + List> tree = createEmptyGraph(n); + + addUndirectedEdge(tree, 0, 1); + addUndirectedEdge(tree, 0, 2); + addUndirectedEdge(tree, 1, 3); + addUndirectedEdge(tree, 1, 4); + addUndirectedEdge(tree, 2, 5); + addUndirectedEdge(tree, 2, 6); + addUndirectedEdge(tree, 2, 7); + addUndirectedEdge(tree, 3, 8); + addUndirectedEdge(tree, 3, 9); + addUndirectedEdge(tree, 5, 10); + addUndirectedEdge(tree, 5, 11); + addUndirectedEdge(tree, 7, 12); + addUndirectedEdge(tree, 7, 13); + addUndirectedEdge(tree, 11, 14); + addUndirectedEdge(tree, 11, 15); + addUndirectedEdge(tree, 11, 16); + + return TreeNode.rootTree(tree, 0); + } + + /* Graph/Tree creation helper methods. */ + + // Create a graph as a adjacency list with 'n' nodes. + public static List> createEmptyGraph(int n) { + List> graph = new ArrayList<>(n); + for (int i = 0; i < n; i++) graph.add(new LinkedList<>()); + return graph; + } + + public static void addUndirectedEdge(List> graph, int from, int to) { + graph.get(from).add(to); + graph.get(to).add(from); + } + + public static class TreeNode { + // Number of nodes in the subtree. Computed when tree is built. + private int n; + + private int index; + private TreeNode parent; + private List children; + + // Useful constructor for root node. + public TreeNode(int index) { + this(index, /* parent= */ null); + } + + public TreeNode(int index, TreeNode parent) { + this.index = index; + this.parent = parent; + children = new LinkedList<>(); + } + + public void addChildren(TreeNode... nodes) { + for (TreeNode node : nodes) { + children.add(node); + } + } + + public void setSize(int n) { + this.n = n; + } + + // Number of nodes in the subtree (including the node itself) + public int size() { + return n; + } + + public int index() { + return index; + } + + public TreeNode parent() { + return parent; + } + + public List children() { + return children; + } + + public static TreeNode rootTree(List> graph, int rootId) { + TreeNode root = new TreeNode(rootId); + TreeNode rootedTree = buildTree(graph, root); + if (rootedTree.size() < graph.size()) { + System.out.println( + "WARNING: Input graph malformed. Did you forget to include all n-1 edges?"); + } + return rootedTree; + } + + // Do dfs to construct rooted tree. + private static TreeNode buildTree(List> graph, TreeNode node) { + int subtreeNodeCount = 1; + for (int neighbor : graph.get(node.index())) { + // Ignore adding an edge pointing back to parent. + if (node.parent() != null && neighbor == node.parent().index()) { + continue; + } + + TreeNode child = new TreeNode(neighbor, node); + node.addChildren(child); + + buildTree(graph, child); + subtreeNodeCount += child.size(); + } + node.setSize(subtreeNodeCount); + return node; + } + + @Override + public String toString() { + return String.valueOf(index); + } + } + + private final int n; + + private int tourIndex = 0; + + // Populated when constructing Euler Tour. + private long[] nodeDepth; + private TreeNode[] nodeOrder; + + // The last occurrence mapping. This mapping keeps track of the last occurrence of a TreeNode in + // the Euler tour for easy indexing. + private int[] last; + + // Sparse table impl which can efficiently do Range Minimum Queries (RMQs). + private MinSparseTable sparseTable; + + public LowestCommonAncestorEulerTour(TreeNode root) { + this.n = root.size(); + setup(root); + } + + private void setup(TreeNode root) { + int eulerTourSize = 2 * n - 1; + nodeOrder = new TreeNode[eulerTourSize]; + nodeDepth = new long[eulerTourSize]; + last = new int[n]; + + // Do depth first search to construct Euler tour. + dfs(root, /* depth= */ 0); + + // Initialize and build sparse table on the `nodeDepth` array which will + // allow us to index into the `nodeOrder` array and return the LCA. + sparseTable = new MinSparseTable(nodeDepth); + } + + // Construct Euler Tour by populating the `nodeDepth` and `nodeOrder` arrays. + private void dfs(TreeNode node, long depth) { + if (node == null) { + return; + } + + visit(node, depth); + for (TreeNode child : node.children()) { + dfs(child, depth + 1); + visit(node, depth); + } + } + + private void visit(TreeNode node, long depth) { + nodeOrder[tourIndex] = node; + nodeDepth[tourIndex] = depth; + last[node.index()] = tourIndex; + tourIndex++; + } + + // Finds the lowest common ancestor of the nodes with `index1` and `index2`. + public TreeNode lca(int index1, int index2) { + int l = Math.min(last[index1], last[index2]); + int r = Math.max(last[index1], last[index2]); + int i = sparseTable.queryIndex(l, r); + return nodeOrder[i]; + } + + // Sparse table for efficient minimum range queries in O(1) with O(nlogn) space + private static class MinSparseTable { + + // The number of elements in the original input array. + private int n; + + // The maximum power of 2 needed. This value is floor(log2(n)) + private int P; + + // Fast log base 2 logarithm lookup table, 1 <= i <= n + private int[] log2; + + // The sparse table values. + private long[][] dp; + + // Index Table (IT) associated with the values in the sparse table. + private int[][] it; + + public MinSparseTable(long[] values) { + init(values); + } + + private void init(long[] v) { + n = v.length; + P = (int) (Math.log(n) / Math.log(2)); + dp = new long[P + 1][n]; + it = new int[P + 1][n]; + + for (int i = 0; i < n; i++) { + dp[0][i] = v[i]; + it[0][i] = i; + } + + log2 = new int[n + 1]; + for (int i = 2; i <= n; i++) { + log2[i] = log2[i / 2] + 1; + } + + // Build sparse table combining the values of the previous intervals. + for (int p = 1; p <= P; p++) { + for (int i = 0; i + (1 << p) <= n; i++) { + long leftInterval = dp[p - 1][i]; + long rightInterval = dp[p - 1][i + (1 << (p - 1))]; + dp[p][i] = Math.min(leftInterval, rightInterval); + + // Propagate the index of the best value + if (leftInterval <= rightInterval) { + it[p][i] = it[p - 1][i]; + } else { + it[p][i] = it[p - 1][i + (1 << (p - 1))]; + } + } + } + } + + // Returns the index of the minimum element in the range [l, r]. + public int queryIndex(int l, int r) { + int len = r - l + 1; + int p = log2[r - l + 1]; + long leftInterval = dp[p][l]; + long rightInterval = dp[p][r - (1 << p) + 1]; + if (leftInterval <= rightInterval) { + return it[p][l]; + } else { + return it[p][r - (1 << p) + 1]; + } + } + } +} diff --git a/com/williamfiset/algorithms/graphtheory/treealgorithms/RootingTree.java b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/RootingTree.java similarity index 89% rename from com/williamfiset/algorithms/graphtheory/treealgorithms/RootingTree.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/RootingTree.java index 113c629a5..09208c8bd 100644 --- a/com/williamfiset/algorithms/graphtheory/treealgorithms/RootingTree.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/RootingTree.java @@ -13,14 +13,13 @@ public class RootingTree { public static class TreeNode { - private int id; private TreeNode parent; private List children; // Useful constructor for root node. public TreeNode(int id) { - this(id, /*parent=*/ null); + this(id, /* parent= */ null); } public TreeNode(int id, TreeNode parent) { @@ -29,8 +28,10 @@ public TreeNode(int id, TreeNode parent) { children = new LinkedList<>(); } - public void addChild(TreeNode child) { - children.add(child); + public void addChildren(TreeNode... nodes) { + for (TreeNode node : nodes) { + children.add(node); + } } public int id() { @@ -45,10 +46,6 @@ public List children() { return children; } - public boolean leaf() { - return children.size() == 0; - } - @Override public String toString() { return String.valueOf(id); @@ -66,19 +63,21 @@ public boolean equals(Object obj) { public static TreeNode rootTree(List> graph, int rootId) { TreeNode root = new TreeNode(rootId); - return buildTree(graph, root, /*parent=*/ null); + return buildTree(graph, root); } // Do dfs to construct rooted tree. - private static TreeNode buildTree(List> graph, TreeNode node, TreeNode parent) { - for (int childId : graph.get(node.id)) { + private static TreeNode buildTree(List> graph, TreeNode node) { + for (int childId : graph.get(node.id())) { // Ignore adding an edge pointing back to parent. - if (parent != null && childId == parent.id) continue; + if (node.parent() != null && childId == node.parent().id()) { + continue; + } TreeNode child = new TreeNode(childId, node); - node.addChild(child); + node.addChildren(child); - buildTree(graph, child, node); + buildTree(graph, child); } return node; } diff --git a/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenter.java b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenter.java similarity index 85% rename from com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenter.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenter.java index e801ca3ab..132b32ea0 100644 --- a/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenter.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenter.java @@ -3,7 +3,8 @@ * *

Time complexity: O(V+E) * - * @author Jeffrey Xiao, https://github.com/jeffrey-xiao + * @author Original author: Jeffrey Xiao, https://github.com/jeffrey-xiao + * @author Modifications by: William Fiset, william.alexandre.fiset@gmail.com */ package com.williamfiset.algorithms.graphtheory.treealgorithms; @@ -11,33 +12,36 @@ import java.util.LinkedList; import java.util.List; -class TreeCenter { +public class TreeCenter { public static List findTreeCenters(List> tree) { final int n = tree.size(); - int[] degrees = new int[n]; + int[] degree = new int[n]; // Find all leaf nodes List leaves = new ArrayList<>(); for (int i = 0; i < n; i++) { List edges = tree.get(i); - degrees[i] = edges.size(); - if (degrees[i] <= 1) leaves.add(i); + degree[i] = edges.size(); + if (degree[i] <= 1) { + leaves.add(i); + degree[i] = 0; + } } int processedLeafs = leaves.size(); - // Remove leaf nodes and decrease the degree of - // each node adding new leaf nodes progressively + // Remove leaf nodes and decrease the degree of each node adding new leaf nodes progressively // until only the centers remain. while (processedLeafs < n) { List newLeaves = new ArrayList<>(); for (int node : leaves) { for (int neighbor : tree.get(node)) { - if (--degrees[neighbor] == 1) { + if (--degree[neighbor] == 1) { newLeaves.add(neighbor); } } + degree[node] = 0; } processedLeafs += newLeaves.size(); leaves = newLeaves; diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenterLongestPathImpl.java b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenterLongestPathImpl.java new file mode 100644 index 000000000..8b3a2265a --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenterLongestPathImpl.java @@ -0,0 +1,153 @@ +/** + * Finds the center(s) of a tree by finding the longest path through the tree. + * + *

./gradlew run + * -Pmain=com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCenterLongestPathImpl + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.graphtheory.treealgorithms; + +import java.util.*; + +public class TreeCenterLongestPathImpl { + + private static class DfsResult { + // The distance to the furthest node (from where the DFS started) + int distance; + + // The index of the furthest node (from where the DFS started) + int index; + + public DfsResult(int distance, int index) { + this.distance = distance; + this.index = index; + } + } + + private static DfsResult dfs( + List> graph, boolean[] visited, int[] prev, int at, int parent) { + + // Already visited this node + if (visited[at]) return new DfsResult(0, parent); + + // Visit this node + visited[at] = true; + + // Remember where we came from to rebuild path later on. + prev[at] = parent; + + int bestDist = 0, index = -1; + List edges = graph.get(at); + + for (int to : edges) { + DfsResult result = dfs(graph, visited, prev, to, at); + int dist = result.distance + 1; + if (dist > bestDist) { + bestDist = dist; + index = result.index; + } + } + + return new DfsResult(bestDist, index); + } + + public static List findTreeCenters(List> graph) { + List centers = new ArrayList<>(); + if (graph == null) return centers; + + int n = graph.size(); + boolean[] visited = new boolean[n]; + int[] prev = new int[n]; + + // Do DFS to find furthest node from the start + DfsResult result = dfs(graph, visited, prev, 0, -1); + int furthestNode1 = result.index; + + // Singleton + if (furthestNode1 == -1) { + centers.add(0); + return centers; + } + + // Do another DFS, but this time from the furthest node. + Arrays.fill(visited, false); + Arrays.fill(prev, 0); + + result = dfs(graph, visited, prev, furthestNode1, -1); + int furthestNode2 = result.index; + + List path = new LinkedList<>(); + for (int i = furthestNode2; i != -1; i = prev[i]) { + path.add(i); + } + + if (path.size() % 2 == 0) { + centers.add(path.get(path.size() / 2 - 1)); + } + centers.add(path.get(path.size() / 2)); + return centers; + } + + /** ********** TESTING ********* */ + + // Create an empty tree as a adjacency list. + public static List> createEmptyTree(int n) { + List> tree = new ArrayList<>(n); + for (int i = 0; i < n; i++) tree.add(new LinkedList<>()); + return tree; + } + + public static void addUndirectedEdge(List> tree, int from, int to) { + tree.get(from).add(to); + tree.get(to).add(from); + } + + public static void main(String[] args) { + + List> graph = createEmptyTree(9); + addUndirectedEdge(graph, 0, 1); + addUndirectedEdge(graph, 2, 1); + addUndirectedEdge(graph, 2, 3); + addUndirectedEdge(graph, 3, 4); + addUndirectedEdge(graph, 5, 3); + addUndirectedEdge(graph, 2, 6); + addUndirectedEdge(graph, 6, 7); + addUndirectedEdge(graph, 6, 8); + + // Centers are 2 + System.out.println(findTreeCenters(graph)); + + // Centers are 0 + List> graph2 = createEmptyTree(1); + System.out.println(findTreeCenters(graph2)); + + // Centers are 0,1 + List> graph3 = createEmptyTree(2); + addUndirectedEdge(graph3, 0, 1); + System.out.println(findTreeCenters(graph3)); + + // Centers are 1 + List> graph4 = createEmptyTree(3); + addUndirectedEdge(graph4, 0, 1); + addUndirectedEdge(graph4, 1, 2); + System.out.println(findTreeCenters(graph4)); + + // Centers are 1,2 + List> graph5 = createEmptyTree(4); + addUndirectedEdge(graph5, 0, 1); + addUndirectedEdge(graph5, 1, 2); + addUndirectedEdge(graph5, 2, 3); + System.out.println(findTreeCenters(graph5)); + + // Centers are 2,3 + List> graph6 = createEmptyTree(7); + addUndirectedEdge(graph6, 0, 1); + addUndirectedEdge(graph6, 1, 2); + addUndirectedEdge(graph6, 2, 3); + addUndirectedEdge(graph6, 3, 4); + addUndirectedEdge(graph6, 4, 5); + addUndirectedEdge(graph6, 4, 6); + System.out.println(findTreeCenters(graph6)); + } +} diff --git a/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeDiameter.java b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeDiameter.java similarity index 100% rename from com/williamfiset/algorithms/graphtheory/treealgorithms/TreeDiameter.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeDiameter.java diff --git a/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphism.java b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphism.java new file mode 100644 index 000000000..015b3ca60 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphism.java @@ -0,0 +1,211 @@ +/** + * Determines if two unrooted trees are isomorphic. This algorithm can easily be modified to support + * checking if two rooted trees are isomorphic. + * + *

Tested code against: https://uva.onlinejudge.org/external/124/p12489.pdf + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.graphtheory.treealgorithms; + +import java.util.*; + +public class TreeIsomorphism { + + public static class TreeNode { + private int id; + private TreeNode parent; + private List children; + + // Useful constructor for root node. + public TreeNode(int id) { + this(id, /* parent= */ null); + } + + public TreeNode(int id, TreeNode parent) { + this.id = id; + this.parent = parent; + children = new LinkedList<>(); + } + + public void addChildren(TreeNode... nodes) { + for (TreeNode node : nodes) { + children.add(node); + } + } + + public int id() { + return id; + } + + public TreeNode parent() { + return parent; + } + + public List children() { + return children; + } + + @Override + public String toString() { + return String.valueOf(id); + } + } + + // Determines if two unrooted trees are isomorphic + public static boolean treesAreIsomorphic(List> tree1, List> tree2) { + if (tree1.isEmpty() || tree2.isEmpty()) { + throw new IllegalArgumentException("Empty tree input"); + } + + List centers1 = findTreeCenters(tree1); + List centers2 = findTreeCenters(tree2); + + TreeNode rootedTree1 = rootTree(tree1, centers1.get(0)); + String tree1Encoding = encode(rootedTree1); + + for (int center : centers2) { + TreeNode rootedTree2 = rootTree(tree2, center); + String tree2Encoding = encode(rootedTree2); + + if (tree1Encoding.equals(tree2Encoding)) { + return true; + } + } + return false; + } + + private static List findTreeCenters(List> tree) { + int n = tree.size(); + + int[] degree = new int[n]; + List leaves = new ArrayList<>(); + + // Find the first outer layer of leaf nodes. + for (int i = 0; i < n; i++) { + List edges = tree.get(i); + degree[i] = edges.size(); + if (degree[i] <= 1) { + leaves.add(i); + degree[i] = 0; + } + } + + int processedLeafs = leaves.size(); + + // Iteratively remove leaf nodes layer by layer until only the centers remain. + while (processedLeafs < n) { + List newLeaves = new ArrayList<>(); + for (int node : leaves) { + for (int neighbor : tree.get(node)) { + if (--degree[neighbor] == 1) { + newLeaves.add(neighbor); + } + } + degree[node] = 0; + } + processedLeafs += newLeaves.size(); + leaves = newLeaves; + } + + return leaves; + } + + private static TreeNode rootTree(List> graph, int rootId) { + TreeNode root = new TreeNode(rootId); + return buildTree(graph, root); + } + + // Do dfs to construct rooted tree. + private static TreeNode buildTree(List> graph, TreeNode node) { + for (int neighbor : graph.get(node.id())) { + // Ignore adding an edge pointing back to parent. + if (node.parent() != null && neighbor == node.parent().id()) { + continue; + } + + TreeNode child = new TreeNode(neighbor, node); + node.addChildren(child); + + buildTree(graph, child); + } + return node; + } + + // Constructs the canonical form representation of a tree as a string. + public static String encode(TreeNode node) { + if (node == null) { + return ""; + } + List labels = new LinkedList<>(); + for (TreeNode child : node.children()) { + labels.add(encode(child)); + } + Collections.sort(labels); + StringBuilder sb = new StringBuilder(); + for (String label : labels) { + sb.append(label); + } + return "(" + sb.toString() + ")"; + } + + /* Graph/Tree creation helper methods. */ + + // Create a graph as a adjacency list with 'n' nodes. + public static List> createEmptyGraph(int n) { + List> graph = new ArrayList<>(n); + for (int i = 0; i < n; i++) graph.add(new LinkedList<>()); + return graph; + } + + public static void addUndirectedEdge(List> graph, int from, int to) { + graph.get(from).add(to); + graph.get(to).add(from); + } + + /* Example usage */ + + public static void main(String[] args) { + simpleIsomorphismTest(); + testEncodingTreeFromSlides(); + } + + // Test if two tree are isomorphic, meaning they are structurally equivalent + // but are labeled differently. + private static void simpleIsomorphismTest() { + List> tree1 = createEmptyGraph(5); + addUndirectedEdge(tree1, 2, 0); + addUndirectedEdge(tree1, 3, 4); + addUndirectedEdge(tree1, 2, 1); + addUndirectedEdge(tree1, 2, 3); + + List> tree2 = createEmptyGraph(5); + addUndirectedEdge(tree2, 1, 0); + addUndirectedEdge(tree2, 2, 4); + addUndirectedEdge(tree2, 1, 3); + addUndirectedEdge(tree2, 1, 2); + + if (!treesAreIsomorphic(tree1, tree2)) { + System.out.println("Oops, these tree should be isomorphic!"); + } + } + + private static void testEncodingTreeFromSlides() { + List> tree = createEmptyGraph(10); + addUndirectedEdge(tree, 0, 2); + addUndirectedEdge(tree, 0, 1); + addUndirectedEdge(tree, 0, 3); + addUndirectedEdge(tree, 2, 6); + addUndirectedEdge(tree, 2, 7); + addUndirectedEdge(tree, 1, 4); + addUndirectedEdge(tree, 1, 5); + addUndirectedEdge(tree, 5, 9); + addUndirectedEdge(tree, 3, 8); + + TreeNode root0 = rootTree(tree, 0); + + if (!encode(root0).equals("(((())())(()())(()))")) { + System.out.println("Tree encoding is wrong: " + encode(root0)); + } + } +} diff --git a/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCanonicalFormAdjacencyList.java b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismWithBfs.java similarity index 90% rename from com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCanonicalFormAdjacencyList.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismWithBfs.java index b489a1983..7f3ccd01d 100644 --- a/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCanonicalFormAdjacencyList.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismWithBfs.java @@ -5,6 +5,9 @@ * *

http://webhome.cs.uvic.ca/~wendym/courses/582/16/notes/582_12_tree_can_form.pdf * + *

This implementation uses a breadth first search on an undirected graph to generate the tree's + * canonical encoding. + * *

Tested code against: https://uva.onlinejudge.org/external/124/p12489.pdf * * @author William Fiset, william.alexandre.fiset@gmail.com @@ -13,7 +16,7 @@ import java.util.*; -public class TreeCanonicalFormAdjacencyList { +public class TreeIsomorphismWithBfs { public static List> createEmptyTree(int n) { List> tree = new ArrayList<>(n); @@ -26,7 +29,7 @@ public static void addUndirectedEdge(List> tree, int from, int to) tree.get(to).add(from); } - public static List findTreeCenters(List> tree) { + private static List findTreeCenters(List> tree) { final int n = tree.size(); int[] degrees = new int[n]; @@ -56,6 +59,7 @@ public static List findTreeCenters(List> tree) { // Encodes a tree as a string such that any isomorphic tree // also has the same encoding. + // TODO(william): make this method private and test only with the treesAreIsomorphic method public static String encodeTree(List> tree) { if (tree == null || tree.size() == 0) return ""; if (tree.size() == 1) return "()"; @@ -135,7 +139,11 @@ public static String encodeTree(List> tree) { // Two nodes remain and we need to combine their labels String l2 = map[leafs.get(1)]; - return (l1.compareTo(l2) < 0) ? (l1 + l2) : (l2 + l1); + return ((l1.compareTo(l2) < 0) ? (l1 + l2) : (l2 + l1)); + } + + public static boolean treesAreIsomorphic(List> tree1, List> tree2) { + return encodeTree(tree1).equals(encodeTree(tree2)); } /* Example usage */ diff --git a/com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeHeight.java b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeHeight.java similarity index 87% rename from com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeHeight.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeHeight.java index facfe2be9..d9138586d 100644 --- a/com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeHeight.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeHeight.java @@ -1,14 +1,11 @@ /** * Tree height example * - *

Download the code: $ git clone https://github.com/williamfiset/Algorithms + *

Download the code:
+ * $ git clone https://github.com/williamfiset/Algorithms * - *

Change directory to the root of the Algorithms directory: $ cd Algorithms - * - *

Compile: $ javac - * com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeHeight.java - * - *

Run: $ java com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeHeight + *

Run:
+ * $ ./gradlew run -Palgorithm=graphtheory.treealgorithms.examples.TreeHeight * *

Time Complexity: O(n) * @@ -16,8 +13,6 @@ */ package com.williamfiset.algorithms.graphtheory.treealgorithms.examples; -import java.util.*; - public class TreeHeight { public static class TreeNode { diff --git a/com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeSum.java b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeSum.java similarity index 82% rename from com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeSum.java rename to src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeSum.java index 8a88a0c5d..ca1105393 100644 --- a/com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeSum.java +++ b/src/main/java/com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeSum.java @@ -1,13 +1,11 @@ /** * Tree sum example * - *

Download the code: $ git clone https://github.com/williamfiset/Algorithms + *

Download the code:
+ * $ git clone https://github.com/williamfiset/Algorithms * - *

Change directory to the root of the Algorithms directory: $ cd Algorithms - * - *

Compile: $ javac com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeSum.java - * - *

Run: $ java com/williamfiset/algorithms/graphtheory/treealgorithms/examples/TreeSum + *

Run:
+ * $ ./gradlew run -Palgorithm=graphtheory.treealgorithms.examples.TreeSum * *

Time Complexity: O(n) * diff --git a/com/williamfiset/algorithms/linearalgebra/FreivaldsAlgorithm.java b/src/main/java/com/williamfiset/algorithms/linearalgebra/FreivaldsAlgorithm.java similarity index 100% rename from com/williamfiset/algorithms/linearalgebra/FreivaldsAlgorithm.java rename to src/main/java/com/williamfiset/algorithms/linearalgebra/FreivaldsAlgorithm.java diff --git a/com/williamfiset/algorithms/linearalgebra/GaussianElimination.java b/src/main/java/com/williamfiset/algorithms/linearalgebra/GaussianElimination.java similarity index 100% rename from com/williamfiset/algorithms/linearalgebra/GaussianElimination.java rename to src/main/java/com/williamfiset/algorithms/linearalgebra/GaussianElimination.java diff --git a/com/williamfiset/algorithms/linearalgebra/LinearRecurrenceSolver.java b/src/main/java/com/williamfiset/algorithms/linearalgebra/LinearRecurrenceSolver.java similarity index 100% rename from com/williamfiset/algorithms/linearalgebra/LinearRecurrenceSolver.java rename to src/main/java/com/williamfiset/algorithms/linearalgebra/LinearRecurrenceSolver.java diff --git a/src/main/java/com/williamfiset/algorithms/linearalgebra/MatrixDeterminantLaplaceExpansion.java b/src/main/java/com/williamfiset/algorithms/linearalgebra/MatrixDeterminantLaplaceExpansion.java new file mode 100644 index 000000000..b0c933f13 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/linearalgebra/MatrixDeterminantLaplaceExpansion.java @@ -0,0 +1,218 @@ +/** + * This is an implementation of finding the determinant of an nxn matrix using Laplace/cofactor + * expansion. Although this method is mathematically beautiful, it is computationally intensive and + * not practical for matrices beyond the size of 7-8. + * + *

Time Complexity: ~O((n+2)!) + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.linearalgebra; + +public class MatrixDeterminantLaplaceExpansion { + + // Define a small value of epsilon to compare double values + static final double EPS = 0.00000001; + + public static void main(String[] args) { + + double[][] m = {{6}}; + System.out.println(determinant(m)); // 6 + + m = + new double[][] { + {1, 2}, + {3, 4} + }; + System.out.println(determinant(m)); // -2 + + m = + new double[][] { + {1, -2, 3}, + {4, -5, 6}, + {7, -8, 10} + }; + System.out.println(determinant(m)); // 3 + + m = + new double[][] { + {1, -2, 3, 7}, + {4, -5, 6, 2}, + {7, -8, 10, 3}, + {-8, 10, 3, 2} + }; + System.out.println(determinant(m)); // -252 + + m = + new double[][] { + {1, -2, 3, 7}, + {4, -5, 6, 2}, + {7, -8, 10, 3}, + {-8, 10, 3, 2} + }; + System.out.println(determinant(m)); // -252 + + m = + new double[][] { + {1, -2, 3, 7, 12}, + {4, -5, 6, 2, 4}, + {7, -8, 10, 3, 1}, + {-8, 10, 8, 3, 2}, + {5, 5, 5, 5, 5} + }; + System.out.println(determinant(m)); // -27435 + + m = + new double[][] { + {1, 3, 5, 9}, + {1, 3, 1, 7}, + {4, 3, 9, 7}, + {5, 2, 0, 9}, + }; // determinant(mat1) = -376 , mat(4 * 4) + System.out.println(determinant(m)); + + m = + new double[][] { + {1, 3, 5, 4}, + {2, 3, 1, 3}, + {4, 3, 9, 7}, + {5, 2, 6, 9}, + }; // determinant(mat2) = -152 , mat(4 * 4) + System.out.println(determinant(m)); + m = + new double[][] { + {4, 7, 2, 3}, + {1, 3, 1, 2}, + {2, 5, 3, 4}, + {1, 4, 2, 3}, + }; // determinant(mat3) = -3 , mat(4 * 4) + System.out.println(determinant(m)); + m = + new double[][] { + {1, 0, 0, 0, 0, 2}, + {0, 1, 0, 0, 2, 0}, + {0, 0, 1, 2, 0, 0}, + {0, 0, 2, 1, 0, 0}, + {0, 2, 0, 0, 1, 0}, + {2, 0, 0, 0, 0, 1}, + }; // determinant(mat4) = -27 , mat(6 * 6) + System.out.println(determinant(m)); + m = + new double[][] { + {1, 1, 9, 3, 1, 2, 3}, + {9, 1, 8, 4, 2, 3, 1}, + {3, 2, 7, 2, 9, 5, 5}, + {4, 6, 2, 1, 7, 9, 6}, + {5, 3, 1, 3, 1, 5, 3}, + {2, 7, 9, 5, 0, 1, 2}, + {2, 1, 3, 8, 9, 1, 4} + }; // determinant(mat5) = 66704 mat(7 * 7) + System.out.println(determinant(m)); + m = + new double[][] { + {1, 1, 9, 3, 1, 2, 3, 9}, + {9, 1, 8, 4, 2, 3, 1, 8}, + {3, 2, 7, 2, 9, 5, 5, 7}, + {4, 6, 2, 1, 7, 9, 6, 6}, + {5, 3, 1, 3, 1, 5, 3, 5}, + {2, 7, 9, 5, 0, 1, 2, 4}, + {2, 1, 3, 8, 9, 1, 4, 3}, + {6, 1, 6, 7, 9, 1, 4, 2} + }; // determinant(mat6) = -39240 , mat(8 * 8) + System.out.println(determinant(m)); + m = + new double[][] { + {1, 1, 9, 3, 1, 2, 3, 9, 1}, + {9, 1, 8, 4, 2, 3, 1, 8, 2}, + {3, 2, 7, 2, 9, 5, 5, 7, 3}, + {4, 6, 2, 1, 7, 9, 6, 6, 4}, + {5, 3, 1, 3, 1, 5, 3, 5, 5}, + {2, 7, 9, 5, 0, 1, 2, 4, 6}, + {2, 1, 3, 8, 9, 1, 4, 3, 7}, + {6, 1, 6, 7, 9, 1, 4, 2, 8}, + {9, 8, 7, 4, 3, 3, 4, 2, 9} + }; // determinant(mat7) = 1910870 , mat( 9 * 9) + System.out.println(determinant(m)); + m = + new double[][] { + {1, 2, 4, 8, 6, 3, 4, 8, 0, 2}, + {2, 2, 3, 4, 5, 6, 7, 8, 9, 1}, + {5, 2, 3, 4, 8, 9, 1, 9, 8, 3}, + {1, 1, 1, 6, 4, 2, 5, 9, 8, 7}, + {9, 5, 0, 1, 2, 0, 6, 0, 0, 0}, + {8, 4, 0, 1, 2, 3, 4, 5, 8, 4}, + {7, 3, 3, 6, 7, 8, 9, 1, 7, 3}, + {1, 2, 4, 0, 0, 0, 0, 3, 5, 2}, + {1, 1, 0, 4, 5, 0, 0, 4, 2, 1}, + {1, 0, 0, 0, 9, 0, 0, 1, 1, 6} + }; // determinant(mat0) = 17265530 (1.726553E7) + System.out.println(determinant(m)); + } + + // Given an n*n matrix, this method finds the determinant using Laplace/cofactor expansion. + // Time Complexity: ~O((n+2)!) + public static double determinant(double[][] matrix) { + + final int n = matrix.length; + + // Use closed form for 1x1 determinant + if (n == 1) return matrix[0][0]; + + // Use closed form for 2x2 determinant + if (n == 2) return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]; + + // For 3x3 matrices and up use Laplace/cofactor expansion + return laplace(matrix); + } + + // This method uses cofactor expansion to compute the determinant + // of a matrix. Unfortunately, this method is very slow and uses + // A LOT of memory, hence it is not too practical for large matrices. + private static double laplace(double[][] m) { + + final int n = m.length; + + // Base case is 3x3 determinant + if (n == 3) { + /* + * Used as a temporary variables to make calculation easy + * | a b c | + * | d e f | + * | g h i | + */ + double a = m[0][0], b = m[0][1], c = m[0][2]; + double d = m[1][0], e = m[1][1], f = m[1][2]; + double g = m[2][0], h = m[2][1], i = m[2][2]; + return a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g); + } + int det = 0; + for (int i = 0; i < n; i++) { + double c = m[0][i]; + if (c > EPS) { + int sign = ((i & 1) == 0) ? +1 : -1; + det += sign * m[0][i] * laplace(constructMatrix(m, 0, i)); + } + } + return det; + } + + // Constructs a matrix one dimension smaller than the last by + // excluding the top row and some selected column. This + // method ends up consuming a lot of space we called recursively multiple times + // since it allocates memory for a new matrix. + private static double[][] constructMatrix(double[][] mat, int excludingRow, int excludingCol) { + int n = mat.length; + double[][] newMatrix = new double[n - 1][n - 1]; + int rPtr = -1; + for (int i = 0; i < n; i++) { + if (i == excludingRow) continue; + ++rPtr; + int cPtr = -1; + for (int j = 0; j < n; j++) { + if (j == excludingCol) continue; + newMatrix[rPtr][++cPtr] = mat[i][j]; + } // end of inner loop + } // end of outer loop + return newMatrix; + } // end of createSubMatrix +} diff --git a/com/williamfiset/algorithms/linearalgebra/MatrixInverse.java b/src/main/java/com/williamfiset/algorithms/linearalgebra/MatrixInverse.java similarity index 100% rename from com/williamfiset/algorithms/linearalgebra/MatrixInverse.java rename to src/main/java/com/williamfiset/algorithms/linearalgebra/MatrixInverse.java diff --git a/com/williamfiset/algorithms/linearalgebra/MatrixMultiplication.java b/src/main/java/com/williamfiset/algorithms/linearalgebra/MatrixMultiplication.java similarity index 100% rename from com/williamfiset/algorithms/linearalgebra/MatrixMultiplication.java rename to src/main/java/com/williamfiset/algorithms/linearalgebra/MatrixMultiplication.java diff --git a/com/williamfiset/algorithms/linearalgebra/MatrixPower.java b/src/main/java/com/williamfiset/algorithms/linearalgebra/MatrixPower.java similarity index 100% rename from com/williamfiset/algorithms/linearalgebra/MatrixPower.java rename to src/main/java/com/williamfiset/algorithms/linearalgebra/MatrixPower.java diff --git a/com/williamfiset/algorithms/linearalgebra/ModularLinearAlgebra.java b/src/main/java/com/williamfiset/algorithms/linearalgebra/ModularLinearAlgebra.java similarity index 100% rename from com/williamfiset/algorithms/linearalgebra/ModularLinearAlgebra.java rename to src/main/java/com/williamfiset/algorithms/linearalgebra/ModularLinearAlgebra.java diff --git a/com/williamfiset/algorithms/linearalgebra/RotateSquareMatrixInplace.java b/src/main/java/com/williamfiset/algorithms/linearalgebra/RotateSquareMatrixInplace.java similarity index 100% rename from com/williamfiset/algorithms/linearalgebra/RotateSquareMatrixInplace.java rename to src/main/java/com/williamfiset/algorithms/linearalgebra/RotateSquareMatrixInplace.java diff --git a/com/williamfiset/algorithms/linearalgebra/Simplex.java b/src/main/java/com/williamfiset/algorithms/linearalgebra/Simplex.java similarity index 100% rename from com/williamfiset/algorithms/linearalgebra/Simplex.java rename to src/main/java/com/williamfiset/algorithms/linearalgebra/Simplex.java diff --git a/com/williamfiset/algorithms/math/ChineseRemainderTheorem.java b/src/main/java/com/williamfiset/algorithms/math/ChineseRemainderTheorem.java similarity index 100% rename from com/williamfiset/algorithms/math/ChineseRemainderTheorem.java rename to src/main/java/com/williamfiset/algorithms/math/ChineseRemainderTheorem.java diff --git a/com/williamfiset/algorithms/math/CompressedPrimeSieve.java b/src/main/java/com/williamfiset/algorithms/math/CompressedPrimeSieve.java similarity index 93% rename from com/williamfiset/algorithms/math/CompressedPrimeSieve.java rename to src/main/java/com/williamfiset/algorithms/math/CompressedPrimeSieve.java index 3d5abae97..ef542cf5b 100644 --- a/com/williamfiset/algorithms/math/CompressedPrimeSieve.java +++ b/src/main/java/com/williamfiset/algorithms/math/CompressedPrimeSieve.java @@ -7,9 +7,10 @@ * *

Time Complexity: ~O(nloglogn) * - *

Compile: javac com/williamfiset/algorithms/math/CompressedPrimeSieve.java + *

Compile: javac -d src/main/java + * src/main/java/com/williamfiset/algorithms/math/CompressedPrimeSieve.java * - *

Run: java com/williamfiset/algorithms/math/CompressedPrimeSieve + *

Run: java -cp src/main/java com/williamfiset/algorithms/math/CompressedPrimeSieve * * @author William Fiset, william.alexandre.fiset@gmail.com */ diff --git a/com/williamfiset/algorithms/math/EulerTotientFunction.java b/src/main/java/com/williamfiset/algorithms/math/EulerTotientFunction.java similarity index 100% rename from com/williamfiset/algorithms/math/EulerTotientFunction.java rename to src/main/java/com/williamfiset/algorithms/math/EulerTotientFunction.java diff --git a/com/williamfiset/algorithms/math/EulerTotientFunctionWithSieve.java b/src/main/java/com/williamfiset/algorithms/math/EulerTotientFunctionWithSieve.java similarity index 100% rename from com/williamfiset/algorithms/math/EulerTotientFunctionWithSieve.java rename to src/main/java/com/williamfiset/algorithms/math/EulerTotientFunctionWithSieve.java diff --git a/com/williamfiset/algorithms/math/ExtendedEuclideanAlgorithm.java b/src/main/java/com/williamfiset/algorithms/math/ExtendedEuclideanAlgorithm.java similarity index 100% rename from com/williamfiset/algorithms/math/ExtendedEuclideanAlgorithm.java rename to src/main/java/com/williamfiset/algorithms/math/ExtendedEuclideanAlgorithm.java diff --git a/com/williamfiset/algorithms/math/FastFourierTransform.java b/src/main/java/com/williamfiset/algorithms/math/FastFourierTransform.java similarity index 100% rename from com/williamfiset/algorithms/math/FastFourierTransform.java rename to src/main/java/com/williamfiset/algorithms/math/FastFourierTransform.java diff --git a/com/williamfiset/algorithms/math/FastFourierTransformComplexNumbers.java b/src/main/java/com/williamfiset/algorithms/math/FastFourierTransformComplexNumbers.java similarity index 100% rename from com/williamfiset/algorithms/math/FastFourierTransformComplexNumbers.java rename to src/main/java/com/williamfiset/algorithms/math/FastFourierTransformComplexNumbers.java diff --git a/com/williamfiset/algorithms/math/GCD.java b/src/main/java/com/williamfiset/algorithms/math/GCD.java similarity index 100% rename from com/williamfiset/algorithms/math/GCD.java rename to src/main/java/com/williamfiset/algorithms/math/GCD.java diff --git a/com/williamfiset/algorithms/math/IsPrime.java b/src/main/java/com/williamfiset/algorithms/math/IsPrime.java similarity index 100% rename from com/williamfiset/algorithms/math/IsPrime.java rename to src/main/java/com/williamfiset/algorithms/math/IsPrime.java diff --git a/com/williamfiset/algorithms/math/LCM.java b/src/main/java/com/williamfiset/algorithms/math/LCM.java similarity index 100% rename from com/williamfiset/algorithms/math/LCM.java rename to src/main/java/com/williamfiset/algorithms/math/LCM.java diff --git a/com/williamfiset/algorithms/math/ModPow.java b/src/main/java/com/williamfiset/algorithms/math/ModPow.java similarity index 87% rename from com/williamfiset/algorithms/math/ModPow.java rename to src/main/java/com/williamfiset/algorithms/math/ModPow.java index 77451811d..94994396d 100644 --- a/com/williamfiset/algorithms/math/ModPow.java +++ b/src/main/java/com/williamfiset/algorithms/math/ModPow.java @@ -11,6 +11,8 @@ */ package com.williamfiset.algorithms.math; +import java.math.BigInteger; + public class ModPow { // The values placed into the modPow function cannot be greater @@ -87,12 +89,12 @@ public static long modPow(long a, long n, long mod) { // Example usage public static void main(String[] args) { - java.math.BigInteger A, N, M, r1; + BigInteger A, N, M, r1; long a, n, m, r2; - A = new java.math.BigInteger("3"); - N = new java.math.BigInteger("4"); - M = new java.math.BigInteger("1000000"); + A = BigInteger.valueOf(3); + N = BigInteger.valueOf(4); + M = BigInteger.valueOf(1000000); a = A.longValue(); n = N.longValue(); m = M.longValue(); @@ -102,9 +104,9 @@ public static void main(String[] args) { r2 = modPow(a, n, m); // 81 System.out.println(r1 + " " + r2); - A = new java.math.BigInteger("-45"); - N = new java.math.BigInteger("12345"); - M = new java.math.BigInteger("987654321"); + A = BigInteger.valueOf(-45); + N = BigInteger.valueOf(12345); + M = BigInteger.valueOf(987654321); a = A.longValue(); n = N.longValue(); m = M.longValue(); @@ -114,9 +116,9 @@ public static void main(String[] args) { r2 = modPow(a, n, m); // 323182557 System.out.println(r1 + " " + r2); - A = new java.math.BigInteger("6"); - N = new java.math.BigInteger("-66"); - M = new java.math.BigInteger("101"); + A = BigInteger.valueOf(6); + N = BigInteger.valueOf(-66); + M = BigInteger.valueOf(101); a = A.longValue(); n = N.longValue(); m = M.longValue(); @@ -126,9 +128,9 @@ public static void main(String[] args) { r2 = modPow(a, n, m); // 84 System.out.println(r1 + " " + r2); - A = new java.math.BigInteger("-5"); - N = new java.math.BigInteger("-7"); - M = new java.math.BigInteger("1009"); + A = BigInteger.valueOf(-5); + N = BigInteger.valueOf(-7); + M = BigInteger.valueOf(1009); a = A.longValue(); n = N.longValue(); m = M.longValue(); @@ -139,9 +141,9 @@ public static void main(String[] args) { System.out.println(r1 + " " + r2); for (int i = 0; i < 1000; i++) { - A = new java.math.BigInteger(a + ""); - N = new java.math.BigInteger(n + ""); - M = new java.math.BigInteger(m + ""); + A = BigInteger.valueOf(a); + N = BigInteger.valueOf(n); + M = BigInteger.valueOf(m); a = Math.random() < 0.5 ? randLong(MAX) : -randLong(MAX); n = randLong(); m = randLong(MAX); diff --git a/com/williamfiset/algorithms/math/ModularInverse.java b/src/main/java/com/williamfiset/algorithms/math/ModularInverse.java similarity index 100% rename from com/williamfiset/algorithms/math/ModularInverse.java rename to src/main/java/com/williamfiset/algorithms/math/ModularInverse.java diff --git a/src/main/java/com/williamfiset/algorithms/math/NChooseRModPrime.java b/src/main/java/com/williamfiset/algorithms/math/NChooseRModPrime.java new file mode 100644 index 000000000..a3574edac --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/math/NChooseRModPrime.java @@ -0,0 +1,63 @@ +/** + * @author Rohit Mazumder, mazumder.rohit7@gmai.com + */ +package com.williamfiset.algorithms.math; + +import java.math.BigInteger; + +public class NChooseRModPrime { + /** + * Calculate the value of C(N, R) % P using Fermat's Little Theorem. + * + * @param N + * @param R + * @param P + * @return The value of N choose R Modulus P + */ + public static long compute(int N, int R, int P) { + if (R == 0) return 1; + + long[] factorial = new long[N + 1]; + factorial[0] = 1; + + for (int i = 1; i <= N; i++) { + factorial[i] = factorial[i - 1] * i % P; + } + + return (factorial[N] + * ModularInverse.modInv(factorial[R], P) + % P + * ModularInverse.modInv(factorial[N - R], P) + % P) + % P; + } + + // Method for testing output against the output generated by the compute(int,int,int) function + private static String bigIntegerNChooseRModP(int N, int R, int P) { + if (R == 0) return "1"; + BigInteger num = BigInteger.ONE; + BigInteger den = BigInteger.ONE; + while (R > 0) { + num = num.multiply(BigInteger.valueOf(N)); + den = den.multiply(BigInteger.valueOf(R)); + BigInteger gcd = num.gcd(den); + num = num.divide(gcd); + den = den.divide(gcd); + N--; + R--; + } + num = num.divide(den); + num = num.mod(BigInteger.valueOf(P)); + return num.toString(); + } + + public static void main(String args[]) { + int N = 500; + int R = 250; + int P = 1000000007; + int expected = Integer.parseInt(bigIntegerNChooseRModP(N, R, P)); + long actual = compute(N, R, P); + System.out.println(expected); // 515561345 + System.out.println(actual); // 515561345 + } +} diff --git a/com/williamfiset/algorithms/math/PrimeFactorization.java b/src/main/java/com/williamfiset/algorithms/math/PrimeFactorization.java similarity index 100% rename from com/williamfiset/algorithms/math/PrimeFactorization.java rename to src/main/java/com/williamfiset/algorithms/math/PrimeFactorization.java diff --git a/com/williamfiset/algorithms/math/RabinMillerPrimalityTest.py b/src/main/java/com/williamfiset/algorithms/math/RabinMillerPrimalityTest.py similarity index 100% rename from com/williamfiset/algorithms/math/RabinMillerPrimalityTest.py rename to src/main/java/com/williamfiset/algorithms/math/RabinMillerPrimalityTest.py diff --git a/com/williamfiset/algorithms/math/RelativelyPrime.java b/src/main/java/com/williamfiset/algorithms/math/RelativelyPrime.java similarity index 100% rename from com/williamfiset/algorithms/math/RelativelyPrime.java rename to src/main/java/com/williamfiset/algorithms/math/RelativelyPrime.java diff --git a/com/williamfiset/algorithms/math/SieveOfEratosthenes.java b/src/main/java/com/williamfiset/algorithms/math/SieveOfEratosthenes.java similarity index 100% rename from com/williamfiset/algorithms/math/SieveOfEratosthenes.java rename to src/main/java/com/williamfiset/algorithms/math/SieveOfEratosthenes.java diff --git a/com/williamfiset/algorithms/other/BitManipulations.java b/src/main/java/com/williamfiset/algorithms/other/BitManipulations.java similarity index 100% rename from com/williamfiset/algorithms/other/BitManipulations.java rename to src/main/java/com/williamfiset/algorithms/other/BitManipulations.java diff --git a/com/williamfiset/algorithms/other/Combinations.java b/src/main/java/com/williamfiset/algorithms/other/Combinations.java similarity index 100% rename from com/williamfiset/algorithms/other/Combinations.java rename to src/main/java/com/williamfiset/algorithms/other/Combinations.java diff --git a/com/williamfiset/algorithms/other/CombinationsWithRepetition.java b/src/main/java/com/williamfiset/algorithms/other/CombinationsWithRepetition.java similarity index 100% rename from com/williamfiset/algorithms/other/CombinationsWithRepetition.java rename to src/main/java/com/williamfiset/algorithms/other/CombinationsWithRepetition.java diff --git a/src/main/java/com/williamfiset/algorithms/other/LazyRangeAdder.java b/src/main/java/com/williamfiset/algorithms/other/LazyRangeAdder.java new file mode 100644 index 000000000..33eaacfab --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/other/LazyRangeAdder.java @@ -0,0 +1,71 @@ +/** + * The LazyRangerAdder is a handy class for performing addition range updates of constant values on + * an array. This range adder is especially useful for offline algorithms which know all range + * updates ahead of time. + * + *

Time complexity to update O(1) but time complexity to finalize all additions is O(n) + * + * @author Atharva Thorve, aaathorve@gmail.com + */ +package com.williamfiset.algorithms.other; + +public class LazyRangeAdder { + + // The number of elements in the input array. + private int n; + + // The original input array + private int[] array; + + // The difference array with the deltas between values, size n+1 + private int[] differenceArray; + + // Initialize an instance of a LazyRangeAdder on some input values + public LazyRangeAdder(int[] array) { + this.array = array; + this.n = array.length; + + differenceArray = new int[n + 1]; + differenceArray[0] = array[0]; + for (int i = 1; i < n; i++) { + differenceArray[i] = array[i] - array[i - 1]; + } + } + + // Add `x` to the range [l, r] inclusive + public void add(int l, int r, int x) { + differenceArray[l] += x; + differenceArray[r + 1] -= x; + } + + // IMPORTANT: Make certain to call this method once all the additions + // have been made with add(l, r, x) + public void done() { + for (int i = 0; i < n; i++) { + if (i == 0) { + array[i] = differenceArray[i]; + } else { + array[i] = differenceArray[i] + array[i - 1]; + } + } + } + + public static void main(String[] args) { + // Array to be updated + int[] array = {10, 4, 6, 13, 8, 15, 17, 22}; + LazyRangeAdder lazyRangeAdder = new LazyRangeAdder(array); + + // After below add(l, r, x), the + // elements should become [10, 14, 16, 23, 18, 15, 17, 22] + lazyRangeAdder.add(1, 4, 10); + lazyRangeAdder.done(); + System.out.println(java.util.Arrays.toString(array)); + + // After below add(l, r, x), the + // elements should become [22, 26, 28, 30, 25, 22, 24, 34] + lazyRangeAdder.add(3, 6, -5); + lazyRangeAdder.add(0, 7, 12); + lazyRangeAdder.done(); + System.out.println(java.util.Arrays.toString(array)); + } +} diff --git a/com/williamfiset/algorithms/other/Permutations.java b/src/main/java/com/williamfiset/algorithms/other/Permutations.java similarity index 100% rename from com/williamfiset/algorithms/other/Permutations.java rename to src/main/java/com/williamfiset/algorithms/other/Permutations.java diff --git a/com/williamfiset/algorithms/other/PowerSet.java b/src/main/java/com/williamfiset/algorithms/other/PowerSet.java similarity index 100% rename from com/williamfiset/algorithms/other/PowerSet.java rename to src/main/java/com/williamfiset/algorithms/other/PowerSet.java diff --git a/com/williamfiset/algorithms/other/SlidingWindowMaximum.java b/src/main/java/com/williamfiset/algorithms/other/SlidingWindowMaximum.java similarity index 100% rename from com/williamfiset/algorithms/other/SlidingWindowMaximum.java rename to src/main/java/com/williamfiset/algorithms/other/SlidingWindowMaximum.java diff --git a/com/williamfiset/algorithms/other/SquareRootDecomposition.java b/src/main/java/com/williamfiset/algorithms/other/SquareRootDecomposition.java similarity index 100% rename from com/williamfiset/algorithms/other/SquareRootDecomposition.java rename to src/main/java/com/williamfiset/algorithms/other/SquareRootDecomposition.java diff --git a/com/williamfiset/algorithms/other/UniqueCombinations.java b/src/main/java/com/williamfiset/algorithms/other/UniqueCombinations.java similarity index 100% rename from com/williamfiset/algorithms/other/UniqueCombinations.java rename to src/main/java/com/williamfiset/algorithms/other/UniqueCombinations.java diff --git a/com/williamfiset/algorithms/search/BinarySearch.java b/src/main/java/com/williamfiset/algorithms/search/BinarySearch.java similarity index 100% rename from com/williamfiset/algorithms/search/BinarySearch.java rename to src/main/java/com/williamfiset/algorithms/search/BinarySearch.java diff --git a/com/williamfiset/algorithms/search/InterpolationSearch.java b/src/main/java/com/williamfiset/algorithms/search/InterpolationSearch.java similarity index 100% rename from com/williamfiset/algorithms/search/InterpolationSearch.java rename to src/main/java/com/williamfiset/algorithms/search/InterpolationSearch.java diff --git a/com/williamfiset/algorithms/search/TernarySearch.java b/src/main/java/com/williamfiset/algorithms/search/TernarySearch.java similarity index 100% rename from com/williamfiset/algorithms/search/TernarySearch.java rename to src/main/java/com/williamfiset/algorithms/search/TernarySearch.java diff --git a/com/williamfiset/algorithms/search/TernarySearchDiscrete.java b/src/main/java/com/williamfiset/algorithms/search/TernarySearchDiscrete.java similarity index 99% rename from com/williamfiset/algorithms/search/TernarySearchDiscrete.java rename to src/main/java/com/williamfiset/algorithms/search/TernarySearchDiscrete.java index 6cb371ea7..ffd179b53 100644 --- a/com/williamfiset/algorithms/search/TernarySearchDiscrete.java +++ b/src/main/java/com/williamfiset/algorithms/search/TernarySearchDiscrete.java @@ -41,7 +41,7 @@ static double discreteTernarySearch(int lo, int hi) { } else if (res1 > res2) lo = mid1; else hi = mid2; } - return lo; + return f(lo); } public static void main(String[] args) { diff --git a/src/main/java/com/williamfiset/algorithms/sorting/BubbleSort.java b/src/main/java/com/williamfiset/algorithms/sorting/BubbleSort.java new file mode 100644 index 000000000..b2c512945 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/sorting/BubbleSort.java @@ -0,0 +1,54 @@ +/** + * Bubble sort implementation + * + *

Run with: + * + *

$ ./gradlew run -Palgorithm=sorting.BubbleSort + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.sorting; + +public class BubbleSort implements InplaceSort { + + @Override + public void sort(int[] values) { + BubbleSort.bubbleSort(values); + } + + // Sort the array using bubble sort. The idea behind + // bubble sort is to look for adjacent indexes which + // are out of place and interchange their elements + // until the entire array is sorted. + private static void bubbleSort(int[] ar) { + if (ar == null) { + return; + } + + boolean sorted; + do { + sorted = true; + for (int i = 1; i < ar.length; i++) { + if (ar[i] < ar[i - 1]) { + swap(ar, i - 1, i); + sorted = false; + } + } + } while (!sorted); + } + + private static void swap(int[] ar, int i, int j) { + int tmp = ar[i]; + ar[i] = ar[j]; + ar[j] = tmp; + } + + public static void main(String[] args) { + int[] array = {10, 4, 6, 8, 13, 2, 3}; + BubbleSort sorter = new BubbleSort(); + sorter.sort(array); + // Prints: + // [2, 3, 4, 6, 8, 10, 13] + System.out.println(java.util.Arrays.toString(array)); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/sorting/BucketSort.java b/src/main/java/com/williamfiset/algorithms/sorting/BucketSort.java new file mode 100644 index 000000000..5b6f365e4 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/sorting/BucketSort.java @@ -0,0 +1,72 @@ +/** + * Bucket sort implementation + * + *

Run with: + * + *

$ ./gradlew run -Palgorithm=sorting.BucketSort + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.sorting; + +import java.util.*; + +public class BucketSort implements InplaceSort { + + @Override + public void sort(int[] values) { + int minValue = Integer.MAX_VALUE; + int maxValue = Integer.MIN_VALUE; + for (int i = 0; i < values.length; i++) { + if (values[i] < minValue) minValue = values[i]; + if (values[i] > maxValue) maxValue = values[i]; + } + BucketSort.bucketSort(values, minValue, maxValue); + } + + // Performs a bucket sort of an array in which all the elements are + // bounded in the range [minValue, maxValue]. For bucket sort to give linear + // performance the elements need to be uniformly distributed + private static void bucketSort(int[] ar, int minValue, int maxValue) { + if (ar == null || ar.length == 0 || minValue == maxValue) return; + + // N is number elements and M is the range of values + final int N = ar.length, M = maxValue - minValue + 1, numBuckets = M / N + 1; + List> buckets = new ArrayList<>(numBuckets); + for (int i = 0; i < numBuckets; i++) buckets.add(new ArrayList<>()); + + // Place each element in a bucket + for (int i = 0; i < N; i++) { + int bi = (ar[i] - minValue) / M; + List bucket = buckets.get(bi); + bucket.add(ar[i]); + } + + // Sort buckets and stitch together answer + for (int bi = 0, j = 0; bi < numBuckets; bi++) { + List bucket = buckets.get(bi); + if (bucket != null) { + Collections.sort(bucket); + for (int k = 0; k < bucket.size(); k++) { + ar[j++] = bucket.get(k); + } + } + } + } + + public static void main(String[] args) { + BucketSort sorter = new BucketSort(); + + int[] array = {10, 4, 6, 8, 13, 2, 3}; + sorter.sort(array); + // Prints: + // [2, 3, 4, 6, 8, 10, 13] + System.out.println(java.util.Arrays.toString(array)); + + array = new int[] {10, 10, 10, 10, 10}; + sorter.sort(array); + // Prints: + // [10, 10, 10, 10, 10] + System.out.println(java.util.Arrays.toString(array)); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/sorting/CountingSort.java b/src/main/java/com/williamfiset/algorithms/sorting/CountingSort.java new file mode 100644 index 000000000..bf8b8153a --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/sorting/CountingSort.java @@ -0,0 +1,44 @@ +/** + * An implementation of counting sort! + * + *

Run with: + * + *

$ ./gradlew run -Palgorithm=sorting.CountingSort + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.sorting; + +public class CountingSort implements InplaceSort { + + @Override + public void sort(int[] values) { + int minValue = Integer.MAX_VALUE; + int maxValue = Integer.MIN_VALUE; + for (int i = 0; i < values.length; i++) { + if (values[i] < minValue) minValue = values[i]; + if (values[i] > maxValue) maxValue = values[i]; + } + CountingSort.countingSort(values, minValue, maxValue); + } + + // Sorts values in the range of [minVal, maxVal] in O(n+maxVal-maxVal) + private static void countingSort(int[] ar, int minVal, int maxVal) { + int sz = maxVal - minVal + 1; + int[] b = new int[sz]; + for (int i = 0; i < ar.length; i++) b[ar[i] - minVal]++; + for (int i = 0, k = 0; i < sz; i++) { + while (b[i]-- > 0) ar[k++] = i + minVal; + } + } + + public static void main(String[] args) { + CountingSort sorter = new CountingSort(); + int[] nums = {+4, -10, +0, +6, +1, -5, -5, +1, +1, -2, 0, +6, +8, -7, +10}; + sorter.sort(nums); + + // Prints: + // [-10, -7, -5, -5, -2, 0, 0, 1, 1, 1, 4, 6, 6, 8, 10] + System.out.println(java.util.Arrays.toString(nums)); + } +} diff --git a/com/williamfiset/algorithms/sorting/Heapsort.java b/src/main/java/com/williamfiset/algorithms/sorting/Heapsort.java similarity index 55% rename from com/williamfiset/algorithms/sorting/Heapsort.java rename to src/main/java/com/williamfiset/algorithms/sorting/Heapsort.java index 029ab42bb..60bd89858 100644 --- a/com/williamfiset/algorithms/sorting/Heapsort.java +++ b/src/main/java/com/williamfiset/algorithms/sorting/Heapsort.java @@ -1,21 +1,32 @@ /** * Implementation of heapsort * + *

Run with: + * + *

$ ./gradlew run -Palgorithm=sorting.Heapsort + * * @author William Fiset, william.alexandre.fiset@gmail.com */ package com.williamfiset.algorithms.sorting; import java.util.*; -public class Heapsort { +public class Heapsort implements InplaceSort { - public static void heapsort(int[] ar) { + @Override + public void sort(int[] values) { + Heapsort.heapsort(values); + } + private static void heapsort(int[] ar) { if (ar == null) return; int n = ar.length; - // Heapify, converts array into binary heap, O(n) - for (int i = Math.max(0, (n / 2) - 1); i >= 0; i--) sink(ar, n, i); + // Heapify, converts array into binary heap O(n), see: + // http://www.cs.umd.edu/~meesh/351/mount/lectures/lect14-heapsort-analysis-part.pdf + for (int i = Math.max(0, (n / 2) - 1); i >= 0; i--) { + sink(ar, n, i); + } // Sorting bit for (int i = n - 1; i >= 0; i--) { @@ -25,9 +36,7 @@ public static void heapsort(int[] ar) { } private static void sink(int[] ar, int n, int i) { - while (true) { - int left = 2 * i + 1; // Left node int right = 2 * i + 2; // Right node int largest = i; @@ -55,38 +64,11 @@ private static void swap(int[] ar, int i, int j) { /* TESTING */ public static void main(String[] args) { - + Heapsort sorter = new Heapsort(); int[] array = {10, 4, 6, 4, 8, -13, 2, 3}; - heapsort(array); + sorter.sort(array); + // Prints: + // [-13, 2, 3, 4, 4, 6, 8, 10] System.out.println(java.util.Arrays.toString(array)); - - // TODO(williamfiset): move to javatests/... - runTests(); - } - - static Random RANDOM = new Random(); - - public static void runTests() { - final int NUM_TESTS = 1000; - for (int i = 1; i <= NUM_TESTS; i++) { - - int[] array = new int[i]; - // for(int j = 0; j < i; j++) array[j] = randInt(-1000000, +1000000); - for (int j = 0; j < i; j++) array[j] = randInt(-10, +10); - int[] arrayCopy = array.clone(); - - heapsort(array); - java.util.Arrays.sort(arrayCopy); - - if (!java.util.Arrays.equals(array, arrayCopy)) { - System.out.println(Arrays.toString(array)); - System.out.println("ERROR"); - break; - } - } - } - - static int randInt(int min, int max) { - return RANDOM.nextInt((max - min) + 1) + min; } } diff --git a/src/main/java/com/williamfiset/algorithms/sorting/InplaceSort.java b/src/main/java/com/williamfiset/algorithms/sorting/InplaceSort.java new file mode 100644 index 000000000..07dd21058 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/sorting/InplaceSort.java @@ -0,0 +1,6 @@ +package com.williamfiset.algorithms.sorting; + +// A shared interface amongst sorting algorithms which +public interface InplaceSort { + public void sort(int[] values); +} diff --git a/src/main/java/com/williamfiset/algorithms/sorting/InsertionSort.java b/src/main/java/com/williamfiset/algorithms/sorting/InsertionSort.java new file mode 100644 index 000000000..6c4b17535 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/sorting/InsertionSort.java @@ -0,0 +1,49 @@ +/** + * Insertion sort implementation + * + *

Run with: + * + *

$ ./gradlew run -Palgorithm=sorting.InsertionSort + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.sorting; + +public class InsertionSort implements InplaceSort { + + @Override + public void sort(int[] values) { + InsertionSort.insertionSort(values); + } + + // Sort the given array using insertion sort. The idea behind + // insertion sort is that at the array is already sorted from + // [0, i] and you want to add the element at position i+1, so + // you 'insert' it at the appropriate location. + private static void insertionSort(int[] ar) { + if (ar == null) { + return; + } + + for (int i = 1; i < ar.length; i++) { + for (int j = i; j > 0 && ar[j] < ar[j - 1]; j--) { + swap(ar, j - 1, j); + } + } + } + + private static void swap(int[] ar, int i, int j) { + int tmp = ar[i]; + ar[i] = ar[j]; + ar[j] = tmp; + } + + public static void main(String[] args) { + InplaceSort sorter = new InsertionSort(); + int[] array = {10, 4, 6, 8, 13, 2, 3}; + sorter.sort(array); + // Prints: + // [2, 3, 4, 6, 8, 10, 13] + System.out.println(java.util.Arrays.toString(array)); + } +} diff --git a/com/williamfiset/algorithms/sorting/Mergesort.java b/src/main/java/com/williamfiset/algorithms/sorting/MergeSort.java similarity index 63% rename from com/williamfiset/algorithms/sorting/Mergesort.java rename to src/main/java/com/williamfiset/algorithms/sorting/MergeSort.java index 14ec962a2..5c290774e 100644 --- a/com/williamfiset/algorithms/sorting/Mergesort.java +++ b/src/main/java/com/williamfiset/algorithms/sorting/MergeSort.java @@ -1,20 +1,32 @@ /** * Mergesort implementation * + *

Run with: + * + *

$ ./gradlew run -Palgorithm=sorting.Mergesort + * * @author William Fiset, william.alexandre.fiset@gmail.com */ package com.williamfiset.algorithms.sorting; import java.util.Arrays; -import java.util.Random; -public class Mergesort { +// Mergesort implements InplaceSort for ease of testings, but in reality +// it is not really a good fit for an inplace sorting algorithm. +public class MergeSort implements InplaceSort { - public static int[] mergesort(int[] ar) { + @Override + public void sort(int[] values) { + int[] sortedValues = MergeSort.mergesort(values); + for (int i = 0; i < values.length; i++) { + values[i] = sortedValues[i]; + } + } + public static int[] mergesort(int[] ar) { // Base case is when a single element (which is already sorted) int n = ar.length; - if (n == 1) return ar; + if (n <= 1) return ar; // Split array into two parts and recursively sort them int[] left = mergesort(Arrays.copyOfRange(ar, 0, n / 2)); @@ -26,7 +38,6 @@ public static int[] mergesort(int[] ar) { // Merge two sorted arrays into a larger sorted array private static int[] merge(int[] ar1, int[] ar2) { - int n1 = ar1.length, n2 = ar2.length; int n = n1 + n2, i1 = 0, i2 = 0; int[] ar = new int[n]; @@ -48,33 +59,10 @@ private static int[] merge(int[] ar1, int[] ar2) { } public static void main(String[] args) { - int[] array = {10, 4, 6, 4, 8, -13, 2, 3}; - array = mergesort(array); + array = MergeSort.mergesort(array); + // Prints: + // [-13, 2, 3, 4, 4, 6, 8, 10] System.out.println(java.util.Arrays.toString(array)); - - // TODO(williamfiset): move to javatests/... - runTests(); - } - - static Random RANDOM = new Random(); - - public static void runTests() { - final int NUM_TESTS = 1000; - for (int i = 1; i <= NUM_TESTS; i++) { - - int[] array = new int[i]; - for (int j = 0; j < i; j++) array[j] = randInt(-1000000, +1000000); - int[] arrayCopy = array.clone(); - - array = mergesort(array); - java.util.Arrays.sort(arrayCopy); - - if (!java.util.Arrays.equals(array, arrayCopy)) System.out.println("ERROR"); - } - } - - static int randInt(int min, int max) { - return RANDOM.nextInt((max - min) + 1) + min; } } diff --git a/src/main/java/com/williamfiset/algorithms/sorting/QuickSelect.java b/src/main/java/com/williamfiset/algorithms/sorting/QuickSelect.java new file mode 100644 index 000000000..e66660e27 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/sorting/QuickSelect.java @@ -0,0 +1,57 @@ +package com.williamfiset.algorithms.sorting; + +public class QuickSelect { + + public Integer quickSelect(int[] ar, int k) { + if (ar == null) return null; + if (k > ar.length) return null; + if (k < 1) return null; + return quickSelect(ar, k, 0, ar.length - 1); + } + + // Sort interval [lo, hi] inplace recursively, returns value when splitPoint == k - 1 + private static Integer quickSelect(int[] ar, int k, int lo, int hi) { + int index = k - 1; + if (lo < hi) { + int splitPoint = partition(ar, lo, hi); + if (splitPoint == index) { + return ar[splitPoint]; + } else if (splitPoint > index) { + return quickSelect(ar, k, lo, splitPoint); + } + return quickSelect(ar, k, splitPoint + 1, hi); + } + return ar[lo]; + } + + // Performs Hoare partition algorithm for quick select, taken from QuickSelect implementation + private static int partition(int[] ar, int lo, int hi) { + int pivot = ar[lo]; + int i = lo - 1, j = hi + 1; + while (true) { + do { + i++; + } while (ar[i] < pivot); + do { + j--; + } while (ar[j] > pivot); + if (i < j) swap(ar, i, j); + else return j; + } + } + + // Swap two elements + private static void swap(int[] ar, int i, int j) { + int tmp = ar[i]; + ar[i] = ar[j]; + ar[j] = tmp; + } + + public static void main(String[] args) { + QuickSelect quickSelect = new QuickSelect(); + int[] array = {-10, 4, 6, 4, 8, -13, 1, 3}; + int kthLargestElement = quickSelect.quickSelect(array, 3); + // Prints: 1 + System.out.println(kthLargestElement); + } +} diff --git a/com/williamfiset/algorithms/sorting/Quicksort.java b/src/main/java/com/williamfiset/algorithms/sorting/QuickSort.java similarity index 63% rename from com/williamfiset/algorithms/sorting/Quicksort.java rename to src/main/java/com/williamfiset/algorithms/sorting/QuickSort.java index 0a72ef36d..c799feb1e 100644 --- a/com/williamfiset/algorithms/sorting/Quicksort.java +++ b/src/main/java/com/williamfiset/algorithms/sorting/QuickSort.java @@ -1,13 +1,20 @@ /** * Quicksort implementation using Hoare partitioning * + *

Run with: + * + *

$ ./gradlew run -Palgorithm=sorting.QuickSort + * * @author William Fiset, william.alexandre.fiset@gmail.com */ package com.williamfiset.algorithms.sorting; -import java.util.Random; +public class QuickSort implements InplaceSort { -public class Quicksort { + @Override + public void sort(int[] values) { + QuickSort.quicksort(values); + } public static void quicksort(int[] ar) { if (ar == null) return; @@ -47,35 +54,11 @@ private static void swap(int[] ar, int i, int j) { } public static void main(String[] args) { - + InplaceSort sorter = new QuickSort(); int[] array = {10, 4, 6, 4, 8, -13, 2, 3}; - quicksort(array); + sorter.sort(array); + // Prints: + // [-13, 2, 3, 4, 4, 6, 8, 10] System.out.println(java.util.Arrays.toString(array)); - - // TODO(williamfiset): Move to test file - runTests(); - } - - /* TESTING BELOW */ - - static Random RANDOM = new Random(); - - public static void runTests() { - final int NUM_TESTS = 1000; - for (int i = 1; i <= NUM_TESTS; i++) { - - int[] array = new int[i]; - for (int j = 0; j < i; j++) array[j] = randInt(-1000000, +1000000); - int[] arrayCopy = array.clone(); - - quicksort(array); - java.util.Arrays.sort(arrayCopy); - - if (!java.util.Arrays.equals(array, arrayCopy)) System.out.println("ERROR"); - } - } - - static int randInt(int min, int max) { - return RANDOM.nextInt((max - min) + 1) + min; } } diff --git a/src/main/java/com/williamfiset/algorithms/sorting/QuickSort3.java b/src/main/java/com/williamfiset/algorithms/sorting/QuickSort3.java new file mode 100644 index 000000000..0006d5d38 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/sorting/QuickSort3.java @@ -0,0 +1,93 @@ +/** + * QuickSort3 or Dutch National Flag algorithm is similar to the QuickSort algorithm but has an + * improved partitioning algorithm. QuickSort is quite slow in the case where very few unique + * elements exist in the array so the QuickSort3 algorithm is used at that time. + * + *

Run with: + * + *

$ ./gradlew run -Palgorithm=sorting.QuickSort3 + * + * @author Atharva Thorve, aaathorve@gmail.com + */ +package com.williamfiset.algorithms.sorting; + +import java.util.Random; + +public class QuickSort3 implements InplaceSort { + private static Random random = new Random(); + + @Override + public void sort(int[] values) { + QuickSort3.quickSort3(values); + } + + public static void quickSort3(int[] ar) { + if (ar == null) return; + QuickSort3.randomizedQuickSort(ar, 0, ar.length - 1); + } + + // partiton array in such a way that all the elements whose value is equal to + // pivot are grouped together + private static int[] partition3(int[] a, int l, int r) { + int j, k; + if (r - l <= 1) { + if (a[r] < a[l]) { + swap(a, l, r); + } + j = l; + k = r; + int[] m = {j, k}; + return m; + } + int mid = l; + int p = a[r]; + while (mid <= r) { + if (a[mid] < p) { + swap(a, l, mid); + l++; + mid++; + } else if (a[mid] == p) { + mid++; + } else { + swap(a, mid, r); + r--; + } + } + j = l - 1; + k = mid; + int[] m = {j, k}; + return m; + } + + // Sort interval [lo, hi] inplace recursively + // This chooses random pivot value thus improving time complexity + private static void randomizedQuickSort(int[] a, int l, int r) { + if (l >= r) { + return; + } + int k = random.nextInt(r - l + 1) + l; + int t = a[l]; + a[l] = a[k]; + a[k] = t; + // use partition3 + int[] m = partition3(a, l, r); + randomizedQuickSort(a, l, m[0]); + randomizedQuickSort(a, m[1], r); + } + + // Swap two elements + private static void swap(int[] ar, int i, int j) { + int tmp = ar[i]; + ar[i] = ar[j]; + ar[j] = tmp; + } + + public static void main(String[] args) { + InplaceSort sorter = new QuickSort3(); + int[] array = {10, 4, 6, 4, 8, -13, 2, 3}; + sorter.sort(array); + // Prints: + // [-13, 2, 3, 4, 4, 6, 8, 10] + System.out.println(java.util.Arrays.toString(array)); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/sorting/RadixSort.java b/src/main/java/com/williamfiset/algorithms/sorting/RadixSort.java new file mode 100644 index 000000000..4e2d5e6b5 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/sorting/RadixSort.java @@ -0,0 +1,86 @@ +/** + * An implementation of Radix Sort. + * + *

See https://en.wikipedia.org/wiki/Radix_sort for details on runtime and complexity Radix sorts + * operates in O(nw) time, where n is the number of keys, and w is the key length where w is + * constant on primitive types like Integer which gives it a better performance than other + * compare-based sort algorithms, like i.e. QuickSort + * + *

Time Complexity: O(nw) + * + *

Run with: + * + *

$ ./gradlew run -Palgorithm=sorting.RadixSort + * + * @author EAlexa + */ +package com.williamfiset.algorithms.sorting; + +public class RadixSort implements InplaceSort { + + @Override + public void sort(int[] values) { + RadixSort.radixSort(values); + } + + static int getMax(int[] array) { + int max = array[0]; + for (int i = 0; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + return max; + } + + static int calculateNumberOfDigits(int number) { + return (int) Math.log10(number) + 1; + } + + // Requires all numbers to be greater than or equal to 1 + public static void radixSort(int[] numbers) { + if (numbers == null || numbers.length <= 1) { + return; + } + int maximum = getMax(numbers); + int numberOfDigits = calculateNumberOfDigits(maximum); + int placeValue = 1; + while (numberOfDigits-- > 0) { + countSort(numbers, placeValue); + placeValue *= 10; + } + } + + private static void countSort(int[] numbers, int placeValue) { + int range = 10; + + int[] frequency = new int[range]; + int[] sortedValues = new int[numbers.length]; + + for (int i = 0; i < numbers.length; i++) { + int digit = (numbers[i] / placeValue) % range; + frequency[digit]++; + } + + for (int i = 1; i < range; i++) { + frequency[i] += frequency[i - 1]; + } + + for (int i = numbers.length - 1; i >= 0; i--) { + int digit = (numbers[i] / placeValue) % range; + sortedValues[frequency[digit] - 1] = numbers[i]; + frequency[digit]--; + } + + System.arraycopy(sortedValues, 0, numbers, 0, numbers.length); + } + + public static void main(String[] args) { + InplaceSort sorter = new RadixSort(); + int[] numbers = {387, 468, 134, 123, 68, 221, 769, 37, 7, 890, 1, 587}; + sorter.sort(numbers); + // Prints: + // [1, 7, 37, 68, 123, 134, 221, 387, 468, 587, 769, 890] + System.out.println(java.util.Arrays.toString(numbers)); + } +} diff --git a/src/main/java/com/williamfiset/algorithms/sorting/SelectionSort.java b/src/main/java/com/williamfiset/algorithms/sorting/SelectionSort.java new file mode 100644 index 000000000..52d7f60b8 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/sorting/SelectionSort.java @@ -0,0 +1,49 @@ +/** + * Selection sort implementation + * + *

Run with: + * + *

$ ./gradlew run -Palgorithm=sorting.SelectionSort + * + * @author William Fiset, william.alexandre.fiset@gmail.com + */ +package com.williamfiset.algorithms.sorting; + +public class SelectionSort implements InplaceSort { + + @Override + public void sort(int[] values) { + SelectionSort.selectionSort(values); + } + + public static void selectionSort(int[] array) { + if (array == null) return; + final int N = array.length; + + for (int i = 0; i < N; i++) { + // Find the index beyond i with a lower value than i + int swapIndex = i; + for (int j = i + 1; j < N; j++) { + if (array[j] < array[swapIndex]) { + swapIndex = j; + } + } + swap(array, i, swapIndex); + } + } + + private static void swap(int[] ar, int i, int j) { + int tmp = ar[i]; + ar[i] = ar[j]; + ar[j] = tmp; + } + + public static void main(String[] args) { + InplaceSort sorter = new SelectionSort(); + int[] array = {10, 4, 6, 8, 13, 2, 3}; + sorter.sort(array); + // Prints: + // [2, 3, 4, 6, 8, 10, 13] + System.out.println(java.util.Arrays.toString(array)); + } +} diff --git a/com/williamfiset/algorithms/strings/BoothsAlgorithm.java b/src/main/java/com/williamfiset/algorithms/strings/BoothsAlgorithm.java similarity index 100% rename from com/williamfiset/algorithms/strings/BoothsAlgorithm.java rename to src/main/java/com/williamfiset/algorithms/strings/BoothsAlgorithm.java diff --git a/src/main/java/com/williamfiset/algorithms/strings/BoyerMooreStringSearch.java b/src/main/java/com/williamfiset/algorithms/strings/BoyerMooreStringSearch.java new file mode 100644 index 000000000..1b99da2b7 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/strings/BoyerMooreStringSearch.java @@ -0,0 +1,70 @@ +/** + * Performs Boyer-Moore search on a given string with a given pattern + * + *

./gradlew run -Palgorithm=strings.BoyerMooreStringSearch + */ +package com.williamfiset.algorithms.strings; + +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.util.Objects.isNull; + +import java.util.ArrayList; +import java.util.List; + +public class BoyerMooreStringSearch { + + private static final int MAX_ALPHABET_SIZE = 256; + + /** + * Performs Boyer-Moore search on a given string with a given pattern + * + * @param text the string being searched in + * @param pattern the string being searched for + * @return List of indexes where the pattern occurs + */ + public List findOccurrences(String text, String pattern) { + if (isNull(text) + || isNull(pattern) + || pattern.length() > text.length() + || pattern.length() == 0) { + return new ArrayList<>(); + } + List occurrences = new ArrayList<>(); + int[] skipTable = generateSkipTable(pattern); + + int n = pattern.length(); + for (int textIndex = n - 1, patternIndex = n - 1; textIndex < text.length(); ) { + // Found a match! + if (patternIndex >= 0 && pattern.charAt(patternIndex) == text.charAt(textIndex)) { + if (patternIndex == 0) { + occurrences.add(textIndex); + } else { + textIndex--; + } + patternIndex--; + } else { + textIndex += n - min(max(patternIndex, 0), skipTable[text.charAt(textIndex)] + 1); + patternIndex = n - 1; + } + } + return occurrences; + } + + private int[] generateSkipTable(String pattern) { + int[] skipTable = new int[MAX_ALPHABET_SIZE]; + for (int i = 0; i < pattern.length(); i++) { + skipTable[(int) pattern.charAt(i)] = i; + } + return skipTable; + } + + public static void main(String[] args) { + BoyerMooreStringSearch searcher = new BoyerMooreStringSearch(); + String t = "ABABAAABAABAB"; + String p = "AA"; + + // Prints: [4, 5, 8] + System.out.println(searcher.findOccurrences(t, p)); + } +} diff --git a/com/williamfiset/algorithms/strings/KMP.java b/src/main/java/com/williamfiset/algorithms/strings/KMP.java similarity index 100% rename from com/williamfiset/algorithms/strings/KMP.java rename to src/main/java/com/williamfiset/algorithms/strings/KMP.java diff --git a/com/williamfiset/algorithms/strings/LongestCommonPrefixArray.java b/src/main/java/com/williamfiset/algorithms/strings/LongestCommonPrefixArray.java similarity index 100% rename from com/williamfiset/algorithms/strings/LongestCommonPrefixArray.java rename to src/main/java/com/williamfiset/algorithms/strings/LongestCommonPrefixArray.java diff --git a/com/williamfiset/algorithms/strings/LongestCommonSubstring.java b/src/main/java/com/williamfiset/algorithms/strings/LongestCommonSubstring.java similarity index 98% rename from com/williamfiset/algorithms/strings/LongestCommonSubstring.java rename to src/main/java/com/williamfiset/algorithms/strings/LongestCommonSubstring.java index 9a74c94b3..f0718493b 100644 --- a/com/williamfiset/algorithms/strings/LongestCommonSubstring.java +++ b/src/main/java/com/williamfiset/algorithms/strings/LongestCommonSubstring.java @@ -3,9 +3,12 @@ * *

Video: https://youtu.be/Ic80xQFWevc Time complexity: O(nlog^2(n)) * - *

Run on command line: Algorithms$ javac - * com/williamfiset/algorithms/strings/LongestCommonSubstring.java Algorithms$ java - * com/williamfiset/algorithms/strings/LongestCommonSubstring + *

Run on command line: + * + *

Compile: $ javac -d src/main/java + * src/main/java/com/williamfiset/algorithms/strings/LongestCommonSubstring.java + * + *

Run: $ java -cp src/main/java com/williamfiset/algorithms/strings/LongestCommonSubstring * * @author William Fiset, william.alexandre.fiset@gmail.com */ diff --git a/com/williamfiset/algorithms/strings/LongestRepeatedSubstring.java b/src/main/java/com/williamfiset/algorithms/strings/LongestRepeatedSubstring.java similarity index 100% rename from com/williamfiset/algorithms/strings/LongestRepeatedSubstring.java rename to src/main/java/com/williamfiset/algorithms/strings/LongestRepeatedSubstring.java diff --git a/com/williamfiset/algorithms/strings/ManachersAlgorithm.java b/src/main/java/com/williamfiset/algorithms/strings/ManachersAlgorithm.java similarity index 99% rename from com/williamfiset/algorithms/strings/ManachersAlgorithm.java rename to src/main/java/com/williamfiset/algorithms/strings/ManachersAlgorithm.java index 62d66a529..84dadae61 100644 --- a/com/williamfiset/algorithms/strings/ManachersAlgorithm.java +++ b/src/main/java/com/williamfiset/algorithms/strings/ManachersAlgorithm.java @@ -51,7 +51,6 @@ private static char[] preProcess(char[] str) { // of each palindrome centered at each position. public static java.util.TreeSet findPalindromeSubstrings(String str) { char[] S = str.toCharArray(); - int N = S.length; int[] centers = manachers(S); java.util.TreeSet palindromes = new java.util.TreeSet<>(); diff --git a/com/williamfiset/algorithms/strings/RabinKarp.java b/src/main/java/com/williamfiset/algorithms/strings/RabinKarp.java similarity index 95% rename from com/williamfiset/algorithms/strings/RabinKarp.java rename to src/main/java/com/williamfiset/algorithms/strings/RabinKarp.java index 96c873912..c8e9adce2 100644 --- a/com/williamfiset/algorithms/strings/RabinKarp.java +++ b/src/main/java/com/williamfiset/algorithms/strings/RabinKarp.java @@ -17,7 +17,7 @@ public class RabinKarp { // same value as ' ' since 0*95^0 = 0*95^0 + 0*95^1 + 0*95^2 private static final long ALPHABET_BASE = 95 + 1; private static final long[] ALPHABET = new long[127]; - private static final BigInteger BIG_ALPHA = new BigInteger(String.valueOf(ALPHABET_BASE)); + private static final BigInteger BIG_ALPHA = BigInteger.valueOf(ALPHABET_BASE); // More primes: 1009, 1013, 1019, 10007, 10009, 10037, 100003, 100019, 100043, 1000003, 1000033, // 1000037, @@ -35,7 +35,7 @@ public class RabinKarp { // Compute modular inverses for chosen mod values for (int i = 0; i < N_HASHES; i++) { - java.math.BigInteger mod = new java.math.BigInteger(String.valueOf(MODS[i])); + java.math.BigInteger mod = java.math.BigInteger.valueOf(MODS[i]); MOD_INVERSES[i] = BIG_ALPHA.modInverse(mod).longValue(); BIG_MODS[i] = mod; } @@ -83,7 +83,7 @@ public static List rabinKarp(String text, String pattern) { long[] patternHash = computeHash(pattern); long[] rollingHash = computeHash(text.substring(0, PL)); - final BigInteger BIG_PL = new BigInteger(String.valueOf(PL)); + final BigInteger BIG_PL = BigInteger.valueOf(PL); final long[] POWERS = new long[N_HASHES]; for (int i = 0; i < N_HASHES; i++) POWERS[i] = BIG_ALPHA.modPow(BIG_PL, BIG_MODS[i]).longValue(); @@ -121,7 +121,7 @@ public static List rabinKarpBackwards(String text, String pattern) { long[] patternHash = computeHash(pattern); long[] rollingHash = computeHash(text.substring(TL - PL, TL)); - final BigInteger BIG_PL = new BigInteger(String.valueOf(PL)); + final BigInteger BIG_PL = BigInteger.valueOf(PL); final long[] POWERS = new long[N_HASHES]; for (int i = 0; i < N_HASHES; i++) POWERS[i] = BIG_ALPHA.modPow(BIG_PL, BIG_MODS[i]).longValue(); diff --git a/com/williamfiset/algorithms/strings/SubstringVerificationSuffixArray.java b/src/main/java/com/williamfiset/algorithms/strings/SubstringVerificationSuffixArray.java similarity index 100% rename from com/williamfiset/algorithms/strings/SubstringVerificationSuffixArray.java rename to src/main/java/com/williamfiset/algorithms/strings/SubstringVerificationSuffixArray.java diff --git a/src/main/java/com/williamfiset/algorithms/strings/ZAlgorithm.java b/src/main/java/com/williamfiset/algorithms/strings/ZAlgorithm.java new file mode 100644 index 000000000..4768a6d09 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/strings/ZAlgorithm.java @@ -0,0 +1,41 @@ +package com.williamfiset.algorithms.strings; + +import static java.util.Objects.isNull; + +public class ZAlgorithm { + + /** + * Calculates the Z-array of a given string + * + * @param text the string on which Z-array is computed + * @return An int-array which is the Z-array of text + */ + public int[] calculateZ(String text) { + if (isNull(text)) { + return new int[] {}; + } + int size = text.length(); + int[] Z = new int[size]; + int L, R, k; + L = R = 0; + for (int i = 0; i < size; i++) { + if (i == 0) Z[i] = size; + else if (i > R) { + L = R = i; + while (R < size && text.charAt(R - L) == text.charAt(R)) R++; + Z[i] = R - L; + R--; + } else { + k = i - L; + if (Z[k] < R - i + 1) Z[i] = Z[k]; + else { + L = i; + while (R < size && text.charAt(R - L) == text.charAt(R)) R++; + Z[i] = R - L; + R--; + } + } + } + return Z; + } +} diff --git a/com/williamfiset/algorithms/utils/TestUtils.java b/src/main/java/com/williamfiset/algorithms/utils/TestUtils.java similarity index 54% rename from com/williamfiset/algorithms/utils/TestUtils.java rename to src/main/java/com/williamfiset/algorithms/utils/TestUtils.java index 07ece087f..0bf5a3066 100644 --- a/com/williamfiset/algorithms/utils/TestUtils.java +++ b/src/main/java/com/williamfiset/algorithms/utils/TestUtils.java @@ -4,6 +4,22 @@ public final class TestUtils { + // Generates an array of random values where every number is between + // [min, max) and there are possible repeats. + public static int[] randomIntegerArray(int sz, int min, int max) { + int[] ar = new int[sz]; + for (int i = 0; i < sz; i++) ar[i] = randValue(min, max); + return ar; + } + + // Generates an array of random values where every number is between + // [min, max) and there are possible repeats. + public static long[] randomLongArray(int sz, long min, long max) { + long[] ar = new long[sz]; + for (int i = 0; i < sz; i++) ar[i] = randValue(min, max); + return ar; + } + // Generates a list of random values where every number is between // [min, max) and there are possible repeats. public static List randomIntegerList(int sz, int min, int max) { @@ -25,4 +41,9 @@ public static List randomUniformUniqueIntegerList(int sz) { public static int randValue(int min, int max) { return min + (int) (Math.random() * ((max - min))); } + + // Generates a random number between [min, max) + public static long randValue(long min, long max) { + return min + (long) (Math.random() * ((max - min))); + } } diff --git a/src/main/java/com/williamfiset/algorithms/utils/graphutils/GraphGenerator.java b/src/main/java/com/williamfiset/algorithms/utils/graphutils/GraphGenerator.java new file mode 100644 index 000000000..8b111ca16 --- /dev/null +++ b/src/main/java/com/williamfiset/algorithms/utils/graphutils/GraphGenerator.java @@ -0,0 +1,64 @@ +package com.williamfiset.algorithms.utils.graphutils; + +import java.util.*; + +public class GraphGenerator { + + public static class DagGenerator { + double edgeProbability; + int minLevels, maxLevels, minNodesPerLevel, maxNodesPerLevel; + + // Generates a DAg gives several parameters. Make sure the edge probability + // is high enough to make a mostly connected graph. + public DagGenerator( + int minLevels, + int maxLevels, + int minNodesPerLevel, + int maxNodesPerLevel, + double edgeProbability) { + this.minLevels = minLevels; + this.maxLevels = maxLevels; + this.minNodesPerLevel = minNodesPerLevel; + this.maxNodesPerLevel = maxNodesPerLevel; + this.edgeProbability = edgeProbability; + } + + /** + * @param min - The minimum. + * @param max - The maximum. + * @return A random double between these numbers (inclusive the minimum and maximum). + */ + private static int rand(int min, int max) { + return min + (int) (Math.random() * ((max - min) + 1)); + } + + public List> createDag() { + int levels = rand(minLevels, maxLevels); + int[] nodesPerLevel = new int[levels]; + + int n = 0; + for (int l = 0; l < levels; l++) { + nodesPerLevel[l] = rand(minNodesPerLevel, maxNodesPerLevel); + n += nodesPerLevel[l]; + } + List> g = Utils.createEmptyAdjacencyList(n); + int levelIndex = 0; + for (int l = 0; l < levels - 1; l++) { // For each level + for (int i = 0; i < nodesPerLevel[l]; i++) { // for each node on each level + for (int j = 0; j < nodesPerLevel[l + 1]; j++) { // for each possible edge link + if (Math.random() <= edgeProbability) { + Utils.addDirectedEdge(g, levelIndex + i, levelIndex + nodesPerLevel[l] + j); + } + } + } + levelIndex += nodesPerLevel[l]; + } + return g; + } + } + + public static void main(String[] args) { + DagGenerator gen = new DagGenerator(10, 10, 5, 5, 0.9); + gen.createDag(); + } +} diff --git a/com/williamfiset/algorithms/utils/graphutils/Utils.java b/src/main/java/com/williamfiset/algorithms/utils/graphutils/Utils.java similarity index 100% rename from com/williamfiset/algorithms/utils/graphutils/Utils.java rename to src/main/java/com/williamfiset/algorithms/utils/graphutils/Utils.java diff --git a/javatests/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeTest.java similarity index 53% rename from javatests/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeTest.java index 155783f87..9461291e3 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/balancedtree/AVLTreeTest.java @@ -1,14 +1,12 @@ -package javatests.com.williamfiset.algorithms.datastructures.balancedtree; +package com.williamfiset.algorithms.datastructures.balancedtree; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.datastructures.balancedtree.AVLTreeRecursive; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.TreeSet; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.*; public class AVLTreeTest { @@ -19,24 +17,24 @@ public class AVLTreeTest { private AVLTreeRecursive tree; - @Before + @BeforeEach public void setup() { tree = new AVLTreeRecursive<>(); } @Test public void testNullInsertion() { - assertFalse(tree.insert(null)); + assertThat(tree.insert(null)).isFalse(); } @Test public void testNullRemoval() { - assertFalse(tree.remove(null)); + assertThat(tree.remove(null)).isFalse(); } @Test public void testTreeContainsNull() { - assertFalse(tree.contains(null)); + assertThat(tree.contains(null)).isFalse(); } @Test @@ -46,14 +44,14 @@ public void testLeftLeftCase() { tree.insert(2); tree.insert(1); - assertEquals(2, tree.root.value.intValue()); - assertEquals(1, tree.root.left.value.intValue()); - assertEquals(3, tree.root.right.value.intValue()); + assertThat(tree.root.value.intValue()).isEqualTo(2); + assertThat(tree.root.left.value.intValue()).isEqualTo(1); + assertThat(tree.root.right.value.intValue()).isEqualTo(3); - assertNull(tree.root.left.left); - assertNull(tree.root.left.right); - assertNull(tree.root.right.left); - assertNull(tree.root.right.right); + assertThat(tree.root.left.left).isNull(); + assertThat(tree.root.left.right).isNull(); + assertThat(tree.root.right.left).isNull(); + assertThat(tree.root.right.right).isNull(); } @Test @@ -63,14 +61,14 @@ public void testLeftRightCase() { tree.insert(1); tree.insert(2); - assertEquals(2, tree.root.value.intValue()); - assertEquals(1, tree.root.left.value.intValue()); - assertEquals(3, tree.root.right.value.intValue()); + assertThat(tree.root.value.intValue()).isEqualTo(2); + assertThat(tree.root.left.value.intValue()).isEqualTo(1); + assertThat(tree.root.right.value.intValue()).isEqualTo(3); - assertNull(tree.root.left.left); - assertNull(tree.root.left.right); - assertNull(tree.root.right.left); - assertNull(tree.root.right.right); + assertThat(tree.root.left.left).isNull(); + assertThat(tree.root.left.right).isNull(); + assertThat(tree.root.right.left).isNull(); + assertThat(tree.root.right.right).isNull(); } @Test @@ -80,14 +78,14 @@ public void testRightRightCase() { tree.insert(2); tree.insert(3); - assertEquals(2, tree.root.value.intValue()); - assertEquals(1, tree.root.left.value.intValue()); - assertEquals(3, tree.root.right.value.intValue()); + assertThat(tree.root.value.intValue()).isEqualTo(2); + assertThat(tree.root.left.value.intValue()).isEqualTo(1); + assertThat(tree.root.right.value.intValue()).isEqualTo(3); - assertNull(tree.root.left.left); - assertNull(tree.root.left.right); - assertNull(tree.root.right.left); - assertNull(tree.root.right.right); + assertThat(tree.root.left.left).isNull(); + assertThat(tree.root.left.right).isNull(); + assertThat(tree.root.right.left).isNull(); + assertThat(tree.root.right.right).isNull(); } @Test @@ -97,26 +95,26 @@ public void testRightLeftCase() { tree.insert(3); tree.insert(2); - assertEquals(2, tree.root.value.intValue()); - assertEquals(1, tree.root.left.value.intValue()); - assertEquals(3, tree.root.right.value.intValue()); + assertThat(tree.root.value.intValue()).isEqualTo(2); + assertThat(tree.root.left.value.intValue()).isEqualTo(1); + assertThat(tree.root.right.value.intValue()).isEqualTo(3); - assertNull(tree.root.left.left); - assertNull(tree.root.left.right); - assertNull(tree.root.right.left); - assertNull(tree.root.right.right); + assertThat(tree.root.left.left).isNull(); + assertThat(tree.root.left.right).isNull(); + assertThat(tree.root.right.left).isNull(); + assertThat(tree.root.right.right).isNull(); } @Test public void testRandomizedBalanceFactorTest() { for (int i = 0; i < TEST_SZ; i++) { tree.insert(randValue()); - assertTrue(validateBalanceFactorValues(tree.root)); + assertThat(validateBalanceFactorValues(tree.root)).isTrue(); } } // Make sure all balance factor values are either -1, 0 or +1 - static boolean validateBalanceFactorValues(AVLTreeRecursive.Node node) { + static boolean validateBalanceFactorValues(AVLTreeRecursive.Node node) { if (node == null) return true; if (node.bf > +1 || node.bf < -1) return false; return validateBalanceFactorValues(node.left) && validateBalanceFactorValues(node.right); @@ -128,9 +126,10 @@ public void testRandomizedValueInsertionsAgainstTreeSet() { TreeSet set = new TreeSet<>(); for (int i = 0; i < TEST_SZ; i++) { int v = randValue(); - assertEquals(set.add(v), tree.insert(v)); - assertEquals(set.size(), tree.size()); - assertTrue(tree.validateBSTInvarient(tree.root)); + + assertThat(tree.insert(v)).isEqualTo(set.add(v)); + assertThat(tree.size()).isEqualTo(set.size()); + assertThat(tree.validateBSTInvarient(tree.root)).isTrue(); } } @@ -139,7 +138,7 @@ public void testTreeHeight() { for (int n = 1; n <= TEST_SZ; n++) { tree.insert(randValue()); - int height = tree.height(); + double height = tree.height(); // Get an upper bound on what the maximum height of // an AVL tree should be. Values were taken from: @@ -148,7 +147,7 @@ public void testTreeHeight() { double b = -0.329; double upperBound = c * (Math.log(n + 2.0) / Math.log(2)) + b; - assertTrue(height < upperBound); + assertThat(height).isLessThan(upperBound); } } @@ -170,12 +169,12 @@ public void randomRemoveTests() { Integer value = lst.get(j); - assertEquals(ts.remove(value), tree.remove(value)); - assertFalse(tree.contains(value)); - assertEquals(size - j - 1, tree.size()); + assertThat(tree.remove(value)).isEqualTo(ts.remove(value)); + assertThat(tree.contains(value)).isFalse(); + assertThat(tree.size()).isEqualTo(size - j - 1); } - assertTrue(tree.isEmpty()); + assertThat(tree.isEmpty()).isTrue(); } } diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/balancedtree/RedBlackTreeTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/balancedtree/RedBlackTreeTest.java new file mode 100644 index 000000000..dd4e536cf --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/balancedtree/RedBlackTreeTest.java @@ -0,0 +1,365 @@ +package com.williamfiset.algorithms.datastructures.balancedtree; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.*; +import org.junit.jupiter.api.*; + +public class RedBlackTreeTest { + + static final int MAX_RAND_NUM = +100000; + static final int MIN_RAND_NUM = -100000; + + static final int TEST_SZ = 9000; + + private RedBlackTree tree; + + @BeforeEach + public void setup() { + tree = new RedBlackTree<>(); + } + + @Test + public void testNullInsertion() { + assertThrows(IllegalArgumentException.class, () -> tree.insert(null)); + } + + @Test + public void testTreeContainsNull() { + assertThat(tree.contains(null)).isFalse(); + } + + @Test + public void testLeftLeftRotation() { + + tree.insert(3); + tree.insert(2); + tree.insert(1); + + assertThat(tree.root.value.intValue()).isEqualTo(2); + assertThat(tree.root.left.value.intValue()).isEqualTo(1); + assertThat(tree.root.right.value.intValue()).isEqualTo(3); + + assertThat(tree.root.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.left.color).isEqualTo(RedBlackTree.RED); + assertThat(tree.root.right.color).isEqualTo(RedBlackTree.RED); + + assertThat(tree.root).isEqualTo(tree.root.left.parent); + assertThat(tree.root).isEqualTo(tree.root.right.parent); + + assertNullChildren(tree, tree.root.left, tree.root.right); + assertCorrectParentLinks(tree, tree.root, tree.NIL); + } + + @Test + public void testLeftRightRotation() { + + tree.insert(3); + tree.insert(1); + tree.insert(2); + + assertThat(tree.root.value.intValue()).isEqualTo(2); + assertThat(tree.root.left.value.intValue()).isEqualTo(1); + assertThat(tree.root.right.value.intValue()).isEqualTo(3); + + assertThat(tree.root.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.left.color).isEqualTo(RedBlackTree.RED); + assertThat(tree.root.right.color).isEqualTo(RedBlackTree.RED); + + assertThat(tree.root).isEqualTo(tree.root.left.parent); + assertThat(tree.root).isEqualTo(tree.root.right.parent); + + assertNullChildren(tree, tree.root.left, tree.root.right); + assertCorrectParentLinks(tree, tree.root, tree.NIL); + } + + @Test + public void testRightLeftRotation() { + + tree.insert(1); + tree.insert(3); + tree.insert(2); + + assertThat(tree.root.value.intValue()).isEqualTo(2); + assertThat(tree.root.left.value.intValue()).isEqualTo(1); + assertThat(tree.root.right.value.intValue()).isEqualTo(3); + + assertThat(tree.root.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.left.color).isEqualTo(RedBlackTree.RED); + assertThat(tree.root.right.color).isEqualTo(RedBlackTree.RED); + + assertThat(tree.root).isEqualTo(tree.root.left.parent); + assertThat(tree.root).isEqualTo(tree.root.right.parent); + + assertNullChildren(tree, tree.root.left, tree.root.right); + assertCorrectParentLinks(tree, tree.root, tree.NIL); + } + + @Test + public void testRightRightRotation() { + + tree.insert(1); + tree.insert(2); + tree.insert(3); + + assertThat(tree.root.value.intValue()).isEqualTo(2); + assertThat(tree.root.left.value.intValue()).isEqualTo(1); + assertThat(tree.root.right.value.intValue()).isEqualTo(3); + + assertThat(tree.root.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.left.color).isEqualTo(RedBlackTree.RED); + assertThat(tree.root.right.color).isEqualTo(RedBlackTree.RED); + + assertThat(tree.root).isEqualTo(tree.root.left.parent); + assertThat(tree.root).isEqualTo(tree.root.right.parent); + + assertNullChildren(tree, tree.root.left, tree.root.right); + assertCorrectParentLinks(tree, tree.root, tree.NIL); + } + + @Test + public void testLeftUncleCase() { + + /* Red left uncle case. */ + + tree.insert(1); + tree.insert(2); + tree.insert(3); + tree.insert(4); + + assertThat(tree.root.value.intValue()).isEqualTo(2); + assertThat(tree.root.left.value.intValue()).isEqualTo(1); + assertThat(tree.root.right.value.intValue()).isEqualTo(3); + assertThat(tree.root.right.right.value.intValue()).isEqualTo(4); + + assertThat(tree.root.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.left.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.right.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.right.right.color).isEqualTo(RedBlackTree.RED); + + assertThat(tree.root.right.left).isEqualTo(tree.NIL); + assertNullChildren(tree, tree.root.left, tree.root.right.right); + assertCorrectParentLinks(tree, tree.root, tree.NIL); + + /* Black left uncle case. */ + + tree.insert(5); + + assertThat(tree.root.value.intValue()).isEqualTo(2); + assertThat(tree.root.left.value.intValue()).isEqualTo(1); + assertThat(tree.root.right.value.intValue()).isEqualTo(4); + assertThat(tree.root.right.left.value.intValue()).isEqualTo(3); + assertThat(tree.root.right.right.value.intValue()).isEqualTo(5); + + assertThat(tree.root.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.left.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.right.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.right.left.color).isEqualTo(RedBlackTree.RED); + assertThat(tree.root.right.right.color).isEqualTo(RedBlackTree.RED); + assertCorrectParentLinks(tree, tree.root, tree.NIL); + } + + @Test + public void testRightUncleCase() { + + /* Red right uncle case. */ + + tree.insert(2); + tree.insert(3); + tree.insert(4); + tree.insert(1); + + assertThat(tree.root.value.intValue()).isEqualTo(3); + assertThat(tree.root.left.value.intValue()).isEqualTo(2); + assertThat(tree.root.right.value.intValue()).isEqualTo(4); + assertThat(tree.root.left.left.value.intValue()).isEqualTo(1); + + assertThat(tree.root.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.left.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.right.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.left.left.color).isEqualTo(RedBlackTree.RED); + + assertThat(tree.root.right.left).isEqualTo(tree.NIL); + assertThat(tree.root.left.right).isEqualTo(tree.NIL); + assertNullChildren(tree, tree.root.right, tree.root.left.left); + assertCorrectParentLinks(tree, tree.root, tree.NIL); + + /* Black right uncle case. */ + + tree.insert(0); + + assertThat(tree.root.value.intValue()).isEqualTo(3); + assertThat(tree.root.left.value.intValue()).isEqualTo(1); + assertThat(tree.root.right.value.intValue()).isEqualTo(4); + assertThat(tree.root.left.left.value.intValue()).isEqualTo(0); + assertThat(tree.root.left.right.value.intValue()).isEqualTo(2); + + assertThat(tree.root.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.left.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.right.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.left.left.color).isEqualTo(RedBlackTree.RED); + assertThat(tree.root.left.right.color).isEqualTo(RedBlackTree.RED); + assertCorrectParentLinks(tree, tree.root, tree.NIL); + } + + @Test + public void interestingCase1() { + + int[] values = {41, 44, 95, 83, 72, 66, 94, 90, 59}; + for (int v : values) tree.insert(v); + + assertThat(tree.root.value.intValue()).isEqualTo(44); + + assertThat(tree.root.left.value.intValue()).isEqualTo(41); + assertThat(tree.root.right.value.intValue()).isEqualTo(83); + + assertThat(tree.root.right.left.value.intValue()).isEqualTo(66); + assertThat(tree.root.right.right.value.intValue()).isEqualTo(94); + + assertThat(tree.root.right.left.left.value.intValue()).isEqualTo(59); + assertThat(tree.root.right.left.right.value.intValue()).isEqualTo(72); + assertThat(tree.root.right.right.left.value.intValue()).isEqualTo(90); + assertThat(tree.root.right.right.right.value.intValue()).isEqualTo(95); + + assertThat(tree.root.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.left.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.right.color).isEqualTo(RedBlackTree.RED); + assertThat(tree.root.right.left.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.right.right.color).isEqualTo(RedBlackTree.BLACK); + assertThat(tree.root.right.left.left.color).isEqualTo(RedBlackTree.RED); + assertThat(tree.root.right.left.right.color).isEqualTo(RedBlackTree.RED); + assertThat(tree.root.right.right.left.color).isEqualTo(RedBlackTree.RED); + assertThat(tree.root.right.right.right.color).isEqualTo(RedBlackTree.RED); + } + + @Test + public void testRandomizedValueInsertionsAgainstTreeSet() { + + TreeSet set = new TreeSet<>(); + for (int i = 0; i < TEST_SZ; i++) { + int v = randValue(); + assertThat(tree.insert(v)).isEqualTo(set.add(v)); + assertThat(tree.size()).isEqualTo(tree.size()); + assertThat(tree.contains(v)).isTrue(); + assertBinarySearchTreeInvariant(tree, tree.root); + } + } + + @Test + public void testRemoval() { + tree.insert(5); + tree.insert(7); + tree.insert(9); + + tree.delete(5); + assertThat(tree.contains(5)).isFalse(); + + tree.delete(7); + assertThat(tree.contains(7)).isFalse(); + + tree.delete(9); + assertThat(tree.contains(9)).isFalse(); + } + + @Test + public void testNullRemoval() { + assertThat(tree.delete(null)).isFalse(); + } + + @Test + public void testNumberDoesntExist() { + assertThat(tree.delete(0)).isFalse(); + } + + @Test + public void randomRemoveTests() { + TreeSet ts = new TreeSet<>(); + for (int i = 0; i < TEST_SZ; i++) { + + List lst = genRandList(i); + for (Integer value : lst) { + tree.insert(value); + ts.add(value); + } + Collections.shuffle(lst); + + for (int j = 0; j < i; j++) { + + Integer value = lst.get(j); + boolean treeSetRemove = ts.remove(value); + boolean treeRemove = tree.delete(value); + assertThat(treeSetRemove).isEqualTo(treeRemove); + assertThat(tree.contains(value)).isFalse(); + assertThat(tree.size()).isEqualTo(i - j - 1); + } + assertThat(ts.isEmpty()).isEqualTo(tree.isEmpty()); + } + } + + @Test + public void testTreeHeight() { + for (int n = 1; n <= TEST_SZ; n++) { + + tree.insert(randValue()); + double height = tree.height(); + + // RB tree height upper bound: + // https://en.wikipedia.org/wiki/AVL_tree#Comparison_to_other_structures + double upperBound = 2 * (Math.log(n + 1) / Math.log(2)); + + assertThat(height).isAtMost(upperBound); + } + } + + static void assertNullChildren(RedBlackTree tree, RedBlackTree.Node... nodes) { + for (RedBlackTree.Node node : nodes) { + assertThat(node.left).isEqualTo(tree.NIL); + assertThat(node.right).isEqualTo(tree.NIL); + } + } + + static void assertCorrectParentLinks( + RedBlackTree tree, RedBlackTree.Node node, RedBlackTree.Node parent) { + if (node == tree.NIL) return; + try { + assertThat(node.parent).isEqualTo(parent); + } catch (AssertionError e) { + e.printStackTrace(); + } + assertCorrectParentLinks(tree, node.left, node); + assertCorrectParentLinks(tree, node.right, node); + } + + // Make sure all left child nodes are smaller in value than their parent and + // make sure all right child nodes are greater in value than their parent. + // (Used only for testing) + boolean assertBinarySearchTreeInvariant(RedBlackTree tree, RedBlackTree.Node node) { + if (node == tree.NIL) return true; + boolean isValid = true; + if (node.left != tree.NIL) isValid = node.left.value.compareTo(node.value) < 0; + if (node.right != tree.NIL) isValid = isValid && node.right.value.compareTo(node.value) > 0; + return isValid + && assertBinarySearchTreeInvariant(tree, node.left) + && assertBinarySearchTreeInvariant(tree, node.right); + } + + // Used for testing. + boolean validateParentLinksAreCorrect(RedBlackTree.Node node, RedBlackTree.Node parent) { + if (node == tree.NIL) return true; + if (node.parent != parent) return false; + return validateParentLinksAreCorrect(node.left, node) + && validateParentLinksAreCorrect(node.right, node); + } + + static List genRandList(int sz) { + List lst = new ArrayList<>(sz); + for (int i = 0; i < sz; i++) lst.add(i); // unique values. + Collections.shuffle(lst); + return lst; + } + + public static int randValue() { + return (int) (Math.random() * MAX_RAND_NUM * 2) + MIN_RAND_NUM; + } +} diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/balancedtree/TreapTreeTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/balancedtree/TreapTreeTest.java new file mode 100644 index 000000000..51f469e8b --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/balancedtree/TreapTreeTest.java @@ -0,0 +1,164 @@ +package com.williamfiset.algorithms.datastructures.balancedtree; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.TreeSet; +import org.junit.jupiter.api.*; + +public class TreapTreeTest { + + static final int MAX_RAND_NUM = +100000; + static final int MIN_RAND_NUM = -100000; + + static final int TEST_SZ = 500; + + private TreapTree tree; + + @BeforeEach + public void setup() { + tree = new TreapTree<>(); + } + + @Test + public void testNullInsertion() { + assertThrows(IllegalArgumentException.class, () -> tree.insert(null)); + } + + @Test + public void testTreeContainsNull() { + assertThat(tree.contains(null)).isFalse(); + } + + @Test + public void LeftLeftCase() { + tree.insert(15, 15); + tree.insert(10, 8); + tree.insert(20, 10); + tree.insert(30, 9); + + assertThat(tree.root.left.getValue()).isEqualTo(10); + assertThat(tree.root.getValue()).isEqualTo(15); + assertThat(tree.root.right.getValue()).isEqualTo(20); + assertThat(tree.root.right.right.getValue()).isEqualTo(30); + + tree.insert(32, 14); + + assertThat(tree.root.left.getValue()).isEqualTo(10); + assertThat(tree.root.getValue()).isEqualTo(15); + assertThat(tree.root.right.getValue()).isEqualTo(32); + assertThat(tree.root.right.left.getValue()).isEqualTo(20); + assertThat(tree.root.right.left.right.getValue()).isEqualTo(30); + + assertThat(tree.root.right.left.right.left).isNull(); + assertThat(tree.root.right.left.right.right).isNull(); + assertThat(tree.root.right.left.left).isNull(); + assertThat(tree.root.right.right).isNull(); + assertThat(tree.root.left.left).isNull(); + assertThat(tree.root.left.right).isNull(); + } + + @Test + public void testLeftRightCase() { + tree.insert(20, 10); + tree.insert(17, 5); + tree.insert(26, 7); + + assertThat(tree.root.getValue()).isEqualTo(20); + assertThat(tree.root.left.getValue()).isEqualTo(17); + assertThat(tree.root.right.getValue()).isEqualTo(26); + + tree.insert(18, 15); + assertThat(tree.root.getValue()).isEqualTo(18); + assertThat(tree.root.left.getValue()).isEqualTo(17); + assertThat(tree.root.right.getValue()).isEqualTo(20); + assertThat(tree.root.right.right.getValue()).isEqualTo(26); + + assertThat(tree.root.left.left).isNull(); + assertThat(tree.root.left.right).isNull(); + assertThat(tree.root.right.left).isNull(); + assertThat(tree.root.right.right.left).isNull(); + assertThat(tree.root.right.right.right).isNull(); + } + + @Test + public void testRightRightCase() { + tree.insert(10, 2); + tree.insert(8, 1); + + assertThat(tree.root.getValue()).isEqualTo(10); + assertThat(tree.root.left.getValue()).isEqualTo(8); + + tree.insert(7, 3); + + assertThat(tree.root.getValue()).isEqualTo(7); + assertThat(tree.root.right.getValue()).isEqualTo(10); + assertThat(tree.root.right.left.getValue()).isEqualTo(8); + + assertThat(tree.root.left).isNull(); + assertThat(tree.root.right.right).isNull(); + assertThat(tree.root.right.left.right).isNull(); + assertThat(tree.root.right.left.left).isNull(); + } + + @Test + public void testRightLeftCase() { + tree.insert(15, 10); + tree.insert(16, 8); + + assertThat(tree.root.getValue()).isEqualTo(15); + assertThat(tree.root.right.getValue()).isEqualTo(16); + + tree.insert(13, 11); + + assertThat(tree.root.getValue()).isEqualTo(13); + assertThat(tree.root.right.getValue()).isEqualTo(15); + assertThat(tree.root.right.right.getValue()).isEqualTo(16); + + assertThat(tree.root.left).isNull(); + assertThat(tree.root.right.left).isNull(); + assertThat(tree.root.right.right.left).isNull(); + assertThat(tree.root.right.right.right).isNull(); + } + + @Test + public void randomTreapOperations() { + TreeSet ts = new TreeSet<>(); + for (int i = 0; i < TEST_SZ; i++) { + + int size = i; + List lst = genRandList(size); + for (Integer value : lst) { + tree.insert(value); + ts.add(value); + } + Collections.shuffle(lst); + + // Remove all the elements we just placed in the tree. + for (int j = 0; j < size; j++) { + + Integer value = lst.get(j); + + assertThat(tree.remove(value)).isEqualTo(ts.remove(value)); + assertThat(tree.contains(value)).isFalse(); + assertThat(tree.size()).isEqualTo(size - j - 1); + } + + assertThat(tree.isEmpty()).isTrue(); + } + } + + static List genRandList(int sz) { + List lst = new ArrayList<>(sz); + for (int i = 0; i < sz; i++) lst.add(i); // unique values. + Collections.shuffle(lst); + return lst; + } + + public static int randValue() { + return (int) (Math.random() * MAX_RAND_NUM * 2) + MIN_RAND_NUM; + } +} diff --git a/javatests/com/williamfiset/algorithms/datastructures/binarysearchtree/BinarySearchTreeTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/binarysearchtree/BinarySearchTreeTest.java similarity index 68% rename from javatests/com/williamfiset/algorithms/datastructures/binarysearchtree/BinarySearchTreeTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/binarysearchtree/BinarySearchTreeTest.java index 976d2f45d..71705f7e1 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/binarysearchtree/BinarySearchTreeTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/binarysearchtree/BinarySearchTreeTest.java @@ -1,9 +1,8 @@ -package javatests.com.williamfiset.algorithms.datastructures.binarysearchtree; +package com.williamfiset.algorithms.datastructures.binarysearchtree; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; -import com.williamfiset.algorithms.datastructures.binarysearchtree.BinarySearchTree; -import com.williamfiset.algorithms.datastructures.binarysearchtree.TreeTraversalOrder; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; @@ -11,8 +10,7 @@ import java.util.Deque; import java.util.Iterator; import java.util.List; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.*; class TestTreeNode { @@ -84,28 +82,28 @@ static void levelOrder(List lst, TestTreeNode node) { public class BinarySearchTreeTest { - static final int LOOPS = 1000; + static final int LOOPS = 100; - @Before + @BeforeEach public void setup() {} @Test public void testIsEmpty() { BinarySearchTree tree = new BinarySearchTree<>(); - assertTrue(tree.isEmpty()); + assertThat(tree.isEmpty()).isTrue(); tree.add("Hello World!"); - assertFalse(tree.isEmpty()); + assertThat(tree.isEmpty()).isFalse(); } @Test public void testSize() { BinarySearchTree tree = new BinarySearchTree<>(); - assertEquals(tree.size(), 0); + assertThat(tree.size()).isEqualTo(0); tree.add("Hello World!"); - assertEquals(tree.size(), 1); + assertThat(tree.size()).isEqualTo(1); } @Test @@ -119,29 +117,29 @@ public void testHeight() { // A // No tree - assertEquals(tree.height(), 0); + assertThat(tree.height()).isEqualTo(0); // Layer One tree.add("M"); - assertEquals(tree.height(), 1); + assertThat(tree.height()).isEqualTo(1); // Layer Two tree.add("J"); - assertEquals(tree.height(), 2); + assertThat(tree.height()).isEqualTo(2); tree.add("S"); - assertEquals(tree.height(), 2); + assertThat(tree.height()).isEqualTo(2); // Layer Three tree.add("B"); - assertEquals(tree.height(), 3); + assertThat(tree.height()).isEqualTo(3); tree.add("N"); - assertEquals(tree.height(), 3); + assertThat(tree.height()).isEqualTo(3); tree.add("Z"); - assertEquals(tree.height(), 3); + assertThat(tree.height()).isEqualTo(3); // Layer 4 tree.add("A"); - assertEquals(tree.height(), 4); + assertThat(tree.height()).isEqualTo(4); } @Test @@ -149,13 +147,13 @@ public void testAdd() { // Add element which does not yet exist BinarySearchTree tree = new BinarySearchTree<>(); - assertTrue(tree.add('A')); + assertThat(tree.add('A')).isTrue(); // Add duplicate element - assertFalse(tree.add('A')); + assertThat(tree.add('A')).isFalse(); // Add a second element which is not a duplicate - assertTrue(tree.add('B')); + assertThat(tree.add('B')).isTrue(); } @Test @@ -164,21 +162,21 @@ public void testRemove() { // Try removing an element which doesn't exist BinarySearchTree tree = new BinarySearchTree<>(); tree.add('A'); - assertEquals(tree.size(), 1); - assertFalse(tree.remove('B')); - assertEquals(tree.size(), 1); + assertThat(tree.size()).isEqualTo(1); + assertThat(tree.remove('B')).isFalse(); + assertThat(tree.size()).isEqualTo(1); // Try removing an element which does exist tree.add('B'); - assertEquals(tree.size(), 2); - assertTrue(tree.remove('B')); - assertEquals(tree.size(), 1); - assertEquals(tree.height(), 1); + assertThat(tree.size()).isEqualTo(2); + assertThat(tree.remove('B')).isTrue(); + assertThat(tree.size()).isEqualTo(1); + assertThat(tree.height()).isEqualTo(1); // Try removing the root - assertTrue(tree.remove('A')); - assertEquals(tree.size(), 0); - assertEquals(tree.height(), 0); + assertThat(tree.remove('A')).isTrue(); + assertThat(tree.size()).isEqualTo(0); + assertThat(tree.height()).isEqualTo(0); } @Test @@ -192,19 +190,19 @@ public void testContains() { tree.add('C'); // Try looking for an element which doesn't exist - assertFalse(tree.contains('D')); + assertThat(tree.contains('D')).isFalse(); // Try looking for an element which exists in the root - assertTrue(tree.contains('B')); + assertThat(tree.contains('B')).isTrue(); // Try looking for an element which exists as the left child of the root - assertTrue(tree.contains('A')); + assertThat(tree.contains('A')).isTrue(); // Try looking for an element which exists as the right child of the root - assertTrue(tree.contains('C')); + assertThat(tree.contains('C')).isTrue(); } - @Test(expected = ConcurrentModificationException.class) + @Test public void concurrentModificationErrorPreOrder() { BinarySearchTree bst = new BinarySearchTree<>(); @@ -215,13 +213,17 @@ public void concurrentModificationErrorPreOrder() { Iterator iter = bst.traverse(TreeTraversalOrder.PRE_ORDER); - while (iter.hasNext()) { - bst.add(0); - iter.next(); - } + assertThrows( + ConcurrentModificationException.class, + () -> { + while (iter.hasNext()) { + bst.add(0); + iter.next(); + } + }); } - @Test(expected = ConcurrentModificationException.class) + @Test public void concurrentModificationErrorInOrderOrder() { BinarySearchTree bst = new BinarySearchTree<>(); @@ -232,13 +234,17 @@ public void concurrentModificationErrorInOrderOrder() { Iterator iter = bst.traverse(TreeTraversalOrder.IN_ORDER); - while (iter.hasNext()) { - bst.add(0); - iter.next(); - } + assertThrows( + ConcurrentModificationException.class, + () -> { + while (iter.hasNext()) { + bst.add(0); + iter.next(); + } + }); } - @Test(expected = ConcurrentModificationException.class) + @Test public void concurrentModificationErrorPostOrder() { BinarySearchTree bst = new BinarySearchTree<>(); @@ -249,13 +255,17 @@ public void concurrentModificationErrorPostOrder() { Iterator iter = bst.traverse(TreeTraversalOrder.POST_ORDER); - while (iter.hasNext()) { - bst.add(0); - iter.next(); - } + assertThrows( + ConcurrentModificationException.class, + () -> { + while (iter.hasNext()) { + bst.add(0); + iter.next(); + } + }); } - @Test(expected = ConcurrentModificationException.class) + @Test public void concurrentModificationErrorLevelOrder() { BinarySearchTree bst = new BinarySearchTree<>(); @@ -266,13 +276,17 @@ public void concurrentModificationErrorLevelOrder() { Iterator iter = bst.traverse(TreeTraversalOrder.LEVEL_ORDER); - while (iter.hasNext()) { - bst.add(0); - iter.next(); - } + assertThrows( + ConcurrentModificationException.class, + () -> { + while (iter.hasNext()) { + bst.add(0); + iter.next(); + } + }); } - @Test(expected = ConcurrentModificationException.class) + @Test public void concurrentModificationErrorRemovingPreOrder() { BinarySearchTree bst = new BinarySearchTree<>(); @@ -283,13 +297,17 @@ public void concurrentModificationErrorRemovingPreOrder() { Iterator iter = bst.traverse(TreeTraversalOrder.PRE_ORDER); - while (iter.hasNext()) { - bst.remove(2); - iter.next(); - } + assertThrows( + ConcurrentModificationException.class, + () -> { + while (iter.hasNext()) { + bst.remove(2); + iter.next(); + } + }); } - @Test(expected = ConcurrentModificationException.class) + @Test public void concurrentModificationErrorRemovingInOrderOrder() { BinarySearchTree bst = new BinarySearchTree<>(); @@ -300,13 +318,17 @@ public void concurrentModificationErrorRemovingInOrderOrder() { Iterator iter = bst.traverse(TreeTraversalOrder.IN_ORDER); - while (iter.hasNext()) { - bst.remove(2); - iter.next(); - } + assertThrows( + ConcurrentModificationException.class, + () -> { + while (iter.hasNext()) { + bst.remove(2); + iter.next(); + } + }); } - @Test(expected = ConcurrentModificationException.class) + @Test public void concurrentModificationErrorRemovingPostOrder() { BinarySearchTree bst = new BinarySearchTree<>(); @@ -317,13 +339,17 @@ public void concurrentModificationErrorRemovingPostOrder() { Iterator iter = bst.traverse(TreeTraversalOrder.POST_ORDER); - while (iter.hasNext()) { - bst.remove(2); - iter.next(); - } + assertThrows( + ConcurrentModificationException.class, + () -> { + while (iter.hasNext()) { + bst.remove(2); + iter.next(); + } + }); } - @Test(expected = ConcurrentModificationException.class) + @Test public void concurrentModificationErrorRemovingLevelOrder() { BinarySearchTree bst = new BinarySearchTree<>(); @@ -334,10 +360,14 @@ public void concurrentModificationErrorRemovingLevelOrder() { Iterator iter = bst.traverse(TreeTraversalOrder.LEVEL_ORDER); - while (iter.hasNext()) { - bst.remove(2); - iter.next(); - } + assertThrows( + ConcurrentModificationException.class, + () -> { + while (iter.hasNext()) { + bst.remove(2); + iter.next(); + } + }); } @Test @@ -356,12 +386,12 @@ public void randomRemoveTests() { Integer value = lst.get(j); - assertTrue(tree.remove(value)); - assertFalse(tree.contains(value)); - assertEquals(tree.size(), size - j - 1); + assertThat(tree.remove(value)).isTrue(); + assertThat(tree.contains(value)).isFalse(); + assertThat(tree.size()).isEqualTo(size - j - 1); } - assertTrue(tree.isEmpty()); + assertThat(tree.isEmpty()).isTrue(); } } @@ -420,7 +450,7 @@ public void testPreOrderTraversal() { for (int i = 0; i < LOOPS; i++) { List input = genRandList(i); - assertTrue(validateTreeTraversal(TreeTraversalOrder.PRE_ORDER, input)); + assertThat(validateTreeTraversal(TreeTraversalOrder.PRE_ORDER, input)).isTrue(); } } @@ -429,7 +459,7 @@ public void testInOrderTraversal() { for (int i = 0; i < LOOPS; i++) { List input = genRandList(i); - assertTrue(validateTreeTraversal(TreeTraversalOrder.IN_ORDER, input)); + assertThat(validateTreeTraversal(TreeTraversalOrder.IN_ORDER, input)).isTrue(); } } @@ -438,7 +468,7 @@ public void testPostOrderTraversal() { for (int i = 0; i < LOOPS; i++) { List input = genRandList(i); - assertTrue(validateTreeTraversal(TreeTraversalOrder.POST_ORDER, input)); + assertThat(validateTreeTraversal(TreeTraversalOrder.POST_ORDER, input)).isTrue(); } } @@ -447,7 +477,7 @@ public void testLevelOrderTraversal() { for (int i = 0; i < LOOPS; i++) { List input = genRandList(i); - assertTrue(validateTreeTraversal(TreeTraversalOrder.LEVEL_ORDER, input)); + assertThat(validateTreeTraversal(TreeTraversalOrder.LEVEL_ORDER, input)).isTrue(); } } } diff --git a/javatests/com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTreeTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTreeTest.java similarity index 63% rename from javatests/com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTreeTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTreeTest.java index e0ee826bf..70ef27d5b 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTreeTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/binarysearchtree/SplayTreeTest.java @@ -1,26 +1,27 @@ -import static org.junit.jupiter.api.Assertions.*; +package com.williamfiset.algorithms.datastructures.binarysearchtree; + +import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.datastructures.binarysearchtree.SplayTree; import com.williamfiset.algorithms.datastructures.utils.TestUtils; import java.util.*; import org.junit.jupiter.api.Test; -class SplayTreeTest { +public class SplayTreeTest { public static final int MAX = Integer.MAX_VALUE / 4, MIN = Integer.MIN_VALUE / 4; @Test - void getRoot() { + public void getRoot() { SplayTree splayTree = new SplayTree<>(); List data = TestUtils.randomIntegerList(100, MIN, MAX); for (int i : data) { splayTree.insert(i); - assertEquals(i, splayTree.getRoot().getData()); + assertThat(splayTree.getRoot().getData()).isEqualTo(i); } } @Test - void splayInsertDeleteSearch() { + public void splayInsertDeleteSearch() { SplayTree splayTree = new SplayTree<>(); List data = TestUtils.randomUniformUniqueIntegerList( @@ -28,61 +29,61 @@ void splayInsertDeleteSearch() { // should assertNull for (int i : data) { splayTree.insert(i); - assertEquals(i, splayTree.getRoot().getData()); + assertThat(splayTree.getRoot().getData()).isEqualTo(i); } for (int i : data) { - assertNotNull(splayTree.search(i)); + assertThat(splayTree.search(i)).isNotNull(); } for (int i : data) { splayTree.delete(i); - assertNull(splayTree.search(i)); + assertThat(splayTree.search(i)).isNull(); } } @Test - void insertSearch() { + public void insertSearch() { SplayTree splayTree = new SplayTree<>(); List data = TestUtils.randomIntegerList(100, MIN, MAX); for (int i : data) { splayTree.insert(i); - assertEquals(i, splayTree.getRoot().getData()); + assertThat(splayTree.getRoot().getData()).isEqualTo(i); } } @Test - void findMax() { + public void findMax() { SplayTree splayTree = new SplayTree<>(); List data = TestUtils.sortedIntegerList(-50, 50); for (int i : data) { splayTree.insert(i); - assertEquals(i, splayTree.findMax(splayTree.getRoot())); + assertThat(splayTree.findMax(splayTree.getRoot())).isEqualTo(i); } } /** Comparison With Built In Priority Queue* */ @Test - void splayTreePriorityQueueConsistencyTest() { + public void splayTreePriorityQueueConsistencyTest() { SplayTree splayTree = new SplayTree<>(); List data = TestUtils.randomUniformUniqueIntegerList(100); Queue pq = new PriorityQueue<>(100, Collections.reverseOrder()); // insertion for (int i : data) { - assertEquals(pq.add(i), splayTree.insert(i) != null); + assertThat(pq.add(i)).isEqualTo(splayTree.insert(i) != null); } // searching for (int i : data) { - assertEquals(splayTree.search(i).getData().equals(i), pq.contains(i)); + assertThat(splayTree.search(i).getData().equals(i)).isEqualTo(pq.contains(i)); } // findMax & delete while (!pq.isEmpty()) { Integer splayTreeMax = splayTree.findMax(); - assertEquals(pq.peek(), splayTreeMax); + assertThat(pq.peek()).isEqualTo(splayTreeMax); splayTree.delete(splayTreeMax); - assertNull(splayTree.search(splayTreeMax)); + assertThat(splayTree.search(splayTreeMax)).isNull(); pq.remove(splayTreeMax); - assertFalse(pq.contains(splayTreeMax)); + assertThat(pq.contains(splayTreeMax)).isFalse(); } } } diff --git a/javatests/com/williamfiset/algorithms/datastructures/bloomfilter/BloomFilterTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/bloomfilter/BloomFilterTest.java similarity index 87% rename from javatests/com/williamfiset/algorithms/datastructures/bloomfilter/BloomFilterTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/bloomfilter/BloomFilterTest.java index 267df90c3..971018c0b 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/bloomfilter/BloomFilterTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/bloomfilter/BloomFilterTest.java @@ -1,14 +1,12 @@ -package javatests.com.williamfiset.algorithms.datastructures.bloomfilter; +package com.williamfiset.algorithms.datastructures.bloomfilter; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.datastructures.bloomfilter.StringSet; import java.security.SecureRandom; import java.util.HashSet; import java.util.Random; import java.util.Set; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.*; public class BloomFilterTest { @@ -21,7 +19,7 @@ public class BloomFilterTest { static final int TEST_SZ = 1000; static final int LOOPS = 1000; - @Before + @BeforeEach public void setup() {} @Test @@ -34,7 +32,7 @@ public void testStringSetAllSubsets() { for (int i = 0; i < s.length(); i++) { for (int j = i + 1; j < s.length(); j++) { String sub = s.substring(i, j + 1); - assertTrue(set.contains(sub)); + assertThat(set.contains(sub)).isTrue(); } } } @@ -68,7 +66,7 @@ public void testStringSetAllSubsetsFailure() { // The probablity of a collision should be really high // because we're using small prime numbers - assertTrue(collisionHappened); + assertThat(collisionHappened).isTrue(); } @Test @@ -87,7 +85,7 @@ public void containsTests() { set.add(randStr); } - for (String s : javaset) assertTrue(set.contains(s)); + for (String s : javaset) assertThat(set.contains(s)).isTrue(); // Check that strings that aren't in the string set actually aren't // in the set, the probablity should be low enough that a false positive @@ -95,7 +93,7 @@ public void containsTests() { for (int l = 0; l < 100; l++) { String randStr = randomString(sz); if (!randStr.contains(randStr)) { - assertFalse(set.contains(randStr)); + assertThat(set.contains(randStr)).isFalse(); } } } diff --git a/javatests/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeQueryPointUpdateTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeQueryPointUpdateTest.java similarity index 62% rename from javatests/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeQueryPointUpdateTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeQueryPointUpdateTest.java index 28f124285..30cf0caef 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeQueryPointUpdateTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeQueryPointUpdateTest.java @@ -1,11 +1,9 @@ -package javatests.com.williamfiset.algorithms.datastructures.fenwicktree; +package com.williamfiset.algorithms.datastructures.fenwicktree; -import static org.junit.Assert.assertEquals; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; -import com.williamfiset.algorithms.datastructures.fenwicktree.FenwickTreeRangeQueryPointUpdate; -import java.util.*; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.*; public class FenwickTreeRangeQueryPointUpdateTest { @@ -17,7 +15,7 @@ public class FenwickTreeRangeQueryPointUpdateTest { static long UNUSED_VAL; - @Before + @BeforeEach public void setup() { UNUSED_VAL = randValue(); } @@ -29,24 +27,23 @@ public void testIntervalSumPositiveValues() { long[] ar = {UNUSED_VAL, 1, 2, 3, 4, 5, 6}; FenwickTreeRangeQueryPointUpdate ft = new FenwickTreeRangeQueryPointUpdate(ar); - assertEquals(21, ft.sum(1, 6)); - assertEquals(15, ft.sum(1, 5)); - assertEquals(10, ft.sum(1, 4)); - assertEquals(6, ft.sum(1, 3)); - assertEquals(3, ft.sum(1, 2)); - assertEquals(1, ft.sum(1, 1)); - // assertEquals( 0, ft.sum(1, 0) ); - - assertEquals(7, ft.sum(3, 4)); - assertEquals(20, ft.sum(2, 6)); - assertEquals(9, ft.sum(4, 5)); - - assertEquals(6, ft.sum(6, 6)); - assertEquals(5, ft.sum(5, 5)); - assertEquals(4, ft.sum(4, 4)); - assertEquals(3, ft.sum(3, 3)); - assertEquals(2, ft.sum(2, 2)); - assertEquals(1, ft.sum(1, 1)); + assertThat(ft.sum(1, 6)).isEqualTo(21); + assertThat(ft.sum(1, 5)).isEqualTo(15); + assertThat(ft.sum(1, 4)).isEqualTo(10); + assertThat(ft.sum(1, 3)).isEqualTo(6); + assertThat(ft.sum(1, 2)).isEqualTo(3); + assertThat(ft.sum(1, 1)).isEqualTo(1); + // assertThat(ft.sum(1, 0)).isEqualTo(0); + + assertThat(ft.sum(3, 4)).isEqualTo(7); + assertThat(ft.sum(2, 6)).isEqualTo(20); + assertThat(ft.sum(4, 5)).isEqualTo(9); + assertThat(ft.sum(6, 6)).isEqualTo(6); + assertThat(ft.sum(5, 5)).isEqualTo(5); + assertThat(ft.sum(4, 4)).isEqualTo(4); + assertThat(ft.sum(3, 3)).isEqualTo(3); + assertThat(ft.sum(2, 2)).isEqualTo(2); + assertThat(ft.sum(1, 1)).isEqualTo(1); } @Test @@ -56,19 +53,19 @@ public void testIntervalSumNegativeValues() { long[] ar = {UNUSED_VAL, -1, -2, -3, -4, -5, -6}; FenwickTreeRangeQueryPointUpdate ft = new FenwickTreeRangeQueryPointUpdate(ar); - assertEquals(-21, ft.sum(1, 6)); - assertEquals(-15, ft.sum(1, 5)); - assertEquals(-10, ft.sum(1, 4)); - assertEquals(-6, ft.sum(1, 3)); - assertEquals(-3, ft.sum(1, 2)); - assertEquals(-1, ft.sum(1, 1)); - - assertEquals(-6, ft.sum(6, 6)); - assertEquals(-5, ft.sum(5, 5)); - assertEquals(-4, ft.sum(4, 4)); - assertEquals(-3, ft.sum(3, 3)); - assertEquals(-2, ft.sum(2, 2)); - assertEquals(-1, ft.sum(1, 1)); + assertThat(ft.sum(1, 6)).isEqualTo(-21); + assertThat(ft.sum(1, 5)).isEqualTo(-15); + assertThat(ft.sum(1, 4)).isEqualTo(-10); + assertThat(ft.sum(1, 3)).isEqualTo(-6); + assertThat(ft.sum(1, 2)).isEqualTo(-3); + assertThat(ft.sum(1, 1)).isEqualTo(-1); + + assertThat(ft.sum(6, 6)).isEqualTo(-6); + assertThat(ft.sum(5, 5)).isEqualTo(-5); + assertThat(ft.sum(4, 4)).isEqualTo(-4); + assertThat(ft.sum(3, 3)).isEqualTo(-3); + assertThat(ft.sum(2, 2)).isEqualTo(-2); + assertThat(ft.sum(1, 1)).isEqualTo(-1); } @Test @@ -79,14 +76,14 @@ public void testIntervalSumNegativeValues2() { FenwickTreeRangeQueryPointUpdate ft = new FenwickTreeRangeQueryPointUpdate(ar); for (int i = 0; i < LOOPS; i++) { - assertEquals(-76871, ft.sum(1, 1)); - assertEquals(-76871, ft.sum(1, 1)); - assertEquals(-241661, ft.sum(1, 2)); - assertEquals(-241661, ft.sum(1, 2)); - assertEquals(-241661, ft.sum(1, 2)); - assertEquals(-164790, ft.sum(2, 2)); - assertEquals(-164790, ft.sum(2, 2)); - assertEquals(-164790, ft.sum(2, 2)); + assertThat(ft.sum(1, 1)).isEqualTo(-76871); + assertThat(ft.sum(1, 1)).isEqualTo(-76871); + assertThat(ft.sum(1, 2)).isEqualTo(-241661); + assertThat(ft.sum(1, 2)).isEqualTo(-241661); + assertThat(ft.sum(1, 2)).isEqualTo(-241661); + assertThat(ft.sum(2, 2)).isEqualTo(-164790); + assertThat(ft.sum(2, 2)).isEqualTo(-164790); + assertThat(ft.sum(2, 2)).isEqualTo(-164790); } } @@ -117,7 +114,7 @@ public void doRandomRangeQuery(long[] arr, FenwickTreeRangeQueryPointUpdate ft) for (int k = lo; k <= hi; k++) sum += arr[k]; - assertEquals(sum, ft.sum(lo, hi)); + assertThat(ft.sum(lo, hi)).isEqualTo(sum); } @Test @@ -172,9 +169,9 @@ public static long randValue() { return (long) (Math.random() * MAX_RAND_NUM * 2) + MIN_RAND_NUM; } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalCreation() { - new FenwickTreeRangeQueryPointUpdate(null); + assertThrows(IllegalArgumentException.class, () -> new FenwickTreeRangeQueryPointUpdate(null)); } // Generate a list of random numbers, one based diff --git a/javatests/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeUpdatePointQueryTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeUpdatePointQueryTest.java similarity index 71% rename from javatests/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeUpdatePointQueryTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeUpdatePointQueryTest.java index 8016e1f6d..13e34e35f 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeUpdatePointQueryTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/fenwicktree/FenwickTreeRangeUpdatePointQueryTest.java @@ -1,10 +1,9 @@ -package javatests.com.williamfiset.algorithms.datastructures.fenwicktree; +package com.williamfiset.algorithms.datastructures.fenwicktree; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; -import com.williamfiset.algorithms.datastructures.fenwicktree.FenwickTreeRangeUpdatePointQuery; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.*; public class FenwickTreeRangeUpdatePointQueryTest { @@ -32,14 +31,14 @@ public void updateRange(int i, int j, long v) { static long UNUSED_VAL; - @Before + @BeforeEach public void setup() { UNUSED_VAL = randValue(); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalCreation() { - new FenwickTreeRangeUpdatePointQuery(null); + assertThrows(IllegalArgumentException.class, () -> new FenwickTreeRangeUpdatePointQuery(null)); } @Test @@ -48,11 +47,11 @@ public void testFenwickTreeRangeUpdatePointQueryNegativeNumbers() { long[] values = {UNUSED_VAL, -1, -1, -1, -1, -1}; FenwickTreeRangeUpdatePointQuery ft = new FenwickTreeRangeUpdatePointQuery(values); ft.updateRange(2, 4, 10); - assertEquals(-1, ft.get(1)); - assertEquals(9, ft.get(2)); - assertEquals(9, ft.get(3)); - assertEquals(9, ft.get(4)); - assertEquals(-1, ft.get(5)); + assertThat(ft.get(1)).isEqualTo(-1); + assertThat(ft.get(2)).isEqualTo(9); + assertThat(ft.get(3)).isEqualTo(9); + assertThat(ft.get(4)).isEqualTo(9); + assertThat(ft.get(5)).isEqualTo(-1); } @Test @@ -61,11 +60,11 @@ public void testFenwickTreeRangeUpdatePointQuerySimple() { long[] values = {UNUSED_VAL, 2, 3, 4, 5, 6}; FenwickTreeRangeUpdatePointQuery ft = new FenwickTreeRangeUpdatePointQuery(values); ft.updateRange(2, 4, 10); - assertEquals(2, ft.get(1)); - assertEquals(13, ft.get(2)); - assertEquals(14, ft.get(3)); - assertEquals(15, ft.get(4)); - assertEquals(6, ft.get(5)); + assertThat(ft.get(1)).isEqualTo(2); + assertThat(ft.get(2)).isEqualTo(13); + assertThat(ft.get(3)).isEqualTo(14); + assertThat(ft.get(4)).isEqualTo(15); + assertThat(ft.get(5)).isEqualTo(6); } @Test @@ -74,11 +73,11 @@ public void testFenwickTreeRangeUpdatePointQuerySimple2() { long[] values = {UNUSED_VAL, 2, -3, -4, 5, 6}; FenwickTreeRangeUpdatePointQuery ft = new FenwickTreeRangeUpdatePointQuery(values); ft.updateRange(2, 4, 10); - assertEquals(2, ft.get(1)); - assertEquals(7, ft.get(2)); - assertEquals(6, ft.get(3)); - assertEquals(15, ft.get(4)); - assertEquals(6, ft.get(5)); + assertThat(ft.get(1)).isEqualTo(2); + assertThat(ft.get(2)).isEqualTo(7); + assertThat(ft.get(3)).isEqualTo(6); + assertThat(ft.get(4)).isEqualTo(15); + assertThat(ft.get(5)).isEqualTo(6); } @Test @@ -93,7 +92,7 @@ public void testFenwickTreeRangeUpdatePointQueryRepeatedAddition() { int delta = 10; for (int loop = 0; loop < TEST_SZ; loop++) { - for (int i = 1; i < n; i++) assertEquals(sum, ft.get(i)); + for (int i = 1; i < n; i++) assertThat(ft.get(i)).isEqualTo(sum); ft.updateRange(1, n - 1, delta); sum += delta; } @@ -112,7 +111,7 @@ public void testFenwickTreeRangeUpdatePointQueryOverlappingRanges() { for (int loop = 0; loop < TEST_SZ; loop++) { - for (int i = 1; i < n; i++) assertEquals(mockedFt.get(i), ft.get(i)); + for (int i = 1; i < n; i++) assertThat(ft.get(i)).isEqualTo(mockedFt.get(i)); long delta = randValue(); int lo = lowBound(n); diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/fibonacciheap/FibonacciHeapTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/fibonacciheap/FibonacciHeapTest.java new file mode 100644 index 000000000..ec59f0bab --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/fibonacciheap/FibonacciHeapTest.java @@ -0,0 +1,163 @@ +package com.williamfiset.algorithms.datastructures.fibonacciheap; + +import static com.google.common.truth.Truth.assertThat; +import static java.util.Collections.sort; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.Random; +import org.junit.jupiter.api.*; + +// Disclaimer: Based by help of +// "http://langrsoft.com/jeff/2011/11/test-driving-a-heap-based-priority-queue/">Test-Driving a +// Heap-Based Priority Queue +// Credits to the respecti owner for code + +public final class FibonacciHeapTest { + + private Queue queue; + + @BeforeEach + public void setUp() { + queue = new FibonacciHeap(); + } + + @AfterEach + public void tearDown() { + queue = null; + } + + @Test + public void emptyWhenCreated() { + assertThat(queue.isEmpty()).isEqualTo(true); + assertThat(queue.poll()).isEqualTo(null); + } + + @Test + public void noLongerEmptyAfterAdd() { + queue.add(50); + + assertThat(queue.isEmpty()).isFalse(); + } + + @Test + public void singletonQueueReturnsSoleItemOnPoll() { + queue.add(50); + + assertThat(queue.poll()).isEqualTo(50); + } + + @Test + public void isEmptyAfterSoleElementRemoved() { + queue.add(50); + queue.poll(); + + assertThat(queue.isEmpty()).isEqualTo(true); + } + + @Test + public void returnsOrderedItems() { + queue.add(100); + queue.add(50); + + assertThat(queue.poll()).isEqualTo(50); + assertThat(queue.poll()).isEqualTo(100); + assertThat(queue.isEmpty()).isEqualTo(true); + } + + @Test + public void insertSingleItem() { + queue.add(50); + + assertThat(queue.poll()).isEqualTo(50); + assertThat(queue.isEmpty()).isEqualTo(true); + } + + @Test + public void insertSameValuesAndReturnsOrderedItems() { + queue.add(50); + queue.add(100); + queue.add(50); + + assertThat(queue.poll()).isEqualTo(50); + assertThat(queue.poll()).isEqualTo(50); + assertThat(queue.poll()).isEqualTo(100); + assertThat(queue.isEmpty()).isEqualTo(true); + } + + @Test + public void returnsOrderedItemsFromRandomInsert() { + final Random r = new Random(System.currentTimeMillis()); + final List expected = new ArrayList(); + + for (int i = 0; i < 1000; i++) { + Integer number = r.nextInt(10000); + expected.add(number); + queue.add(number); + } + sort(expected); + + for (Integer integer : expected) { + Integer i = queue.poll(); + assertThat(i).isEqualTo(integer); + } + + assertThat(queue.isEmpty()).isEqualTo(true); + } + + @Test + public void addAllAndContinsItem() { + Collection c = new ArrayList(); + + c.add(50); + c.add(100); + c.add(20); + c.add(21); + + queue.addAll(c); + + assertThat(queue.isEmpty()).isEqualTo(false); + assertThat(queue.containsAll(c)).isEqualTo(true); + + assertThat(queue.contains(100)).isEqualTo(true); + assertThat(queue.contains(21)).isEqualTo(true); + assertThat(queue.contains(50)).isEqualTo(true); + assertThat(queue.contains(20)).isEqualTo(true); + } + + @Test + public void clearQueue() { + final Random r = new Random(System.currentTimeMillis()); + for (int i = 0; i < 1000; i++) { + Integer number = r.nextInt(10000); + queue.add(number); + } + + assertThat(queue.isEmpty()).isEqualTo(false); + queue.clear(); + assertThat(queue.isEmpty()).isEqualTo(true); + } + + @Test + public void offerPeekAndElement() { + queue.offer(50); + queue.offer(100); + queue.offer(20); + queue.offer(21); + + assertThat(queue.isEmpty()).isFalse(); + ; + assertThat(queue.peek()).isEqualTo(20); + assertThat(queue.element()).isEqualTo(20); + assertThat(queue.size()).isEqualTo(4); + } + + @Test + public void elementThrowsException() { + assertThrows(NoSuchElementException.class, () -> queue.element()); + } +} diff --git a/com/williamfiset/algorithms/datastructures/hashtable/Benchmark.java b/src/test/java/com/williamfiset/algorithms/datastructures/hashtable/Benchmark.java similarity index 100% rename from com/williamfiset/algorithms/datastructures/hashtable/Benchmark.java rename to src/test/java/com/williamfiset/algorithms/datastructures/hashtable/Benchmark.java diff --git a/com/williamfiset/algorithms/datastructures/hashtable/DoubleHashingTestObject.java b/src/test/java/com/williamfiset/algorithms/datastructures/hashtable/DoubleHashingTestObject.java similarity index 83% rename from com/williamfiset/algorithms/datastructures/hashtable/DoubleHashingTestObject.java rename to src/test/java/com/williamfiset/algorithms/datastructures/hashtable/DoubleHashingTestObject.java index 3e1395032..a65c6c85d 100644 --- a/com/williamfiset/algorithms/datastructures/hashtable/DoubleHashingTestObject.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/hashtable/DoubleHashingTestObject.java @@ -88,10 +88,13 @@ public int hashCode2() { @Override public boolean equals(Object o) { - DoubleHashingTestObject obj = (DoubleHashingTestObject) o; - if (hash != obj.hash) return false; - if (intData != null) return intData.equals(obj.intData); - if (vectorData != null) return java.util.Arrays.equals(vectorData, obj.vectorData); - return stringData.equals(obj.stringData); + if (this == o) return true; + else if (o instanceof DoubleHashingTestObject) { + DoubleHashingTestObject obj = (DoubleHashingTestObject) o; + if (hash != obj.hash) return false; + if (intData != null) return intData.equals(obj.intData); + if (vectorData != null) return java.util.Arrays.equals(vectorData, obj.vectorData); + return stringData.equals(obj.stringData); + } else return false; } } diff --git a/javatests/com/williamfiset/algorithms/datastructures/hashtable/HashTableDoubleHashingTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableDoubleHashingTest.java similarity index 64% rename from javatests/com/williamfiset/algorithms/datastructures/hashtable/HashTableDoubleHashingTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableDoubleHashingTest.java index c3592b6fa..982658a61 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/hashtable/HashTableDoubleHashingTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableDoubleHashingTest.java @@ -1,12 +1,10 @@ -package javatests.com.williamfiset.algorithms.datastructures.hashtable; +package com.williamfiset.algorithms.datastructures.hashtable; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.*; -import com.williamfiset.algorithms.datastructures.hashtable.DoubleHashingTestObject; // Move to test - // dir? -import com.williamfiset.algorithms.datastructures.hashtable.HashTableDoubleHashing; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class HashTableDoubleHashingTest { @@ -14,42 +12,39 @@ public class HashTableDoubleHashingTest { static int LOOPS, MAX_SIZE, MAX_RAND_NUM; static { - LOOPS = randInt(25000, 75000); + LOOPS = 500; MAX_SIZE = randInt(1, 750); MAX_RAND_NUM = randInt(1, 350); } HashTableDoubleHashing map; - @Before + @BeforeEach public void setup() { map = new HashTableDoubleHashing<>(); } - @Test(expected = IllegalArgumentException.class) - public void testNullKey() { - map.put(null, 5); - } - - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalCreation1() { - new HashTableDoubleHashing<>(-3, 0.5); + assertThrows(IllegalArgumentException.class, () -> new HashTableDoubleHashing<>(-3, 0.5)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalCreation2() { - new HashTableDoubleHashing<>(5, Double.POSITIVE_INFINITY); + assertThrows( + IllegalArgumentException.class, + () -> new HashTableDoubleHashing<>(5, Double.POSITIVE_INFINITY)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalCreation3() { - new HashTableDoubleHashing<>(6, -0.5); + assertThrows(IllegalArgumentException.class, () -> new HashTableDoubleHashing<>(6, -0.5)); } @Test public void testLegalCreation() { // System.out.println("testLegalCreation"); - new HashTableDoubleHashing<>(6, 0.9); + assertDoesNotThrow(() -> new HashTableDoubleHashing<>(6, 0.9)); } @Test @@ -60,13 +55,13 @@ public void testUpdatingValue() { DoubleHashingTestObject on7 = new DoubleHashingTestObject(-7); map.add(o1, 1); - assertTrue(1 == map.get(o1)); + assertThat(map.get(o1)).isEqualTo(1); map.add(o5, 5); - assertTrue(5 == map.get(o5)); + assertThat(map.get(o5)).isEqualTo(5); map.add(on7, -7); - assertTrue(-7 == map.get(on7)); + assertThat(map.get(on7)).isEqualTo(-7); } @Test @@ -81,23 +76,23 @@ public void testIterator() { mmap.clear(); jmap.clear(); - assertTrue(mmap.isEmpty()); + assertThat(mmap.isEmpty()).isTrue(); List rand_nums = genRandList(MAX_SIZE); for (DoubleHashingTestObject key : rand_nums) - assertEquals(mmap.add(key, key), jmap.put(key, key)); + assertThat(mmap.add(key, key)).isEqualTo(jmap.put(key, key)); int count = 0; for (DoubleHashingTestObject key : mmap) { - assertEquals(key, mmap.get(key)); - assertEquals(mmap.get(key), jmap.get(key)); - assertTrue(mmap.hasKey(key)); - assertTrue(rand_nums.contains(key)); + assertThat(mmap.get(key)).isEqualTo(key); + assertThat(mmap.get(key)).isEqualTo(jmap.get(key)); + assertThat(mmap.hasKey(key)).isTrue(); + assertThat(rand_nums.contains(key)).isTrue(); count++; } for (DoubleHashingTestObject key : jmap.keySet()) { - assertEquals(key, mmap.get(key)); + assertThat(mmap.get(key)).isEqualTo(key); } Set set = new HashSet<>(); @@ -105,33 +100,41 @@ public void testIterator() { // System.out.println(set.size() + " " + jmap.size() + " " + count); - assertEquals(set.size(), count); - assertEquals(jmap.size(), count); + assertThat(set.size()).isEqualTo(count); + assertThat(jmap.size()).isEqualTo(count); } } - @Test(expected = java.util.ConcurrentModificationException.class) + @Test public void testConcurrentModificationException() { - // System.out.println("testConcurrentModificationException"); - DoubleHashingTestObject o1 = new DoubleHashingTestObject(1); - DoubleHashingTestObject o2 = new DoubleHashingTestObject(2); - DoubleHashingTestObject o3 = new DoubleHashingTestObject(3); - DoubleHashingTestObject o4 = new DoubleHashingTestObject(4); - map.add(o1, 1); - map.add(o2, 1); - map.add(o3, 1); - for (DoubleHashingTestObject key : map) map.add(o4, 4); + assertThrows( + ConcurrentModificationException.class, + () -> { + // System.out.println("testConcurrentModificationException"); + DoubleHashingTestObject o1 = new DoubleHashingTestObject(1); + DoubleHashingTestObject o2 = new DoubleHashingTestObject(2); + DoubleHashingTestObject o3 = new DoubleHashingTestObject(3); + DoubleHashingTestObject o4 = new DoubleHashingTestObject(4); + map.add(o1, 1); + map.add(o2, 1); + map.add(o3, 1); + for (DoubleHashingTestObject key : map) map.add(o4, 4); + }); } - @Test(expected = java.util.ConcurrentModificationException.class) + @Test public void testConcurrentModificationException2() { - DoubleHashingTestObject o1 = new DoubleHashingTestObject(1); - DoubleHashingTestObject o2 = new DoubleHashingTestObject(2); - DoubleHashingTestObject o3 = new DoubleHashingTestObject(3); - map.add(o1, 1); - map.add(o2, 1); - map.add(o3, 1); - for (DoubleHashingTestObject key : map) map.remove(o2); + assertThrows( + ConcurrentModificationException.class, + () -> { + DoubleHashingTestObject o1 = new DoubleHashingTestObject(1); + DoubleHashingTestObject o2 = new DoubleHashingTestObject(2); + DoubleHashingTestObject o3 = new DoubleHashingTestObject(3); + map.add(o1, 1); + map.add(o2, 1); + map.add(o3, 1); + for (DoubleHashingTestObject key : map) map.remove(o2); + }); } @Test @@ -153,12 +156,12 @@ public void randomRemove() { map.put(obj, 5); } - assertEquals(map.size(), keys_set.size()); + assertThat(map.size()).isEqualTo(keys_set.size()); List keys = map.keys(); for (DoubleHashingTestObject key : keys) map.remove(key); - assertTrue(map.isEmpty()); + assertThat(map.isEmpty()).isTrue(); } } @@ -175,21 +178,21 @@ public void removeTest() { map.put(o11, 0); map.put(o12, 0); map.put(o13, 0); - assertEquals(3, map.size()); + assertThat(map.size()).isEqualTo(3); // Add ten more for (int i = 1; i <= 10; i++) map.put(new DoubleHashingTestObject(i), 0); - assertEquals(13, map.size()); + assertThat(map.size()).isEqualTo(13); // Remove ten for (int i = 1; i <= 10; i++) map.remove(new DoubleHashingTestObject(i)); - assertEquals(3, map.size()); + assertThat(map.size()).isEqualTo(3); // remove three map.remove(o11); map.remove(o12); map.remove(o13); - assertEquals(0, map.size()); + assertThat(map.size()).isEqualTo(0); } @Test @@ -201,7 +204,7 @@ public void testRandomMapOperations() { map.clear(); jmap.clear(); - assertEquals(jmap.size(), map.size()); + assertThat(jmap.size()).isEqualTo(map.size()); map = new HashTableDoubleHashing<>(); @@ -216,17 +219,17 @@ public void testRandomMapOperations() { DoubleHashingTestObject key = nums.get(i); int val = i; - if (r < probability1) assertEquals(jmap.put(key, val), map.put(key, val)); + if (r < probability1) assertThat(jmap.put(key, val)).isEqualTo(map.put(key, val)); - assertEquals(jmap.get(key), map.get(key)); - assertEquals(jmap.containsKey(key), map.containsKey(key)); - assertEquals(jmap.size(), map.size()); + assertThat(jmap.get(key)).isEqualTo(map.get(key)); + assertThat(jmap.containsKey(key)).isEqualTo(map.containsKey(key)); + assertThat(jmap.size()).isEqualTo(map.size()); - if (r > probability2) assertEquals(map.remove(key), jmap.remove(key)); + if (r > probability2) assertThat(map.remove(key)).isEqualTo(jmap.remove(key)); - assertEquals(jmap.get(key), map.get(key)); - assertEquals(jmap.containsKey(key), map.containsKey(key)); - assertEquals(jmap.size(), map.size()); + assertThat(jmap.get(key)).isEqualTo(map.get(key)); + assertThat(jmap.containsKey(key)).isEqualTo(map.containsKey(key)); + assertThat(jmap.size()).isEqualTo(map.size()); } } } @@ -242,7 +245,7 @@ public void randomIteratorTests() { m.clear(); hm.clear(); - assertEquals(m.size(), hm.size()); + assertThat(m.size()).isEqualTo(hm.size()); int sz = randInt(1, MAX_SIZE); m = new HashTableDoubleHashing<>(sz); @@ -277,8 +280,8 @@ public void randomIteratorTests() { l2.add(randVal); } - assertEquals(m.size(), hm.size()); - assertEquals(l1, l2); + assertThat(m.size()).isEqualTo(hm.size()); + assertThat(l1).isEqualTo(l2); } } } diff --git a/javatests/com/williamfiset/algorithms/datastructures/hashtable/HashTableLinearProbingTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableLinearProbingTest.java similarity index 64% rename from javatests/com/williamfiset/algorithms/datastructures/hashtable/HashTableLinearProbingTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableLinearProbingTest.java index df23fc915..f18806fd0 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/hashtable/HashTableLinearProbingTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableLinearProbingTest.java @@ -1,10 +1,10 @@ -package javatests.com.williamfiset.algorithms.datastructures.hashtable; +package com.williamfiset.algorithms.datastructures.hashtable; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.*; -import com.williamfiset.algorithms.datastructures.hashtable.HashTableLinearProbing; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class HashTableLinearProbingTest { @@ -25,8 +25,12 @@ public int hashCode() { @Override public boolean equals(Object o) { - HashObject ho = (HashObject) o; - return hashCode() == ho.hashCode() && data == ho.data; + if (this == o) return true; + else if (o instanceof HashObject) { + HashObject ho = (HashObject) o; + return hashCode() == ho.hashCode() && data == ho.data; + } + return false; } } @@ -34,54 +38,56 @@ public boolean equals(Object o) { static int LOOPS, MAX_SIZE, MAX_RAND_NUM; static { - LOOPS = randInt(25000, 75000); + LOOPS = 500; MAX_SIZE = randInt(1, 750); MAX_RAND_NUM = randInt(1, 350); } HashTableLinearProbing map; - @Before + @BeforeEach public void setup() { map = new HashTableLinearProbing<>(); } - @Test(expected = IllegalArgumentException.class) + @Test public void testNullKey() { - map.put(null, 5); + assertThrows(IllegalArgumentException.class, () -> map.put(null, 5)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalCreation1() { - new HashTableLinearProbing<>(-3, 0.5); + assertThrows(IllegalArgumentException.class, () -> new HashTableLinearProbing<>(-3, 0.5)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalCreation2() { - new HashTableLinearProbing<>(5, Double.POSITIVE_INFINITY); + assertThrows( + IllegalArgumentException.class, + () -> new HashTableLinearProbing<>(5, Double.POSITIVE_INFINITY)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalCreation3() { - new HashTableLinearProbing<>(6, -0.5); + assertThrows(IllegalArgumentException.class, () -> new HashTableLinearProbing<>(6, -0.5)); } @Test public void testLegalCreation() { - new HashTableLinearProbing<>(6, 0.9); + assertDoesNotThrow(() -> new HashTableLinearProbing<>(6, 0.9)); } @Test public void testUpdatingValue() { map.add(1, 1); - assertTrue(1 == map.get(1)); + assertThat(map.get(1)).isEqualTo(1); map.add(1, 5); - assertTrue(5 == map.get(1)); + assertThat(map.get(1)).isEqualTo(5); map.add(1, -7); - assertTrue(-7 == map.get(1)); + assertThat(map.get(1)).isEqualTo(-7); } @Test @@ -93,48 +99,56 @@ public void testIterator() { map.clear(); map2.clear(); - assertTrue(map.isEmpty()); + assertThat(map.isEmpty()).isTrue(); map = new HashTableLinearProbing<>(); List rand_nums = genRandList(MAX_SIZE); - for (Integer key : rand_nums) assertEquals(map.add(key, key), map2.put(key, key)); + for (Integer key : rand_nums) assertThat(map.add(key, key)).isEqualTo(map2.put(key, key)); int count = 0; for (Integer key : map) { - assertEquals(key, map.get(key)); - assertEquals(map.get(key), map2.get(key)); - assertTrue(map.hasKey(key)); - assertTrue(rand_nums.contains(key)); + assertThat(map.get(key)).isEqualTo(key); + assertThat(map.get(key)).isEqualTo(map2.get(key)); + assertThat(map.hasKey(key)).isTrue(); + assertThat(rand_nums.contains(key)).isTrue(); count++; } for (Integer key : map2.keySet()) { - assertEquals(key, map.get(key)); + assertThat(map.get(key)).isEqualTo(key); } Set set = new HashSet<>(); for (int n : rand_nums) set.add(n); - assertEquals(set.size(), count); - assertEquals(map2.size(), count); + assertThat(set.size()).isEqualTo(count); + assertThat(map2.size()).isEqualTo(count); } } - @Test(expected = java.util.ConcurrentModificationException.class) + @Test public void testConcurrentModificationException() { - map.add(1, 1); - map.add(2, 1); - map.add(3, 1); - for (Integer key : map) map.add(4, 4); + assertThrows( + ConcurrentModificationException.class, + () -> { + map.add(1, 1); + map.add(2, 1); + map.add(3, 1); + for (Integer key : map) map.add(4, 4); + }); } - @Test(expected = java.util.ConcurrentModificationException.class) + @Test public void testConcurrentModificationException2() { - map.add(1, 1); - map.add(2, 1); - map.add(3, 1); - for (Integer key : map) map.remove(2); + assertThrows( + ConcurrentModificationException.class, + () -> { + map.add(1, 1); + map.add(2, 1); + map.add(3, 1); + for (Integer key : map) map.remove(2); + }); } @Test @@ -155,12 +169,12 @@ public void randomRemove() { map.put(randomVal, 5); } - assertEquals(map.size(), keys_set.size()); + assertThat(map.size()).isEqualTo(keys_set.size()); List keys = map.keys(); for (Integer key : keys) map.remove(key); - assertTrue(map.isEmpty()); + assertThat(map.isEmpty()).isTrue(); } } @@ -173,21 +187,21 @@ public void removeTest() { map.put(11, 0); map.put(12, 0); map.put(13, 0); - assertEquals(3, map.size()); + assertThat(map.size()).isEqualTo(3); // Add ten more for (int i = 1; i <= 10; i++) map.put(i, 0); - assertEquals(13, map.size()); + assertThat(map.size()).isEqualTo(13); // Remove ten for (int i = 1; i <= 10; i++) map.remove(i); - assertEquals(3, map.size()); + assertThat(map.size()).isEqualTo(3); // remove three map.remove(11); map.remove(12); map.remove(13); - assertEquals(0, map.size()); + assertThat(map.size()).isEqualTo(0); } @Test @@ -210,7 +224,7 @@ public void removeTestComplex1() { map.remove(o1); map.remove(o4); - assertEquals(0, map.size()); + assertThat(map.size()).isEqualTo(0); } @Test @@ -222,7 +236,7 @@ public void testRandomMapOperations() { map.clear(); jmap.clear(); - assertEquals(jmap.size(), map.size()); + assertThat(jmap.size()).isEqualTo(map.size()); map = new HashTableLinearProbing<>(); @@ -237,17 +251,17 @@ public void testRandomMapOperations() { int key = nums.get(i); int val = i; - if (r < probability1) assertEquals(jmap.put(key, val), map.put(key, val)); + if (r < probability1) assertThat(jmap.put(key, val)).isEqualTo(map.put(key, val)); - assertEquals(jmap.get(key), map.get(key)); - assertEquals(jmap.containsKey(key), map.containsKey(key)); - assertEquals(jmap.size(), map.size()); + assertThat(jmap.get(key)).isEqualTo(map.get(key)); + assertThat(jmap.containsKey(key)).isEqualTo(map.containsKey(key)); + assertThat(jmap.size()).isEqualTo(map.size()); - if (r > probability2) assertEquals(map.remove(key), jmap.remove(key)); + if (r > probability2) assertThat(map.remove(key)).isEqualTo(jmap.remove(key)); - assertEquals(jmap.get(key), map.get(key)); - assertEquals(jmap.containsKey(key), map.containsKey(key)); - assertEquals(jmap.size(), map.size()); + assertThat(jmap.get(key)).isEqualTo(map.get(key)); + assertThat(jmap.containsKey(key)).isEqualTo(map.containsKey(key)); + assertThat(jmap.size()).isEqualTo(map.size()); } } } @@ -262,7 +276,7 @@ public void randomIteratorTests() { m.clear(); hm.clear(); - assertEquals(m.size(), hm.size()); + assertThat(m.size()).isEqualTo(hm.size()); int sz = randInt(1, MAX_SIZE); m = new HashTableLinearProbing<>(sz); @@ -296,8 +310,8 @@ public void randomIteratorTests() { l2.add(rand_val); } - assertEquals(m.size(), hm.size()); - assertEquals(l1, l2); + assertThat(m.size()).isEqualTo(hm.size()); + assertThat(l1).isEqualTo(l2); } } } diff --git a/javatests/com/williamfiset/algorithms/datastructures/hashtable/HashTableQuadraticProbingTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableQuadraticProbingTest.java similarity index 66% rename from javatests/com/williamfiset/algorithms/datastructures/hashtable/HashTableQuadraticProbingTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableQuadraticProbingTest.java index e36cde05a..13c1daad5 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/hashtable/HashTableQuadraticProbingTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableQuadraticProbingTest.java @@ -1,10 +1,10 @@ -package javatests.com.williamfiset.algorithms.datastructures.hashtable; +package com.williamfiset.algorithms.datastructures.hashtable; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.*; -import com.williamfiset.algorithms.datastructures.hashtable.HashTableQuadraticProbing; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class HashTableQuadraticProbingTest { @@ -25,8 +25,11 @@ public int hashCode() { @Override public boolean equals(Object o) { - HashObject ho = (HashObject) o; - return hashCode() == ho.hashCode() && data == ho.data; + if (this == o) return true; + else if (o instanceof HashObject) { + HashObject ho = (HashObject) o; + return hashCode() == ho.hashCode() && data == ho.data; + } else return false; } } @@ -34,60 +37,62 @@ public boolean equals(Object o) { static int LOOPS, MAX_SIZE, MAX_RAND_NUM; static { - LOOPS = randInt(25000, 75000); + LOOPS = 500; MAX_SIZE = randInt(1, 750); MAX_RAND_NUM = randInt(1, 350); } HashTableQuadraticProbing map; - @Before + @BeforeEach public void setup() { map = new HashTableQuadraticProbing<>(); } - @Test(expected = IllegalArgumentException.class) + @Test public void testNullKey() { - map.put(null, 5); + assertThrows(IllegalArgumentException.class, () -> map.put(null, 5)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalCreation1() { - new HashTableQuadraticProbing<>(-3, 0.5); + assertThrows(IllegalArgumentException.class, () -> new HashTableQuadraticProbing<>(-3, 0.5)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalCreation2() { - new HashTableQuadraticProbing<>(5, Double.POSITIVE_INFINITY); + assertThrows( + IllegalArgumentException.class, + () -> new HashTableQuadraticProbing<>(5, Double.POSITIVE_INFINITY)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalCreation3() { - new HashTableQuadraticProbing<>(6, -0.5); + assertThrows(IllegalArgumentException.class, () -> new HashTableQuadraticProbing<>(6, -0.5)); } @Test public void testLegalCreation() { - new HashTableQuadraticProbing<>(6, 0.9); + assertDoesNotThrow(() -> new HashTableQuadraticProbing<>(6, 0.9)); } @Test public void testUpdatingValue() { map.add(1, 1); - assertTrue(1 == map.get(1)); + assertThat(map.get(1)).isEqualTo(1); map.add(1, 5); - assertTrue(5 == map.get(1)); + assertThat(map.get(1)).isEqualTo(5); map.add(1, -7); - assertTrue(-7 == map.get(1)); + assertThat(map.get(1)).isEqualTo(-7); } private void assertCapacityIsPowerOfTwo(HashTableQuadraticProbing ht) { int sz = ht.getCapacity(); if (sz == 0) return; - assertTrue((sz & (sz - 1)) == 0); + assertThat((sz & (sz - 1))).isEqualTo(0); } // Test that as the table size increases the hashtable @@ -114,48 +119,56 @@ public void testIterator() { map.clear(); map2.clear(); - assertTrue(map.isEmpty()); + assertThat(map.isEmpty()).isTrue(); map = new HashTableQuadraticProbing<>(); List rand_nums = genRandList(MAX_SIZE); - for (Integer key : rand_nums) assertEquals(map.add(key, key), map2.put(key, key)); + for (Integer key : rand_nums) assertThat(map.add(key, key)).isEqualTo(map2.put(key, key)); int count = 0; for (Integer key : map) { - assertEquals(key, map.get(key)); - assertEquals(map.get(key), map2.get(key)); - assertTrue(map.hasKey(key)); - assertTrue(rand_nums.contains(key)); + assertThat(map.get(key)).isEqualTo(key); + assertThat(map.get(key)).isEqualTo(map2.get(key)); + assertThat(map.hasKey(key)).isTrue(); + assertThat(rand_nums.contains(key)).isTrue(); count++; } for (Integer key : map2.keySet()) { - assertEquals(key, map.get(key)); + assertThat(map.get(key)).isEqualTo(key); } Set set = new HashSet<>(); for (int n : rand_nums) set.add(n); - assertEquals(set.size(), count); - assertEquals(map2.size(), count); + assertThat(set.size()).isEqualTo(count); + assertThat(map2.size()).isEqualTo(count); } } - @Test(expected = java.util.ConcurrentModificationException.class) + @Test public void testConcurrentModificationException() { - map.add(1, 1); - map.add(2, 1); - map.add(3, 1); - for (Integer key : map) map.add(4, 4); + assertThrows( + ConcurrentModificationException.class, + () -> { + map.add(1, 1); + map.add(2, 1); + map.add(3, 1); + for (Integer key : map) map.add(4, 4); + }); } - @Test(expected = java.util.ConcurrentModificationException.class) + @Test public void testConcurrentModificationException2() { - map.add(1, 1); - map.add(2, 1); - map.add(3, 1); - for (Integer key : map) map.remove(2); + assertThrows( + ConcurrentModificationException.class, + () -> { + map.add(1, 1); + map.add(2, 1); + map.add(3, 1); + for (Integer key : map) map.remove(2); + }); } @Test @@ -176,12 +189,12 @@ public void randomRemove() { map.put(randomVal, 5); } - assertEquals(map.size(), keys_set.size()); + assertThat(map.size()).isEqualTo(keys_set.size()); List keys = map.keys(); for (Integer key : keys) map.remove(key); - assertTrue(map.isEmpty()); + assertThat(map.isEmpty()).isTrue(); } } @@ -194,21 +207,21 @@ public void removeTest() { map.put(11, 0); map.put(12, 0); map.put(13, 0); - assertEquals(3, map.size()); + assertThat(map.size()).isEqualTo(3); // Add ten more for (int i = 1; i <= 10; i++) map.put(i, 0); - assertEquals(13, map.size()); + assertThat(map.size()).isEqualTo(13); // Remove ten for (int i = 1; i <= 10; i++) map.remove(i); - assertEquals(3, map.size()); + assertThat(map.size()).isEqualTo(3); // remove three map.remove(11); map.remove(12); map.remove(13); - assertEquals(0, map.size()); + assertThat(map.size()).isEqualTo(0); } @Test @@ -231,7 +244,7 @@ public void removeTestComplex1() { map.remove(o1); map.remove(o4); - assertEquals(0, map.size()); + assertThat(map.size()).isEqualTo(0); } @Test @@ -243,7 +256,7 @@ public void testRandomMapOperations() { map.clear(); jmap.clear(); - assertEquals(jmap.size(), map.size()); + assertThat(jmap.size()).isEqualTo(map.size()); map = new HashTableQuadraticProbing<>(); @@ -258,17 +271,17 @@ public void testRandomMapOperations() { int key = nums.get(i); int val = i; - if (r < probability1) assertEquals(jmap.put(key, val), map.put(key, val)); + if (r < probability1) assertThat(jmap.put(key, val)).isEqualTo(map.put(key, val)); - assertEquals(jmap.get(key), map.get(key)); - assertEquals(jmap.containsKey(key), map.containsKey(key)); - assertEquals(jmap.size(), map.size()); + assertThat(jmap.get(key)).isEqualTo(map.get(key)); + assertThat(jmap.containsKey(key)).isEqualTo(map.containsKey(key)); + assertThat(jmap.size()).isEqualTo(map.size()); - if (r > probability2) assertEquals(map.remove(key), jmap.remove(key)); + if (r > probability2) assertThat(map.remove(key)).isEqualTo(jmap.remove(key)); - assertEquals(jmap.get(key), map.get(key)); - assertEquals(jmap.containsKey(key), map.containsKey(key)); - assertEquals(jmap.size(), map.size()); + assertThat(jmap.get(key)).isEqualTo(map.get(key)); + assertThat(jmap.containsKey(key)).isEqualTo(map.containsKey(key)); + assertThat(jmap.size()).isEqualTo(map.size()); } } } @@ -283,7 +296,7 @@ public void randomIteratorTests() { m.clear(); hm.clear(); - assertEquals(m.size(), hm.size()); + assertThat(m.size()).isEqualTo(hm.size()); int sz = randInt(1, MAX_SIZE); m = new HashTableQuadraticProbing<>(sz); @@ -317,8 +330,8 @@ public void randomIteratorTests() { l2.add(rand_val); } - assertEquals(m.size(), hm.size()); - assertEquals(l1, l2); + assertThat(m.size()).isEqualTo(hm.size()); + assertThat(l1).isEqualTo(l2); } } } diff --git a/javatests/com/williamfiset/algorithms/datastructures/hashtable/HashTableSeperateChainingTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableSeparateChainingTest.java similarity index 56% rename from javatests/com/williamfiset/algorithms/datastructures/hashtable/HashTableSeperateChainingTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableSeparateChainingTest.java index 4b3f32c5d..84518b394 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/hashtable/HashTableSeperateChainingTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/hashtable/HashTableSeparateChainingTest.java @@ -1,12 +1,12 @@ -package javatests.com.williamfiset.algorithms.datastructures.hashtable; +package com.williamfiset.algorithms.datastructures.hashtable; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.*; -import com.williamfiset.algorithms.datastructures.hashtable.HashTableSeperateChaining; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; -public class HashTableSeperateChainingTest { +public class HashTableSeparateChainingTest { // You can set the hash value of this object to be whatever you want // This makes it great for testing special cases. @@ -25,8 +25,11 @@ public int hashCode() { @Override public boolean equals(Object o) { - HashObject ho = (HashObject) o; - return hashCode() == ho.hashCode() && data == ho.data; + if (this == o) return true; + else if (o instanceof HashObject) { + HashObject ho = (HashObject) o; + return hashCode() == ho.hashCode() && data == ho.data; + } else return false; } } @@ -34,54 +37,51 @@ public boolean equals(Object o) { static int LOOPS, MAX_SIZE, MAX_RAND_NUM; static { - LOOPS = randInt(25000, 75000); + LOOPS = 500; MAX_SIZE = randInt(1, 750); MAX_RAND_NUM = randInt(1, 350); } - HashTableSeperateChaining map; + HashTableSeparateChaining map; - @Before + @BeforeEach public void setup() { - map = new HashTableSeperateChaining<>(); + map = new HashTableSeparateChaining<>(); } - @Test(expected = IllegalArgumentException.class) - public void testNullKey() { - map.put(null, 5); - } - - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalCreation1() { - new HashTableSeperateChaining<>(-3, 0.5); + assertThrows(IllegalArgumentException.class, () -> new HashTableSeparateChaining<>(-3, 0.5)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalCreation2() { - new HashTableSeperateChaining<>(5, Double.POSITIVE_INFINITY); + assertThrows( + IllegalArgumentException.class, + () -> new HashTableSeparateChaining<>(5, Double.POSITIVE_INFINITY)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalCreation3() { - new HashTableSeperateChaining<>(6, -0.5); + assertThrows(IllegalArgumentException.class, () -> new HashTableSeparateChaining<>(6, -0.5)); } @Test public void testLegalCreation() { - new HashTableSeperateChaining<>(6, 0.9); + assertDoesNotThrow(() -> new HashTableSeparateChaining<>(6, 0.9)); } @Test public void testUpdatingValue() { map.add(1, 1); - assertTrue(1 == map.get(1)); + assertThat(map.get(1)).isEqualTo(1); map.add(1, 5); - assertTrue(5 == map.get(1)); + assertThat(map.get(1)).isEqualTo(5); map.add(1, -7); - assertTrue(-7 == map.get(1)); + assertThat(map.get(1)).isEqualTo(-7); } @Test @@ -93,58 +93,66 @@ public void testIterator() { map.clear(); map2.clear(); - assertTrue(map.isEmpty()); + assertThat(map.isEmpty()).isTrue(); - map = new HashTableSeperateChaining<>(); + map = new HashTableSeparateChaining<>(); List rand_nums = genRandList(MAX_SIZE); - for (Integer key : rand_nums) assertEquals(map.add(key, key), map2.put(key, key)); + for (Integer key : rand_nums) assertThat(map.add(key, key)).isEqualTo(map2.put(key, key)); int count = 0; for (Integer key : map) { - assertEquals(key, map.get(key)); - assertEquals(map.get(key), map2.get(key)); - assertTrue(map.hasKey(key)); - assertTrue(rand_nums.contains(key)); + assertThat(map.get(key)).isEqualTo(key); + assertThat(map.get(key)).isEqualTo(map2.get(key)); + assertThat(map.hasKey(key)).isTrue(); + assertThat(rand_nums.contains(key)).isTrue(); count++; } for (Integer key : map2.keySet()) { - assertEquals(key, map.get(key)); + assertThat(map.get(key)).isEqualTo(key); } Set set = new HashSet<>(); for (int n : rand_nums) set.add(n); - assertEquals(set.size(), count); - assertEquals(map2.size(), count); + assertThat(set.size()).isEqualTo(count); + assertThat(map2.size()).isEqualTo(count); } } - @Test(expected = java.util.ConcurrentModificationException.class) + @Test public void testConcurrentModificationException() { - map.add(1, 1); - map.add(2, 1); - map.add(3, 1); - for (Integer key : map) map.add(4, 4); + assertThrows( + ConcurrentModificationException.class, + () -> { + map.add(1, 1); + map.add(2, 1); + map.add(3, 1); + for (Integer key : map) map.add(4, 4); + }); } - @Test(expected = java.util.ConcurrentModificationException.class) + @Test public void testConcurrentModificationException2() { - map.add(1, 1); - map.add(2, 1); - map.add(3, 1); - for (Integer key : map) map.remove(2); + assertThrows( + ConcurrentModificationException.class, + () -> { + map.add(1, 1); + map.add(2, 1); + map.add(3, 1); + for (Integer key : map) map.remove(2); + }); } @Test public void randomRemove() { - HashTableSeperateChaining map; + HashTableSeparateChaining map; for (int loop = 0; loop < LOOPS; loop++) { - map = new HashTableSeperateChaining<>(); + map = new HashTableSeparateChaining<>(); map.clear(); // Add some random values @@ -155,45 +163,45 @@ public void randomRemove() { map.put(randomVal, 5); } - assertEquals(map.size(), keys_set.size()); + assertThat(map.size()).isEqualTo(keys_set.size()); List keys = map.keys(); for (Integer key : keys) map.remove(key); - assertTrue(map.isEmpty()); + assertThat(map.isEmpty()).isTrue(); } } @Test public void removeTest() { - HashTableSeperateChaining map = new HashTableSeperateChaining<>(7); + HashTableSeparateChaining map = new HashTableSeparateChaining<>(7); // Add three elements map.put(11, 0); map.put(12, 0); map.put(13, 0); - assertEquals(3, map.size()); + assertThat(map.size()).isEqualTo(3); // Add ten more for (int i = 1; i <= 10; i++) map.put(i, 0); - assertEquals(13, map.size()); + assertThat(map.size()).isEqualTo(13); // Remove ten for (int i = 1; i <= 10; i++) map.remove(i); - assertEquals(3, map.size()); + assertThat(map.size()).isEqualTo(3); // remove three map.remove(11); map.remove(12); map.remove(13); - assertEquals(0, map.size()); + assertThat(map.size()).isEqualTo(0); } @Test public void removeTestComplex1() { - HashTableSeperateChaining map = new HashTableSeperateChaining<>(); + HashTableSeparateChaining map = new HashTableSeparateChaining<>(); HashObject o1 = new HashObject(88, 1); HashObject o2 = new HashObject(88, 2); @@ -210,7 +218,7 @@ public void removeTestComplex1() { map.remove(o1); map.remove(o4); - assertEquals(0, map.size()); + assertThat(map.size()).isEqualTo(0); } @Test @@ -222,9 +230,9 @@ public void testRandomMapOperations() { map.clear(); jmap.clear(); - assertEquals(jmap.size(), map.size()); + assertThat(jmap.size()).isEqualTo(map.size()); - map = new HashTableSeperateChaining<>(); + map = new HashTableSeparateChaining<>(); final double probability1 = Math.random(); final double probability2 = Math.random(); @@ -237,17 +245,17 @@ public void testRandomMapOperations() { int key = nums.get(i); int val = i; - if (r < probability1) assertEquals(jmap.put(key, val), map.put(key, val)); + if (r < probability1) assertThat(jmap.put(key, val)).isEqualTo(map.put(key, val)); - assertEquals(jmap.get(key), map.get(key)); - assertEquals(jmap.containsKey(key), map.containsKey(key)); - assertEquals(jmap.size(), map.size()); + assertThat(jmap.get(key)).isEqualTo(map.get(key)); + assertThat(jmap.containsKey(key)).isEqualTo(map.containsKey(key)); + assertThat(jmap.size()).isEqualTo(map.size()); - if (r > probability2) assertEquals(map.remove(key), jmap.remove(key)); + if (r > probability2) assertThat(map.remove(key)).isEqualTo(jmap.remove(key)); - assertEquals(jmap.get(key), map.get(key)); - assertEquals(jmap.containsKey(key), map.containsKey(key)); - assertEquals(jmap.size(), map.size()); + assertThat(jmap.get(key)).isEqualTo(map.get(key)); + assertThat(jmap.containsKey(key)).isEqualTo(map.containsKey(key)); + assertThat(jmap.size()).isEqualTo(map.size()); } } } @@ -255,17 +263,17 @@ public void testRandomMapOperations() { @Test public void randomIteratorTests() { - HashTableSeperateChaining> m = new HashTableSeperateChaining<>(); + HashTableSeparateChaining> m = new HashTableSeparateChaining<>(); HashMap> hm = new HashMap<>(); for (int loop = 0; loop < LOOPS; loop++) { m.clear(); hm.clear(); - assertEquals(m.size(), hm.size()); + assertThat(m.size()).isEqualTo(hm.size()); int sz = randInt(1, MAX_SIZE); - m = new HashTableSeperateChaining<>(sz); + m = new HashTableSeparateChaining<>(sz); hm = new HashMap<>(sz); final double probability = Math.random(); @@ -296,8 +304,8 @@ public void randomIteratorTests() { l2.add(rand_val); } - assertEquals(m.size(), hm.size()); - assertEquals(l1, l2); + assertThat(m.size()).isEqualTo(hm.size()); + assertThat(l1).isEqualTo(l2); } } } diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/kdtree/GeneralKDTreeTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/kdtree/GeneralKDTreeTest.java new file mode 100644 index 000000000..4d2005331 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/kdtree/GeneralKDTreeTest.java @@ -0,0 +1,187 @@ +package com.williamfiset.algorithms.datastructures.kdtree; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class GeneralKDTreeTest { + + /* TREE CREATION TESTS */ + @Test + public void testDimensionsZero() { + assertThrows(IllegalArgumentException.class, () -> new GeneralKDTree<>(0)); + } + + @Test + public void testDimensionsNegative() { + assertThrows(IllegalArgumentException.class, () -> new GeneralKDTree<>(-5)); + } + + /* INSERT METHOD TESTS */ + @Test + public void testInsert() { + GeneralKDTree kdTree = new GeneralKDTree(3); + assertThat(kdTree.getRootPoint() == null).isTrue(); + Integer[] pointRoot = {3, 4, 3}; + Integer[] pointLeft = {1, 7, 6}; + Integer[] pointRight = {3, 0, 2}; + kdTree.insert(pointRoot); + kdTree.insert(pointLeft); + kdTree.insert(pointRight); + assertThat(kdTree.getRootPoint() != null).isTrue(); + assertThat(kdTree.getRootPoint() == pointRoot).isTrue(); + } + + @Test + public void testInsertNull() { + GeneralKDTree kdTree = new GeneralKDTree(2); + assertThrows(IllegalArgumentException.class, () -> kdTree.insert(null)); + } + + @Test + public void testInsertMismatchDimensions() { + GeneralKDTree kdTree = new GeneralKDTree(2); + assertThrows(IllegalArgumentException.class, () -> kdTree.insert(new Integer[] {1, 2, 3})); + } + + /* SEARCH METHOD TESTS */ + @Test + public void testSearch() { + GeneralKDTree kdTree = new GeneralKDTree(4); + assertThat(kdTree.search(new Integer[] {7, 5, 4, 9})).isFalse(); + Integer[] point1 = {3, 4, 3, 9}; + Integer[] point2 = {2, 1, 5, 9}; + Integer[] point3 = {5, 6, 9, 9}; + Integer[] point4 = {4, 4, 0, 9}; + kdTree.insert(point1); + kdTree.insert(point2); + kdTree.insert(point3); + kdTree.insert(point4); + assertThat(kdTree.search(point1)).isTrue(); + assertThat(kdTree.search(point2)).isTrue(); + assertThat(kdTree.search(point3)).isTrue(); + assertThat(kdTree.search(point4)).isTrue(); + assertThat(kdTree.search(new Integer[] {7, 5, 4, 9})).isFalse(); + } + + @Test + public void testSearchNull() { + GeneralKDTree kdTree = new GeneralKDTree(2); + assertThrows(IllegalArgumentException.class, () -> kdTree.search(null)); + } + + @Test + public void testSearchMismatchDimensions() { + GeneralKDTree kdTree = new GeneralKDTree(2); + assertThrows(IllegalArgumentException.class, () -> kdTree.search(new Integer[] {1, 2, 3})); + } + + /* FINDMIN METHOD TESTS */ + @Test + public void testFindMin() { + GeneralKDTree kdTree = new GeneralKDTree(3); + assertThat(kdTree.findMin(0) == null).isTrue(); + Integer[] min1 = {0, 5, 4}; + Integer[] min2 = {3, 0, 7}; + Integer[] min3 = {6, 6, 0}; + kdTree.insert(new Integer[] {3, 7, 9}); + kdTree.insert(min1); + kdTree.insert(min3); + kdTree.insert(min2); + kdTree.insert(new Integer[] {4, 7, 5}); + kdTree.insert(new Integer[] {3, 4, 8}); + kdTree.insert(new Integer[] {7, 7, 2}); + kdTree.insert(new Integer[] {8, 9, 8}); + assertThat(kdTree.findMin(0) == min1).isTrue(); + assertThat(kdTree.findMin(1) == min2).isTrue(); + assertThat(kdTree.findMin(2) == min3).isTrue(); + } + + @Test + public void testFindMinOutOfBounds() { + GeneralKDTree kdTree = new GeneralKDTree(2); + assertThrows(IllegalArgumentException.class, () -> kdTree.findMin(2)); + } + + @Test + public void testFindMinNegative() { + GeneralKDTree kdTree = new GeneralKDTree(2); + assertThrows(IllegalArgumentException.class, () -> kdTree.findMin(-1)); + } + + /* DELETE METHOD TESTS */ + @Test + public void testDeleteEmpty() { + GeneralKDTree kdTree = new GeneralKDTree(2); + assertThat(kdTree.delete(new Integer[] {1, 2}) == null).isTrue(); + } + + @Test + public void testDeleteRoot() { + // General Setup + GeneralKDTree kdTreeBarren = new GeneralKDTree(3); + GeneralKDTree kdTreeLeft = new GeneralKDTree(3); + GeneralKDTree kdTreeRight = new GeneralKDTree(3); + GeneralKDTree kdTreeTwo = new GeneralKDTree(3); + Integer[] rootPoint = {2, 2, 2}; + Integer[] leftPoint = {1, 1, 1}; + Integer[] rightPoint = {3, 3, 3}; + // No child test + kdTreeBarren.insert(rootPoint); + assertThat(kdTreeBarren.delete(rootPoint) == rootPoint).isTrue(); + // Left child test + kdTreeLeft.insert(rootPoint); + kdTreeLeft.insert(leftPoint); + assertThat(kdTreeLeft.delete(rootPoint) == rootPoint).isTrue(); + // Right child test + kdTreeRight.insert(rootPoint); + kdTreeRight.insert(rightPoint); + assertThat(kdTreeRight.delete(rootPoint) == rootPoint).isTrue(); + // Both children test + kdTreeTwo.insert(rootPoint); + kdTreeTwo.insert(leftPoint); + kdTreeTwo.insert(rightPoint); + assertThat(kdTreeTwo.delete(rootPoint) == rootPoint).isTrue(); + } + + @Test + public void testDelete() { + // Tree Setup + GeneralKDTree kdTree = new GeneralKDTree(3); + assertThat(kdTree.findMin(0) == null).isTrue(); + Integer[] point1 = {3, 7, 9}; + Integer[] point2 = {0, 5, 4}; + Integer[] point3 = {6, 6, 0}; + Integer[] point4 = {3, 0, 7}; + Integer[] point5 = {4, 7, 5}; + Integer[] point6 = {3, 4, 8}; + Integer[] point7 = {7, 7, 2}; + Integer[] point8 = {8, 9, 8}; + kdTree.insert(point1); + kdTree.insert(point2); + kdTree.insert(point3); + kdTree.insert(point4); + kdTree.insert(point5); + kdTree.insert(point6); + kdTree.insert(point7); + kdTree.insert(point8); + // Delete Action Assertions + assertThat(kdTree.delete(point8) == point8).isTrue(); + assertThat(kdTree.delete(point5) == point5).isTrue(); + assertThat(kdTree.delete(point3) == point3).isTrue(); + assertThat(kdTree.delete(point8) == null).isTrue(); + } + + @Test + public void testDeleteNull() { + GeneralKDTree kdTree = new GeneralKDTree(2); + assertThrows(IllegalArgumentException.class, () -> kdTree.delete(null)); + } + + @Test + public void testDeleteMismatchDimensions() { + GeneralKDTree kdTree = new GeneralKDTree(2); + assertThrows(IllegalArgumentException.class, () -> kdTree.delete(new Integer[] {1, 2, 3})); + } +} diff --git a/javatests/com/williamfiset/algorithms/datastructures/linkedlist/LinkedListTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/linkedlist/LinkedListTest.java similarity index 58% rename from javatests/com/williamfiset/algorithms/datastructures/linkedlist/LinkedListTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/linkedlist/LinkedListTest.java index 1a112a6f9..70cbaaad7 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/linkedlist/LinkedListTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/linkedlist/LinkedListTest.java @@ -1,12 +1,12 @@ -package javatests.com.williamfiset.algorithms.datastructures.linkedlist; +package com.williamfiset.algorithms.datastructures.linkedlist; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; -import com.williamfiset.algorithms.datastructures.linkedlist.DoublyLinkedList; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.junit.*; +import org.junit.jupiter.api.*; public class LinkedListTest { private static final int LOOPS = 10000; @@ -16,117 +16,131 @@ public class LinkedListTest { DoublyLinkedList list; - @Before + @BeforeEach public void setup() { list = new DoublyLinkedList<>(); } @Test public void testEmptyList() { - assertTrue(list.isEmpty()); - assertEquals(list.size(), 0); + assertThat(list.isEmpty()).isTrue(); + assertThat(list.size()).isEqualTo(0); } - @Test(expected = Exception.class) + @Test public void testRemoveFirstOfEmpty() { - list.removeFirst(); + assertThrows(Exception.class, () -> list.removeFirst()); } - @Test(expected = Exception.class) + @Test public void testRemoveLastOfEmpty() { - list.removeLast(); + assertThrows(Exception.class, () -> list.removeLast()); } - @Test(expected = Exception.class) + @Test public void testPeekFirstOfEmpty() { - list.peekFirst(); + assertThrows(Exception.class, () -> list.peekFirst()); } - @Test(expected = Exception.class) + @Test public void testPeekLastOfEmpty() { - list.peekLast(); + assertThrows(Exception.class, () -> list.peekLast()); } @Test public void testAddFirst() { list.addFirst(3); - assertEquals(list.size(), 1); + assertThat(list.size()).isEqualTo(1); list.addFirst(5); - assertEquals(list.size(), 2); + assertThat(list.size()).isEqualTo(2); } @Test public void testAddLast() { list.addLast(3); - assertEquals(list.size(), 1); + assertThat(list.size()).isEqualTo(1); list.addLast(5); - assertEquals(list.size(), 2); + assertThat(list.size()).isEqualTo(2); + } + + @Test + public void testAddAt() throws Exception { + list.addAt(0, 1); + assertThat(list.size()).isEqualTo(1); + list.addAt(1, 2); + assertThat(list.size()).isEqualTo(2); + list.addAt(1, 3); + assertThat(list.size()).isEqualTo(3); + list.addAt(2, 4); + assertThat(list.size()).isEqualTo(4); + list.addAt(1, 8); + assertThat(list.size()).isEqualTo(5); } @Test public void testRemoveFirst() { list.addFirst(3); - assertTrue(list.removeFirst() == 3); - assertTrue(list.isEmpty()); + assertThat(list.removeFirst()).isEqualTo(3); + assertThat(list.isEmpty()); } @Test public void testRemoveLast() { list.addLast(4); - assertTrue(list.removeLast() == 4); - assertTrue(list.isEmpty()); + assertThat(list.removeLast()).isEqualTo(4); + assertThat(list.isEmpty()).isTrue(); } @Test public void testPeekFirst() { list.addFirst(4); - assertTrue(list.peekFirst() == 4); - assertEquals(list.size(), 1); + assertThat(list.peekFirst()).isEqualTo(4); + assertThat(list.size()).isEqualTo(1); } @Test public void testPeekLast() { list.addLast(4); - assertTrue(list.peekLast() == 4); - assertEquals(list.size(), 1); + assertThat(list.peekLast()).isEqualTo(4); + assertThat(list.size()).isEqualTo(1); } @Test public void testPeeking() { // 5 list.addFirst(5); - assertTrue(list.peekFirst() == 5); - assertTrue(list.peekLast() == 5); + assertThat(list.peekFirst()).isEqualTo(5); + assertThat(list.peekLast()).isEqualTo(5); // 6 - 5 list.addFirst(6); - assertTrue(list.peekFirst() == 6); - assertTrue(list.peekLast() == 5); + assertThat(list.peekFirst()).isEqualTo(6); + assertThat(list.peekLast()).isEqualTo(5); // 7 - 6 - 5 list.addFirst(7); - assertTrue(list.peekFirst() == 7); - assertTrue(list.peekLast() == 5); + assertThat(list.peekFirst()).isEqualTo(7); + assertThat(list.peekLast()).isEqualTo(5); // 7 - 6 - 5 - 8 list.addLast(8); - assertTrue(list.peekFirst() == 7); - assertTrue(list.peekLast() == 8); + assertThat(list.peekFirst()).isEqualTo(7); + assertThat(list.peekLast()).isEqualTo(8); // 7 - 6 - 5 list.removeLast(); - assertTrue(list.peekFirst() == 7); - assertTrue(list.peekLast() == 5); + assertThat(list.peekFirst()).isEqualTo(7); + assertThat(list.peekLast()).isEqualTo(5); // 7 - 6 list.removeLast(); - assertTrue(list.peekFirst() == 7); - assertTrue(list.peekLast() == 6); + assertThat(list.peekFirst()).isEqualTo(7); + assertThat(list.peekLast()).isEqualTo(6); // 6 list.removeFirst(); - assertTrue(list.peekFirst() == 6); - assertTrue(list.peekLast() == 6); + assertThat(list.peekFirst()).isEqualTo(6); + assertThat(list.peekLast()).isEqualTo(6); } @Test @@ -144,7 +158,7 @@ public void testRemoving() { strs.remove("e"); strs.remove("c"); strs.remove("f"); - assertEquals(0, strs.size()); + assertThat(strs.size()).isEqualTo(0); } @Test @@ -155,11 +169,11 @@ public void testRemoveAt() { list.add(4); list.removeAt(0); list.removeAt(2); - assertTrue(list.peekFirst() == 2); - assertTrue(list.peekLast() == 3); + assertThat(list.peekFirst()).isEqualTo(2); + assertThat(list.peekLast()).isEqualTo(3); list.removeAt(1); list.removeAt(0); - assertEquals(list.size(), 0); + assertThat(list.size()).isEqualTo(0); } @Test @@ -167,15 +181,15 @@ public void testClear() { list.add(22); list.add(33); list.add(44); - assertEquals(list.size(), 3); + assertThat(list.size()).isEqualTo(3); list.clear(); - assertEquals(list.size(), 0); + assertThat(list.size()).isEqualTo(0); list.add(22); list.add(33); list.add(44); - assertEquals(list.size(), 3); + assertThat(list.size()).isEqualTo(3); list.clear(); - assertEquals(list.size(), 0); + assertThat(list.size()).isEqualTo(0); } @Test @@ -197,16 +211,16 @@ public void testRandomizedRemoving() { for (int i = 0; i < randNums.size(); i++) { Integer rm_val = randNums.get(i); - assertEquals(javaLinkedList.remove(rm_val), list.remove(rm_val)); - assertEquals(javaLinkedList.size(), list.size()); + assertThat(javaLinkedList.remove(rm_val)).isEqualTo(list.remove(rm_val)); + assertThat(javaLinkedList.size()).isEqualTo(list.size()); java.util.Iterator iter1 = javaLinkedList.iterator(); java.util.Iterator iter2 = list.iterator(); - while (iter1.hasNext()) assertEquals(iter1.next(), iter2.next()); + while (iter1.hasNext()) assertThat(iter1.next()).isEqualTo(iter2.next()); iter1 = javaLinkedList.iterator(); iter2 = list.iterator(); - while (iter1.hasNext()) assertEquals(iter1.next(), iter2.next()); + while (iter1.hasNext()) assertThat(iter1.next()).isEqualTo(iter2.next()); } list.clear(); @@ -221,12 +235,12 @@ public void testRandomizedRemoving() { for (int i = 0; i < randNums.size(); i++) { Integer rm_val = (int) (MAX_RAND_NUM * Math.random()); - assertEquals(javaLinkedList.remove(rm_val), list.remove(rm_val)); - assertEquals(javaLinkedList.size(), list.size()); + assertThat(javaLinkedList.remove(rm_val)).isEqualTo(list.remove(rm_val)); + assertThat(javaLinkedList.size()).isEqualTo(list.size()); java.util.Iterator iter1 = javaLinkedList.iterator(); java.util.Iterator iter2 = list.iterator(); - while (iter1.hasNext()) assertEquals(iter1.next(), iter2.next()); + while (iter1.hasNext()) assertThat(iter1.next()).isEqualTo(iter2.next()); } } } @@ -253,12 +267,12 @@ public void testRandomizedRemoveAt() { Integer num1 = javaLinkedList.remove(rm_index); Integer num2 = list.removeAt(rm_index); - assertEquals(num1, num2); - assertEquals(javaLinkedList.size(), list.size()); + assertThat(num1).isEqualTo(num2); + assertThat(javaLinkedList.size()).isEqualTo(list.size()); java.util.Iterator iter1 = javaLinkedList.iterator(); java.util.Iterator iter2 = list.iterator(); - while (iter1.hasNext()) assertEquals(iter1.next(), iter2.next()); + while (iter1.hasNext()) assertThat(iter1.next()).isEqualTo(iter2.next()); } } } @@ -286,16 +300,31 @@ public void testRandomizedIndexOf() { Integer index1 = javaLinkedList.indexOf(elem); Integer index2 = list.indexOf(elem); - assertEquals(index1, index2); - assertEquals(javaLinkedList.size(), list.size()); + assertThat(index1).isEqualTo(index2); + assertThat(javaLinkedList.size()).isEqualTo(list.size()); java.util.Iterator iter1 = javaLinkedList.iterator(); java.util.Iterator iter2 = list.iterator(); - while (iter1.hasNext()) assertEquals(iter1.next(), iter2.next()); + while (iter1.hasNext()) assertThat(iter1.next()).isEqualTo(iter2.next()); } } } + @Test + public void testToString() { + DoublyLinkedList strs = new DoublyLinkedList<>(); + assertThat(strs.toString()).isEqualTo("[ ]"); + strs.add("a"); + assertThat(strs.toString()).isEqualTo("[ a ]"); + strs.add("b"); + assertThat(strs.toString()).isEqualTo("[ a, b ]"); + strs.add("c"); + strs.add("d"); + strs.add("e"); + strs.add("f"); + assertThat(strs.toString()).isEqualTo("[ a, b, c, d, e, f ]"); + } + // Generate a list of random numbers static List genRandList(int sz) { List lst = new ArrayList<>(sz); diff --git a/javatests/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapQuickRemovalsTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapQuickRemovalsTest.java similarity index 68% rename from javatests/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapQuickRemovalsTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapQuickRemovalsTest.java index c12662557..0d52de160 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapQuickRemovalsTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapQuickRemovalsTest.java @@ -1,29 +1,28 @@ -package javatests.com.williamfiset.algorithms.datastructures.priorityqueue; +package com.williamfiset.algorithms.datastructures.priorityqueue; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.datastructures.priorityqueue.BinaryHeapQuickRemovals; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.PriorityQueue; -import org.junit.*; +import org.junit.jupiter.api.*; public class BinaryHeapQuickRemovalsTest { - static final int LOOPS = 1000; + static final int LOOPS = 100; static final int MAX_SZ = 100; - @Before + @BeforeEach public void setup() {} @Test public void testEmpty() { BinaryHeapQuickRemovals q = new BinaryHeapQuickRemovals<>(); - assertEquals(q.size(), 0); - assertTrue(q.isEmpty()); - assertEquals(q.poll(), null); - assertEquals(q.peek(), null); + assertThat(q.size()).isEqualTo(0); + assertThat(q.isEmpty()).isTrue(); + assertThat(q.poll()).isNull(); + assertThat(q.peek()).isNull(); } @Test @@ -34,13 +33,13 @@ public void testHeapProperty() { // Try manually creating heap for (int n : nums) q.add(n); - for (int i = 1; i <= 9; i++) assertTrue(i == q.poll()); + for (int i = 1; i <= 9; i++) assertThat(q.poll()).isEqualTo(i); q.clear(); // Try heapify constructor q = new BinaryHeapQuickRemovals<>(nums); - for (int i = 1; i <= 9; i++) assertTrue(i == q.poll()); + for (int i = 1; i <= 9; i++) assertThat(q.poll()).isEqualTo(i); } @Test @@ -54,9 +53,10 @@ public void testHeapify() { PriorityQueue pq2 = new PriorityQueue<>(i); for (int x : lst) pq2.add(x); - assertTrue(pq.isMinHeap(0)); + assertThat(pq.isMinHeap(0)).isTrue(); + ; while (!pq2.isEmpty()) { - assertEquals(pq.poll(), pq2.poll()); + assertThat(pq.poll()).isEqualTo(pq2.poll()); } } } @@ -68,8 +68,8 @@ public void testClear() { String[] strs = {"aa", "bb", "cc", "dd", "ee"}; q = new BinaryHeapQuickRemovals<>(strs); q.clear(); - assertEquals(q.size(), 0); - assertTrue(q.isEmpty()); + assertThat(q.size()).isEqualTo(0); + assertThat(q.isEmpty()).isTrue(); } @Test @@ -78,15 +78,15 @@ public void testContainment() { String[] strs = {"aa", "bb", "cc", "dd", "ee"}; BinaryHeapQuickRemovals q = new BinaryHeapQuickRemovals<>(strs); q.remove("aa"); - assertFalse(q.contains("aa")); + assertThat(q.contains("aa")).isFalse(); q.remove("bb"); - assertFalse(q.contains("bb")); + assertThat(q.contains("bb")).isFalse(); q.remove("cc"); - assertFalse(q.contains("cc")); + assertThat(q.contains("cc")).isFalse(); q.remove("dd"); - assertFalse(q.contains("dd")); + assertThat(q.contains("dd")).isFalse(); q.clear(); - assertFalse(q.contains("ee")); + assertThat(q.contains("ee")).isFalse(); } @Test @@ -105,35 +105,35 @@ public void testContainmentRandomized() { for (int j = 0; j < randNums.size(); j++) { int randVal = randNums.get(j); - assertEquals(pq.contains(randVal), PQ.contains(randVal)); + assertThat(pq.contains(randVal)).isEqualTo(PQ.contains(randVal)); pq.remove(randVal); PQ.remove(randVal); - assertEquals(pq.contains(randVal), PQ.contains(randVal)); + assertThat(pq.contains(randVal)).isEqualTo(PQ.contains(randVal)); } } } public void sequentialRemoving(Integer[] in, Integer[] removeOrder) { - assertEquals(in.length, removeOrder.length); + assertThat(in.length).isEqualTo(removeOrder.length); BinaryHeapQuickRemovals pq = new BinaryHeapQuickRemovals<>(in); PriorityQueue PQ = new PriorityQueue<>(); for (int value : in) PQ.offer(value); - assertTrue(pq.isMinHeap(0)); + assertThat(pq.isMinHeap(0)).isTrue(); for (int i = 0; i < removeOrder.length; i++) { int elem = removeOrder[i]; - assertTrue(pq.peek() == PQ.peek()); - assertEquals(pq.remove(elem), PQ.remove(elem)); - assertTrue(pq.size() == PQ.size()); - assertTrue(pq.isMinHeap(0)); + assertThat(pq.peek()).isEqualTo(PQ.peek()); + assertThat(pq.remove(elem)).isEqualTo(PQ.remove(elem)); + assertThat(pq.size()).isEqualTo(PQ.size()); + assertThat(pq.isMinHeap(0)).isTrue(); } - assertTrue(pq.isEmpty()); + assertThat(pq.isEmpty()).isTrue(); } @Test @@ -166,17 +166,17 @@ public void testRemovingDuplicates() { Integer[] in = new Integer[] {2, 7, 2, 11, 7, 13, 2}; BinaryHeapQuickRemovals pq = new BinaryHeapQuickRemovals<>(in); - assertTrue(pq.peek() == 2); + assertThat(pq.peek()).isEqualTo(2); pq.add(3); - assertTrue(pq.poll() == 2); - assertTrue(pq.poll() == 2); - assertTrue(pq.poll() == 2); - assertTrue(pq.poll() == 3); - assertTrue(pq.poll() == 7); - assertTrue(pq.poll() == 7); - assertTrue(pq.poll() == 11); - assertTrue(pq.poll() == 13); + assertThat(pq.poll()).isEqualTo(2); + assertThat(pq.poll()).isEqualTo(2); + assertThat(pq.poll()).isEqualTo(2); + assertThat(pq.poll()).isEqualTo(3); + assertThat(pq.poll()).isEqualTo(7); + assertThat(pq.poll()).isEqualTo(7); + assertThat(pq.poll()).isEqualTo(11); + assertThat(pq.poll()).isEqualTo(13); } @Test @@ -197,18 +197,18 @@ public void testRandomizedPolling() { while (!pq1.isEmpty()) { - assertTrue(pq2.isMinHeap(0)); - assertEquals(pq1.size(), pq2.size()); - assertEquals(pq1.peek(), pq2.peek()); - assertEquals(pq1.contains(pq1.peek()), pq2.contains(pq2.peek())); + assertThat(pq2.isMinHeap(0)).isTrue(); + assertThat(pq1.size()).isEqualTo(pq2.size()); + assertThat(pq1.peek()).isEqualTo(pq2.peek()); + assertThat(pq1.contains(pq1.peek())).isEqualTo(pq2.contains(pq2.peek())); Integer v1 = pq1.poll(); Integer v2 = pq2.poll(); - assertEquals(v1, v2); - assertEquals(pq1.peek(), pq2.peek()); - assertEquals(pq1.size(), pq2.size()); - assertTrue(pq2.isMinHeap(0)); + assertThat(v1).isEqualTo(v2); + assertThat(pq1.peek()).isEqualTo(pq2.peek()); + assertThat(pq1.size()).isEqualTo(pq2.size()); + assertThat(pq2.isMinHeap(0)).isTrue(); } } } @@ -236,14 +236,14 @@ public void testRandomizedRemoving() { int removeNum = randNums.get(index++); - assertTrue(pq2.isMinHeap(0)); - assertEquals(pq1.size(), pq2.size()); - assertEquals(pq1.peek(), pq2.peek()); + assertThat(pq2.isMinHeap(0)).isTrue(); + assertThat(pq1.size()).isEqualTo(pq2.size()); + assertThat(pq1.peek()).isEqualTo(pq2.peek()); pq1.remove(removeNum); pq2.remove(removeNum); - assertEquals(pq1.peek(), pq2.peek()); - assertEquals(pq1.size(), pq2.size()); - assertTrue(pq2.isMinHeap(0)); + assertThat(pq1.peek()).isEqualTo(pq2.peek()); + assertThat(pq1.size()).isEqualTo(pq2.size()); + assertThat(pq2.isMinHeap(0)).isTrue(); } } } @@ -280,16 +280,16 @@ public void testPQReusability() { int removeNum = nums.get(i); - assertTrue(pq.isMinHeap(0)); - assertEquals(PQ.size(), pq.size()); - assertEquals(PQ.peek(), pq.peek()); + assertThat(pq.isMinHeap(0)).isTrue(); + assertThat(PQ.size()).isEqualTo(pq.size()); + assertThat(PQ.peek()).isEqualTo(pq.peek()); PQ.remove(removeNum); pq.remove(removeNum); - assertEquals(PQ.peek(), pq.peek()); - assertEquals(PQ.size(), pq.size()); - assertTrue(pq.isMinHeap(0)); + assertThat(PQ.peek()).isEqualTo(pq.peek()); + assertThat(PQ.size()).isEqualTo(pq.size()); + assertThat(pq.isMinHeap(0)).isTrue(); } } } diff --git a/javatests/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapTest.java similarity index 67% rename from javatests/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapTest.java index 1dcebdd99..f85cb9c7a 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/priorityqueue/BinaryHeapTest.java @@ -1,29 +1,28 @@ -package javatests.com.williamfiset.algorithms.datastructures.priorityqueue; +package com.williamfiset.algorithms.datastructures.priorityqueue; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.datastructures.priorityqueue.BinaryHeap; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.PriorityQueue; -import org.junit.*; +import org.junit.jupiter.api.*; public class BinaryHeapTest { - static final int LOOPS = 1000; + static final int LOOPS = 100; static final int MAX_SZ = 100; - @Before + @BeforeEach public void setup() {} @Test public void testEmpty() { BinaryHeap q = new BinaryHeap<>(); - assertEquals(q.size(), 0); - assertTrue(q.isEmpty()); - assertEquals(q.poll(), null); - assertEquals(q.peek(), null); + assertThat(q.size()).isEqualTo(0); + assertThat(q.isEmpty()).isTrue(); + assertThat(q.poll()).isNull(); + assertThat(q.peek()).isNull(); } @Test @@ -34,13 +33,13 @@ public void testHeapProperty() { // Try manually creating heap for (int n : nums) q.add(n); - for (int i = 1; i <= 9; i++) assertTrue(i == q.poll()); + for (int i = 1; i <= 9; i++) assertThat(q.poll()).isEqualTo(i); q.clear(); // Try heapify constructor q = new BinaryHeap<>(nums); - for (int i = 1; i <= 9; i++) assertTrue(i == q.poll()); + for (int i = 1; i <= 9; i++) assertThat(q.poll()).isEqualTo(i); } @Test @@ -54,9 +53,9 @@ public void testHeapify() { PriorityQueue pq2 = new PriorityQueue<>(i); for (int x : lst) pq2.add(x); - assertTrue(pq.isMinHeap(0)); + assertThat(pq.isMinHeap(0)).isTrue(); while (!pq2.isEmpty()) { - assertEquals(pq.poll(), pq2.poll()); + assertThat(pq.poll()).isEqualTo(pq2.poll()); } } } @@ -68,8 +67,8 @@ public void testClear() { String[] strs = {"aa", "bb", "cc", "dd", "ee"}; q = new BinaryHeap<>(strs); q.clear(); - assertEquals(q.size(), 0); - assertTrue(q.isEmpty()); + assertThat(q.size()).isEqualTo(0); + assertThat(q.isEmpty()).isTrue(); } @Test @@ -78,15 +77,15 @@ public void testContainment() { String[] strs = {"aa", "bb", "cc", "dd", "ee"}; BinaryHeap q = new BinaryHeap<>(strs); q.remove("aa"); - assertFalse(q.contains("aa")); + assertThat(q.contains("aa")).isFalse(); q.remove("bb"); - assertFalse(q.contains("bb")); + assertThat(q.contains("bb")).isFalse(); q.remove("cc"); - assertFalse(q.contains("cc")); + assertThat(q.contains("cc")).isFalse(); q.remove("dd"); - assertFalse(q.contains("dd")); + assertThat(q.contains("dd")).isFalse(); q.clear(); - assertFalse(q.contains("ee")); + assertThat(q.contains("ee")).isFalse(); } @Test @@ -105,35 +104,35 @@ public void testContainmentRandomized() { for (int j = 0; j < randNums.size(); j++) { int randVal = randNums.get(j); - assertEquals(pq.contains(randVal), PQ.contains(randVal)); + assertThat(pq.contains(randVal)).isEqualTo(PQ.contains(randVal)); pq.remove(randVal); PQ.remove(randVal); - assertEquals(pq.contains(randVal), PQ.contains(randVal)); + assertThat(pq.contains(randVal)).isEqualTo(PQ.contains(randVal)); } } } public void sequentialRemoving(Integer[] in, Integer[] removeOrder) { - assertEquals(in.length, removeOrder.length); + assertThat(in.length).isEqualTo(removeOrder.length); BinaryHeap pq = new BinaryHeap<>(in); PriorityQueue PQ = new PriorityQueue<>(); for (int value : in) PQ.offer(value); - assertTrue(pq.isMinHeap(0)); + assertThat(pq.isMinHeap(0)).isTrue(); for (int i = 0; i < removeOrder.length; i++) { int elem = removeOrder[i]; - assertTrue(pq.peek() == PQ.peek()); - assertEquals(pq.remove(elem), PQ.remove(elem)); - assertTrue(pq.size() == PQ.size()); - assertTrue(pq.isMinHeap(0)); + assertThat(pq.peek()).isEqualTo(PQ.peek()); + assertThat(pq.remove(elem)).isEqualTo(PQ.remove(elem)); + assertThat(pq.size()).isEqualTo(PQ.size()); + assertThat(pq.isMinHeap(0)).isTrue(); } - assertTrue(pq.isEmpty()); + assertThat(pq.isEmpty()).isTrue(); } @Test @@ -166,17 +165,17 @@ public void testRemovingDuplicates() { Integer[] in = new Integer[] {2, 7, 2, 11, 7, 13, 2}; BinaryHeap pq = new BinaryHeap<>(in); - assertTrue(pq.peek() == 2); + assertThat(pq.peek()).isEqualTo(2); pq.add(3); - assertTrue(pq.poll() == 2); - assertTrue(pq.poll() == 2); - assertTrue(pq.poll() == 2); - assertTrue(pq.poll() == 3); - assertTrue(pq.poll() == 7); - assertTrue(pq.poll() == 7); - assertTrue(pq.poll() == 11); - assertTrue(pq.poll() == 13); + assertThat(pq.poll()).isEqualTo(2); + assertThat(pq.poll()).isEqualTo(2); + assertThat(pq.poll()).isEqualTo(2); + assertThat(pq.poll()).isEqualTo(3); + assertThat(pq.poll()).isEqualTo(7); + assertThat(pq.poll()).isEqualTo(7); + assertThat(pq.poll()).isEqualTo(11); + assertThat(pq.poll()).isEqualTo(13); } @Test @@ -197,18 +196,18 @@ public void testRandomizedPolling() { while (!pq1.isEmpty()) { - assertTrue(pq2.isMinHeap(0)); - assertEquals(pq1.size(), pq2.size()); - assertEquals(pq1.peek(), pq2.peek()); - assertEquals(pq1.contains(pq1.peek()), pq2.contains(pq2.peek())); + assertThat(pq2.isMinHeap(0)).isTrue(); + assertThat(pq1.size()).isEqualTo(pq2.size()); + assertThat(pq1.peek()).isEqualTo(pq2.peek()); + assertThat(pq1.contains(pq1.peek())).isEqualTo(pq2.contains(pq2.peek())); Integer v1 = pq1.poll(); Integer v2 = pq2.poll(); - assertEquals(v1, v2); - assertEquals(pq1.peek(), pq2.peek()); - assertEquals(pq1.size(), pq2.size()); - assertTrue(pq2.isMinHeap(0)); + assertThat(v1).isEqualTo(v2); + assertThat(pq1.peek()).isEqualTo(pq2.peek()); + assertThat(pq1.size()).isEqualTo(pq2.size()); + assertThat(pq2.isMinHeap(0)); } } } @@ -236,14 +235,14 @@ public void testRandomizedRemoving() { int removeNum = randNums.get(index++); - assertTrue(pq2.isMinHeap(0)); - assertEquals(pq1.size(), pq2.size()); - assertEquals(pq1.peek(), pq2.peek()); + assertThat(pq2.isMinHeap(0)).isTrue(); + assertThat(pq1.size()).isEqualTo(pq2.size()); + assertThat(pq1.peek()).isEqualTo(pq2.peek()); pq1.remove(removeNum); pq2.remove(removeNum); - assertEquals(pq1.peek(), pq2.peek()); - assertEquals(pq1.size(), pq2.size()); - assertTrue(pq2.isMinHeap(0)); + assertThat(pq1.peek()).isEqualTo(pq2.peek()); + assertThat(pq1.size()).isEqualTo(pq2.size()); + assertThat(pq2.isMinHeap(0)).isTrue(); } } } @@ -280,16 +279,16 @@ public void testPQReusability() { int removeNum = nums.get(i); - assertTrue(pq.isMinHeap(0)); - assertEquals(PQ.size(), pq.size()); - assertEquals(PQ.peek(), pq.peek()); + assertThat(pq.isMinHeap(0)).isTrue(); + assertThat(PQ.size()).isEqualTo(pq.size()); + assertThat(PQ.peek()).isEqualTo(pq.peek()); PQ.remove(removeNum); pq.remove(removeNum); - assertEquals(PQ.peek(), pq.peek()); - assertEquals(PQ.size(), pq.size()); - assertTrue(pq.isMinHeap(0)); + assertThat(PQ.peek()).isEqualTo(pq.peek()); + assertThat(PQ.size()).isEqualTo(pq.size()); + assertThat(pq.isMinHeap(0)).isTrue(); } } } diff --git a/javatests/com/williamfiset/algorithms/datastructures/priorityqueue/MinDHeapTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinDHeapTest.java similarity index 70% rename from javatests/com/williamfiset/algorithms/datastructures/priorityqueue/MinDHeapTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinDHeapTest.java index 4bfbf6e2a..a36ea404b 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/priorityqueue/MinDHeapTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinDHeapTest.java @@ -1,30 +1,28 @@ -package javatests.com.williamfiset.algorithms.datastructures.priorityqueue; +package com.williamfiset.algorithms.datastructures.priorityqueue; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.datastructures.priorityqueue.MinDHeap; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.PriorityQueue; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.*; public class MinDHeapTest { static final int LOOPS = 1000; static final int MAX_SZ = 100; - @Before + @BeforeEach public void setup() {} @Test public void testEmpty() { MinDHeap q = new MinDHeap<>(4, 0); - assertEquals(q.size(), 0); - assertTrue(q.isEmpty()); - assertEquals(q.poll(), null); - assertEquals(q.peek(), null); + assertThat(q.size()).isEqualTo(0); + assertThat(q.isEmpty()).isTrue(); + assertThat(q.poll()).isNull(); + assertThat(q.peek()).isNull(); } @Test @@ -35,7 +33,7 @@ public void testHeapProperty() { // Try manually creating heap for (int n : nums) q.add(n); - for (int i = 1; i <= 9; i++) assertTrue(i == q.poll()); + for (int i = 1; i <= 9; i++) assertThat(q.poll()).isEqualTo(i); } @Test @@ -51,7 +49,7 @@ public void testPriorityQueueSizeParam() { pq2.add(x); pq.add(x); } - while (!pq2.isEmpty()) assertEquals(pq.poll(), pq2.poll()); + while (!pq2.isEmpty()) assertThat(pq.poll()).isEqualTo(pq2.poll()); } } @@ -64,7 +62,7 @@ public void testPriorityRandomOperations() { if (p2 < p1) { double tmp = p1; p1 = p2; - p2 = p1; + p2 = tmp; } Integer[] ar = genRandArray(LOOPS); @@ -79,14 +77,14 @@ public void testPriorityRandomOperations() { pq.add(e); pq2.add(e); } else if (p1 < r && r <= p2) { - if (!pq2.isEmpty()) assertEquals(pq.poll(), pq2.poll()); + if (!pq2.isEmpty()) assertThat(pq.poll()).isEqualTo(pq2.poll()); } else { pq.clear(); pq2.clear(); } } - assertEquals(pq.peek(), pq2.peek()); + assertThat(pq.peek()).isEqualTo(pq2.peek()); } } @@ -96,8 +94,8 @@ public void testClear() { MinDHeap q = new MinDHeap<>(2, strs.length); for (String s : strs) q.add(s); q.clear(); - assertEquals(q.size(), 0); - assertTrue(q.isEmpty()); + assertThat(q.size()).isEqualTo(0); + assertThat(q.isEmpty()).isTrue(); } /* @@ -117,9 +115,9 @@ public void testContainmentRandomized() { for (int j = 0; j < randNums.size(); j++) { int randVal = randNums.get(j); - assertEquals( pq.contains(randVal), PQ.contains(randVal) ); + assertThat( pq.contains(randVal)).isEqualTo(PQ.contains(randVal) ); pq.remove(randVal); PQ.remove(randVal); - assertEquals( pq.contains(randVal), PQ.contains(randVal) ); + assertThat( pq.contains(randVal)).isEqualTo(PQ.contains(randVal) ); } @@ -129,26 +127,26 @@ public void testContainmentRandomized() { public void sequentialRemoving(Integer[] in, Integer[] removeOrder) { - assertEquals(in.length, removeOrder.length); + assertThat(in.length, removeOrder.length); PQueue pq = new PQueue<>(in); PriorityQueue PQ = new PriorityQueue<>(); for (int value : in) PQ.offer(value); - assertTrue(pq.isMinHeap(0)); + assertThat(pq.isMinHeap(0)).isTrue(); for (int i = 0; i < removeOrder.length; i++) { int elem = removeOrder[i]; - assertTrue(pq.peek() == PQ.peek()); - assertEquals( pq.remove(elem), PQ.remove(elem)); - assertTrue(pq.size() == PQ.size()); - assertTrue(pq.isMinHeap(0)); + assertThat(pq.peek()).isEqualTo(PQ.peek()); + assertThat( pq.remove(elem)).isEqualTo(PQ.remove(elem)); + assertThat(pq.size()).isEqualTo(PQ.size()); + assertThat(pq.isMinHeap(0)).isTrue(); } - assertTrue(pq.isEmpty()); + assertThat(pq.isEmpty()).isTrue(); } @@ -185,18 +183,19 @@ public void testRemovingDuplicates() { MinDHeap pq = new MinDHeap<>(3, in.length + 1); for (Integer x : in) pq.add(x); - assertTrue(pq.peek() == 2); + assertThat(pq.peek()).isEqualTo(2); pq.add(3); - assertTrue(pq.poll() == 2); - assertTrue(pq.poll() == 2); - assertTrue(pq.poll() == 2); - assertTrue(pq.poll() == 3); - assertTrue(pq.poll() == 7); - assertTrue(pq.poll() == 7); - assertTrue(pq.poll() == 11); - assertTrue(pq.poll() == 13); + assertThat(pq.poll()).isEqualTo(2); + assertThat(pq.poll()).isEqualTo(2); + assertThat(pq.poll()).isEqualTo(2); + assertThat(pq.poll()).isEqualTo(3); + assertThat(pq.poll()).isEqualTo(7); + assertThat(pq.poll()).isEqualTo(7); + assertThat(pq.poll()).isEqualTo(11); + assertThat(pq.poll()).isEqualTo(13); } + /* @Test public void testRandomizedPolling() { @@ -216,18 +215,18 @@ public void testRandomizedPolling() { while( !pq1.isEmpty() ) { - assertTrue(pq2.isMinHeap(0)); - assertEquals( pq1.size(), pq2.size() ); - assertEquals( pq1.peek(), pq2.peek() ); - assertEquals( pq1.contains(pq1.peek()), pq2.contains(pq2.peek()) ); + assertThat(pq2.isMinHeap(0)).isTrue(); + assertThat(pq1.size()).isEqualTo(pq2.size()); + assertThat(pq1.peek()).isEqualTo(pq2.peek()); + assertThat(pq1.contains(pq1.peek())).isEqualTo(pq2.contains(pq2.peek())); Integer v1 = pq1.poll(); Integer v2 = pq2.poll(); - assertEquals(v1, v2); - assertEquals( pq1.peek(), pq2.peek() ); - assertEquals( pq1.size(), pq2.size() ); - assertTrue(pq2.isMinHeap(0)); + assertThat(v1).isEqualTo(v2); + assertThat(pq1.peek()).isEqualTo(pq2.peek()); + assertThat(pq1.size()).isEqualTo(pq2.size()); + assertThat(pq2.isMinHeap(0)).isTrue(); } @@ -258,13 +257,13 @@ public void testRandomizedRemoving() { int removeNum = randNums.get(index++); - assertTrue(pq2.isMinHeap(0)); - assertEquals( pq1.size(), pq2.size() ); - assertEquals( pq1.peek(), pq2.peek() ); + assertThat(pq2.isMinHeap(0)).isTrue(); + assertThat( pq1.size()).isEqualTo(pq2.size()); + assertThat( pq1.peek()).isEqualTo(pq2.peek()); pq1.remove(removeNum); pq2.remove(removeNum); - assertEquals( pq1.peek(), pq2.peek() ); - assertEquals( pq1.size(), pq2.size() ); - assertTrue(pq2.isMinHeap(0)); + assertThat( pq1.peek()).isEqualTo(pq2.peek()); + assertThat( pq1.size()).isEqualTo(pq2.size()); + assertThat(pq2.isMinHeap(0)).isTrue(); } @@ -304,16 +303,16 @@ public void testPQReusability() { int removeNum = nums.get(i); - assertTrue(pq.isMinHeap(0)); - assertEquals( PQ.size(), pq.size() ); - assertEquals( PQ.peek(), pq.peek() ); + assertThat(pq.isMinHeap(0)).isTrue(); + assertThat( PQ.size()).isEqualTo(pq.size()); + assertThat( PQ.peek().isEqualTo(pq.peek()); PQ.remove(removeNum); pq.remove(removeNum); - assertEquals( PQ.peek(), pq.peek() ); - assertEquals( PQ.size(), pq.size() ); - assertTrue(pq.isMinHeap(0)); + assertThat( PQ.peek().isEqualTo(pq.peek()); + assertThat( PQ.size().isEqualTo(pq.size()); + assertThat(pq.isMinHeap(0)).isTrue(); } diff --git a/javatests/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedBinaryHeapTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedBinaryHeapTest.java similarity index 93% rename from javatests/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedBinaryHeapTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedBinaryHeapTest.java index 6473eb79d..eb9565afb 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedBinaryHeapTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/priorityqueue/MinIndexedBinaryHeapTest.java @@ -1,8 +1,8 @@ -package javatests.com.williamfiset.algorithms.datastructures.priorityqueue; +package com.williamfiset.algorithms.datastructures.priorityqueue; import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; -import com.williamfiset.algorithms.datastructures.priorityqueue.MinIndexedBinaryHeap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -10,21 +10,21 @@ import java.util.List; import java.util.PriorityQueue; import java.util.Random; -import org.junit.*; +import org.junit.jupiter.api.*; public class MinIndexedBinaryHeapTest { - @Before + @BeforeEach public void setup() {} - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalSizeOfNegativeOne() { - new MinIndexedBinaryHeap(-1); + assertThrows(IllegalArgumentException.class, () -> new MinIndexedBinaryHeap(-1)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testIllegalSizeOfZero() { - new MinIndexedBinaryHeap(0); + assertThrows(IllegalArgumentException.class, () -> new MinIndexedBinaryHeap(0)); } @Test @@ -46,11 +46,15 @@ public void testContainsInvalidKey() { assertThat(pq.contains(3)).isFalse(); } - @Test(expected = IllegalArgumentException.class) + @Test public void testDuplicateKeys() { - MinIndexedBinaryHeap pq = new MinIndexedBinaryHeap(10); - pq.insert(5, "abcdef"); - pq.insert(5, "xyz"); + assertThrows( + IllegalArgumentException.class, + () -> { + MinIndexedBinaryHeap pq = new MinIndexedBinaryHeap(10); + pq.insert(5, "abcdef"); + pq.insert(5, "xyz"); + }); } @Test diff --git a/javatests/com/williamfiset/algorithms/datastructures/quadtree/QuadTreeTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/quadtree/QuadTreeTest.java similarity index 97% rename from javatests/com/williamfiset/algorithms/datastructures/quadtree/QuadTreeTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/quadtree/QuadTreeTest.java index d38ee969b..6a34e3ecf 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/quadtree/QuadTreeTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/quadtree/QuadTreeTest.java @@ -1,12 +1,8 @@ -package javatests.com.williamfiset.algorithms.datastructures.quadtree; +package com.williamfiset.algorithms.datastructures.quadtree; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.*; -import com.williamfiset.algorithms.datastructures.quadtree.QuadTree; -import java.util.*; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.*; public class QuadTreeTest { @@ -14,7 +10,7 @@ public class QuadTreeTest { static final int TEST_SZ = 1000; static final int MAX_RAND_NUM = +2000; - @Before + @BeforeEach public void setup() {} @Test diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/queue/IntQueueTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/queue/IntQueueTest.java new file mode 100644 index 000000000..2a5dfd4a0 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/queue/IntQueueTest.java @@ -0,0 +1,139 @@ +package com.williamfiset.algorithms.datastructures.queue; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.*; +import org.junit.jupiter.api.*; + +public class IntQueueTest { + + @BeforeEach + public void setup() {} + + @Test + public void testEmptyQueue() { + IntQueue queue = new IntQueue(0); + assertThat(queue.isEmpty()).isTrue(); + assertThat(queue.size()).isEqualTo(0); + } + + // Doesn't apply to this implementation because of wrap + // @Test(expected=Exception.class) + // public void testPollOnEmpty() { + // IntQueue queue = new IntQueue(0); + // queue.poll(); + // } + + // Doesn't apply to this implementation because of wrap + // @Test(expected=Exception.class) + // public void testPeekOnEmpty() { + // IntQueue queue = new IntQueue(0); + // queue.peek(); + // } + + @Test + public void testofferOneElement() { + IntQueue queue = new IntQueue(1); + queue.offer(77); + assertThat(queue.size()).isEqualTo(1); + } + + @Test + public void testAll() { + int n = 5; + IntQueue queue = new IntQueue(10); + assertThat(queue.isEmpty()).isTrue(); + for (int i = 1; i <= n; i++) { + queue.offer(i); + assertThat(queue.isEmpty()).isFalse(); + } + for (int i = 1; i <= n; i++) { + assertThat((int) queue.peek()).isEqualTo(i); + assertThat((int) queue.poll()).isEqualTo(i); + assertThat(queue.size()).isEqualTo(n - i); + } + assertThat(queue.isEmpty()).isTrue(); + n = 8; + for (int i = 1; i <= n; i++) { + queue.offer(i); + assertThat(queue.isEmpty()).isFalse(); + } + for (int i = 1; i <= n; i++) { + assertThat((int) queue.peek()).isEqualTo(i); + assertThat((int) queue.poll()).isEqualTo(i); + assertThat(queue.size()).isEqualTo(n - i); + } + assertThat(queue.isEmpty()).isTrue(); + n = 9; + for (int i = 1; i <= n; i++) { + queue.offer(i); + assertThat(queue.isEmpty()).isFalse(); + } + for (int i = 1; i <= n; i++) { + assertThat((int) queue.peek()).isEqualTo(i); + assertThat((int) queue.poll()).isEqualTo(i); + assertThat(queue.size()).isEqualTo(n - i); + } + assertThat(queue.isEmpty()).isTrue(); + n = 10; + for (int i = 1; i <= n; i++) { + queue.offer(i); + assertThat(queue.isEmpty()).isFalse(); + } + for (int i = 1; i <= n; i++) { + assertThat((int) queue.peek()).isEqualTo(i); + assertThat((int) queue.poll()).isEqualTo(i); + assertThat(queue.size()).isEqualTo(n - i); + } + assertThat(queue.isEmpty()).isTrue(); + } + + @Test + public void testPeekOneElement() { + IntQueue queue = new IntQueue(1); + queue.offer(77); + assertThat(queue.peek()).isEqualTo(77); + assertThat(queue.size()).isEqualTo(1); + } + + @Test + public void testpollOneElement() { + IntQueue queue = new IntQueue(1); + queue.offer(77); + assertThat(queue.poll()).isEqualTo(77); + assertThat(queue.size()).isEqualTo(0); + } + + @Test + public void testRandom() { + + for (int qSize = 1; qSize <= 50; qSize++) { + + IntQueue intQ = new IntQueue(qSize); + ArrayDeque javaQ = new ArrayDeque<>(qSize); + + assertThat(javaQ.isEmpty()).isEqualTo(intQ.isEmpty()); + assertThat(javaQ.size()).isEqualTo(intQ.size()); + + for (int operations = 0; operations < 5000; operations++) { + + double r = Math.random(); + + if (r < 0.60) { + int elem = (int) (1000 * Math.random()); + if (javaQ.size() < qSize) { + javaQ.offer(elem); + intQ.offer(elem); + } + } else { + if (!javaQ.isEmpty()) { + assertThat((int) javaQ.poll()).isEqualTo((int) intQ.poll()); + } + } + + assertThat(javaQ.isEmpty()).isEqualTo(intQ.isEmpty()); + assertThat(javaQ.size()).isEqualTo(intQ.size()); + } + } + } +} diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/queue/QueueTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/queue/QueueTest.java new file mode 100644 index 000000000..b045ee8dc --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/queue/QueueTest.java @@ -0,0 +1,81 @@ +package com.williamfiset.algorithms.datastructures.queue; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class QueueTest { + + private static List> inputs() { + List> queues = new ArrayList<>(); + queues.add(new ArrayQueue(2)); + queues.add(new LinkedQueue()); + queues.add(new IntQueue(2)); + return queues; + } + + @ParameterizedTest + @MethodSource("inputs") + public void testEmptyQueue(Queue queue) { + assertThat(queue.isEmpty()).isTrue(); + assertThat(queue.size()).isEqualTo(0); + } + + @ParameterizedTest + @MethodSource("inputs") + public void testPollOnEmpty(Queue queue) { + assertThrows(Exception.class, () -> queue.poll()); + } + + @ParameterizedTest + @MethodSource("inputs") + public void testPeekOnEmpty(Queue queue) { + assertThrows(Exception.class, () -> queue.peek()); + } + + @ParameterizedTest + @MethodSource("inputs") + public void testOffer(Queue queue) { + queue.offer(2); + assertThat(queue.size()).isEqualTo(1); + } + + @ParameterizedTest + @MethodSource("inputs") + public void testPeek(Queue queue) { + queue.offer(2); + assertThat((int) queue.peek()).isEqualTo(2); + assertThat(queue.size()).isEqualTo(1); + } + + @ParameterizedTest + @MethodSource("inputs") + public void testPoll(Queue queue) { + queue.offer(2); + assertThat((int) queue.poll()).isEqualTo(2); + assertThat(queue.size()).isEqualTo(0); + } + + @ParameterizedTest + @MethodSource("inputs") + public void testExhaustively(Queue queue) { + assertThat(queue.isEmpty()).isTrue(); + queue.offer(1); + assertThat(queue.isEmpty()).isFalse(); + queue.offer(2); + assertThat(queue.size()).isEqualTo(2); + assertThat((int) queue.peek()).isEqualTo(1); + assertThat(queue.size()).isEqualTo(2); + assertThat((int) queue.poll()).isEqualTo(1); + assertThat(queue.size()).isEqualTo(1); + assertThat((int) queue.peek()).isEqualTo(2); + assertThat(queue.size()).isEqualTo(1); + assertThat((int) queue.poll()).isEqualTo(2); + assertThat(queue.size()).isEqualTo(0); + assertThat(queue.isEmpty()).isTrue(); + } +} diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTree2Test.java b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTree2Test.java new file mode 100644 index 000000000..aa36954ab --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTree2Test.java @@ -0,0 +1,498 @@ +/** + * gradle test --info --tests + * "com.williamfiset.algorithms.datastructures.segmenttree.GenericSegmentTree2Test" + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +import static com.google.common.truth.Truth.assertThat; + +import com.williamfiset.algorithms.utils.TestUtils; +import org.junit.jupiter.api.*; + +public class GenericSegmentTree2Test { + + static int ITERATIONS = 100; + static int MAX_N = 28; + + @BeforeEach + public void setup() {} + + @Test + public void testSumQuerySumUpdate_Simple() { + long[] values = {1, 2, 3, 4, 5}; + GenericSegmentTree2 st = + new GenericSegmentTree2( + values, + GenericSegmentTree2.SegmentCombinationFn.SUM, + GenericSegmentTree2.RangeUpdateFn.ADDITION); + + assertThat(st.rangeQuery1(0, 1)).isEqualTo(3); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(3); + assertThat(st.rangeQuery1(0, 4)).isEqualTo(15); + } + + @Test + public void testSumQuerySumUpdate_RangeUpdate() { + // 0, 1, 2, 3, 4 + long[] ar = {1, 2, 1, 2, 1}; + GenericSegmentTree2 st = + new GenericSegmentTree2( + ar, + GenericSegmentTree2.SegmentCombinationFn.SUM, + GenericSegmentTree2.RangeUpdateFn.ADDITION); + + // Do multiple range updates + st.rangeUpdate1(0, 1, 5); + st.rangeUpdate1(3, 4, 2); + st.rangeUpdate1(0, 4, 3); + + // Point queries + assertThat(st.rangeQuery1(0, 0)).isEqualTo(1 + 3 + 5); + assertThat(st.rangeQuery1(1, 1)).isEqualTo(2 + 3 + 5); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(1 + 3); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(2 + 3 + 2); + assertThat(st.rangeQuery1(4, 4)).isEqualTo(2 + 3 + 1); + + // Range queries + assertThat(st.rangeQuery1(0, 1)).isEqualTo(2 * 5 + 2 * 3 + 1 + 2); + assertThat(st.rangeQuery1(0, 2)).isEqualTo(2 * 5 + 3 * 3 + 1 + 2 + 1); + assertThat(st.rangeQuery1(3, 4)).isEqualTo(2 * 2 + 2 * 3 + 2 + 1); + assertThat(st.rangeQuery1(0, 4)).isEqualTo(2 * 5 + 2 * 2 + 3 * 5 + 1 + 1 + 1 + 2 + 2); + } + + @Test + public void testSumQueryAssignUpdate_simple() { + long[] ar = {2, 1, 3, 4, -1}; + GenericSegmentTree2 st = + new GenericSegmentTree2( + ar, + GenericSegmentTree2.SegmentCombinationFn.SUM, + GenericSegmentTree2.RangeUpdateFn.ASSIGN); + + st.rangeUpdate1(3, 4, 2); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(10); + assertThat(st.rangeQuery1(3, 4)).isEqualTo(4); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(2); + assertThat(st.rangeQuery1(4, 4)).isEqualTo(2); + + st.rangeUpdate1(1, 3, 4); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(16); + assertThat(st.rangeQuery1(0, 1)).isEqualTo(6); + assertThat(st.rangeQuery1(3, 4)).isEqualTo(6); + assertThat(st.rangeQuery1(1, 1)).isEqualTo(4); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(4); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(4); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(12); + assertThat(st.rangeQuery1(2, 3)).isEqualTo(8); + assertThat(st.rangeQuery1(1, 2)).isEqualTo(8); + + st.rangeUpdate1(2, 2, 5); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(17); + assertThat(st.rangeQuery1(0, 2)).isEqualTo(11); + assertThat(st.rangeQuery1(2, 4)).isEqualTo(11); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(13); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(5); + } + + @Test + public void testSumQueryMulUpdate_simple() { + long[] ar = {1, 4, 5, 3, 2}; + GenericSegmentTree2 st = + new GenericSegmentTree2( + ar, + GenericSegmentTree2.SegmentCombinationFn.SUM, + GenericSegmentTree2.RangeUpdateFn.MULTIPLICATION); + + st.rangeUpdate1(1, 3, 3); + + assertThat(st.rangeQuery1(1, 3)).isEqualTo(4 * 3 + 5 * 3 + 3 * 3); + assertThat(st.rangeQuery1(0, 4)).isEqualTo(1 + 4 * 3 + 5 * 3 + 3 * 3 + 2); + assertThat(st.rangeQuery1(0, 2)).isEqualTo(1 + 4 * 3 + 5 * 3); + assertThat(st.rangeQuery1(2, 4)).isEqualTo(5 * 3 + 3 * 3 + 2); + + st.rangeUpdate1(1, 3, 2); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(4 * 3 * 2 + 5 * 3 * 2 + 3 * 3 * 2); + } + + @Test + public void minQuerySumUpdates_simple() { + long[] ar = {2, 1, 3, 4, -1}; + GenericSegmentTree2 st = + new GenericSegmentTree2( + ar, + GenericSegmentTree2.SegmentCombinationFn.MIN, + GenericSegmentTree2.RangeUpdateFn.ADDITION); + + st.rangeUpdate1(0, 4, 1); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(0); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(2); + assertThat(st.rangeQuery1(2, 4)).isEqualTo(0); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(5); + + st.rangeUpdate1(3, 4, 4); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(2); + assertThat(st.rangeQuery1(0, 1)).isEqualTo(2); + assertThat(st.rangeQuery1(3, 4)).isEqualTo(4); + assertThat(st.rangeQuery1(1, 1)).isEqualTo(2); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(4); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(9); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(2); + assertThat(st.rangeQuery1(2, 3)).isEqualTo(4); + assertThat(st.rangeQuery1(1, 2)).isEqualTo(2); + + st.rangeUpdate1(1, 3, 3); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(3); + assertThat(st.rangeQuery1(0, 2)).isEqualTo(3); + assertThat(st.rangeQuery1(2, 4)).isEqualTo(4); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(5); + assertThat(st.rangeQuery1(0, 0)).isEqualTo(3); + assertThat(st.rangeQuery1(1, 1)).isEqualTo(5); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(7); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(12); + assertThat(st.rangeQuery1(4, 4)).isEqualTo(4); + } + + @Test + public void maxQuerySumUpdate_simple() { + long[] ar = {2, 1, 3, 4, -1}; + GenericSegmentTree2 st = + new GenericSegmentTree2( + ar, + GenericSegmentTree2.SegmentCombinationFn.MAX, + GenericSegmentTree2.RangeUpdateFn.ADDITION); + + // st.printDebugInfo(); + st.rangeUpdate1(0, 4, 1); + // st.printDebugInfo(); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(5); + // st.printDebugInfo(); + assertThat(st.rangeQuery1(0, 1)).isEqualTo(3); + + assertThat(st.rangeQuery1(1, 2)).isEqualTo(4); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(5); + + st.rangeUpdate1(3, 4, 4); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(9); + assertThat(st.rangeQuery1(0, 1)).isEqualTo(3); + assertThat(st.rangeQuery1(3, 4)).isEqualTo(9); + assertThat(st.rangeQuery1(1, 1)).isEqualTo(2); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(4); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(9); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(9); + assertThat(st.rangeQuery1(2, 3)).isEqualTo(9); + assertThat(st.rangeQuery1(1, 2)).isEqualTo(4); + + st.rangeUpdate1(1, 3, 3); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(12); + assertThat(st.rangeQuery1(0, 2)).isEqualTo(7); + assertThat(st.rangeQuery1(2, 4)).isEqualTo(12); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(12); + assertThat(st.rangeQuery1(0, 0)).isEqualTo(3); + assertThat(st.rangeQuery1(1, 1)).isEqualTo(5); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(7); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(12); + assertThat(st.rangeQuery1(4, 4)).isEqualTo(4); + } + + @Test + public void maxminQueryMulUpdate_simple() { + long[] ar = {2, 1, 3, 4, -1}; + GenericSegmentTree2 st1 = + new GenericSegmentTree2( + ar, + GenericSegmentTree2.SegmentCombinationFn.MAX, + GenericSegmentTree2.RangeUpdateFn.MULTIPLICATION); + GenericSegmentTree2 st2 = + new GenericSegmentTree2( + ar, + GenericSegmentTree2.SegmentCombinationFn.MIN, + GenericSegmentTree2.RangeUpdateFn.MULTIPLICATION); + + st1.rangeUpdate1(0, 4, 1); + st2.rangeUpdate1(0, 4, 1); + + assertThat(st1.rangeQuery1(0, 4)).isEqualTo(4); + assertThat(st2.rangeQuery1(0, 4)).isEqualTo(-1); + + // TODO(issue/208): Negative numbers are a known issue + st1.rangeUpdate1(0, 4, -2); + st2.rangeUpdate1(0, 4, -2); + + assertThat(st1.rangeQuery1(0, 4)).isEqualTo(2); + assertThat(st2.rangeQuery1(0, 4)).isEqualTo(-8); + + st1.rangeUpdate1(0, 4, -1); + st2.rangeUpdate1(0, 4, -1); + + assertThat(st1.rangeQuery1(0, 4)).isEqualTo(8); + assertThat(st2.rangeQuery1(0, 4)).isEqualTo(-2); + } + + @Test + public void maxQueryMulUpdate_simple() { + long[] ar = {2, 1, 3, 4, -1}; + GenericSegmentTree2 st1 = + new GenericSegmentTree2( + ar, + GenericSegmentTree2.SegmentCombinationFn.MAX, + GenericSegmentTree2.RangeUpdateFn.MULTIPLICATION); + + // [4, 2, 6, 8, -2] + st1.rangeUpdate1(0, 4, 2); + assertThat(st1.rangeQuery1(0, 4)).isEqualTo(8); + assertThat(st1.rangeQuery1(0, 0)).isEqualTo(4); + assertThat(st1.rangeQuery1(0, 1)).isEqualTo(4); + assertThat(st1.rangeQuery1(0, 2)).isEqualTo(6); + assertThat(st1.rangeQuery1(1, 3)).isEqualTo(8); + + // [4, 2, 6, -16, 4] + st1.rangeUpdate1(3, 4, -2); + assertThat(st1.rangeQuery1(0, 4)).isEqualTo(6); + assertThat(st1.rangeQuery1(0, 0)).isEqualTo(4); + assertThat(st1.rangeQuery1(0, 1)).isEqualTo(4); + assertThat(st1.rangeQuery1(0, 2)).isEqualTo(6); + assertThat(st1.rangeQuery1(1, 3)).isEqualTo(6); + assertThat(st1.rangeQuery1(3, 4)).isEqualTo(4); + } + + @Test + public void minQueryMulUpdate_simple() { + long[] ar = {2, 1, 3, 4, -1}; + GenericSegmentTree2 st1 = + new GenericSegmentTree2( + ar, + GenericSegmentTree2.SegmentCombinationFn.MIN, + GenericSegmentTree2.RangeUpdateFn.MULTIPLICATION); + + // [4, 2, 6, 8, -2] + st1.rangeUpdate1(0, 4, 2); + assertThat(st1.rangeQuery1(0, 4)).isEqualTo(-2); + assertThat(st1.rangeQuery1(0, 0)).isEqualTo(4); + assertThat(st1.rangeQuery1(0, 1)).isEqualTo(2); + assertThat(st1.rangeQuery1(0, 2)).isEqualTo(2); + assertThat(st1.rangeQuery1(1, 3)).isEqualTo(2); + + // [4, 2, 6, -16, 4] + st1.rangeUpdate1(3, 4, -2); + assertThat(st1.rangeQuery1(0, 4)).isEqualTo(-16); + assertThat(st1.rangeQuery1(0, 0)).isEqualTo(4); + assertThat(st1.rangeQuery1(0, 1)).isEqualTo(2); + assertThat(st1.rangeQuery1(0, 2)).isEqualTo(2); + assertThat(st1.rangeQuery1(1, 3)).isEqualTo(-16); + assertThat(st1.rangeQuery1(3, 4)).isEqualTo(-16); + } + + // Test segment tree min/max with mul range updates. These tests have smaller + // values to avoid overflow + // @Test + // public void testMinMax_mul() { + // GenericSegmentTree2.SegmentCombinationFn[] combinationFns = { + // GenericSegmentTree2.SegmentCombinationFn.MIN, GenericSegmentTree2.SegmentCombinationFn.MAX + // }; + + // GenericSegmentTree2.RangeUpdateFn[] rangeUpdateFns = { + // GenericSegmentTree2.RangeUpdateFn.MULTIPLICATION + // }; + + // for (GenericSegmentTree2.SegmentCombinationFn combinationFn : combinationFns) { + // for (GenericSegmentTree2.RangeUpdateFn rangeUpdateFn : rangeUpdateFns) { + + // for (int n = 5; n < 20; n++) { + // long[] ar = TestUtils.randomLongArray(n, -5, +5); + // GenericSegmentTree2 st = + // new GenericSegmentTree2( + // ar, GenericSegmentTree2.SegmentCombinationFn.MIN, rangeUpdateFn); + // GenericSegmentTree2 st2 = + // new GenericSegmentTree2( + // ar, GenericSegmentTree2.SegmentCombinationFn.MAX, rangeUpdateFn); + // System.out.println(); + + // for (int i = 0; i < n; i++) { + // int j = TestUtils.randValue(0, n - 1); + // int k = TestUtils.randValue(0, n - 1); + // int i1 = Math.min(j, k); + // int i2 = Math.max(j, k); + + // j = TestUtils.randValue(0, n - 1); + // k = TestUtils.randValue(0, n - 1); + // int i3 = Math.min(j, k); + // int i4 = Math.max(j, k); + + // // Range update + // long randValue = TestUtils.randValue(-10, 10); + // System.out.printf("UPDATE [%d, %d] with %d\n", i3, i4, randValue); + + // if (rangeUpdateFn == GenericSegmentTree2.RangeUpdateFn.ADDITION) { + // bruteForceSumRangeUpdate(ar, i3, i4, randValue); + // } else if (rangeUpdateFn == GenericSegmentTree2.RangeUpdateFn.ASSIGN) { + // bruteForceAssignRangeUpdate(ar, i3, i4, randValue); + // } else if (rangeUpdateFn == GenericSegmentTree2.RangeUpdateFn.MULTIPLICATION) { + // bruteForceMulRangeUpdate(ar, i3, i4, randValue); + // } + + // st.rangeUpdate1(i3, i4, randValue); + // st2.rangeUpdate1(i3, i4, randValue); + + // long bf = 0; + + // if (combinationFn == GenericSegmentTree2.SegmentCombinationFn.SUM) { + // bf = bruteForceSum(ar, i1, i2); + // } else if (combinationFn == GenericSegmentTree2.SegmentCombinationFn.MIN) { + // bf = bruteForceMin(ar, i1, i2); + // } else if (combinationFn == GenericSegmentTree2.SegmentCombinationFn.MAX) { + // bf = bruteForceMax(ar, i1, i2); + // } + + // long segTreeAnswer = st.rangeQuery1(i1, i2); + // long segTreeAnswer2 = st2.rangeQuery1(i1, i2); + // System.out.printf( + // "QUERY [%d, %d] want: %d, got: %d, got2: %d\n", + // i1, i2, bf, segTreeAnswer, segTreeAnswer2); + // // System.out.printf("QUERY [%d, %d] want: %d, got: %d\n", i1, i2, bf, + // segTreeAnswer2); + // if (bf != segTreeAnswer) { + // System.out.printf( + // "(%s query, %s range update) | [%d, %d], want = %d, got = %d, got2 = %d\n", + // combinationFn, rangeUpdateFn, i1, i2, bf, segTreeAnswer, segTreeAnswer2); + // } + // assertThat(bf).isEqualTo(segTreeAnswer); + // } + // } + // } + // } + // } + + @Test + public void testAllFunctionCombinations() { + GenericSegmentTree2.SegmentCombinationFn[] combinationFns = { + GenericSegmentTree2.SegmentCombinationFn.SUM, + GenericSegmentTree2.SegmentCombinationFn.MIN, + GenericSegmentTree2.SegmentCombinationFn.MAX, + }; + + GenericSegmentTree2.RangeUpdateFn[] rangeUpdateFns = { + GenericSegmentTree2.RangeUpdateFn.ADDITION, + GenericSegmentTree2.RangeUpdateFn.ASSIGN, + GenericSegmentTree2.RangeUpdateFn.MULTIPLICATION + }; + + for (GenericSegmentTree2.SegmentCombinationFn combinationFn : combinationFns) { + for (GenericSegmentTree2.RangeUpdateFn rangeUpdateFn : rangeUpdateFns) { + + // TODO(issue/208): The multiplication range update function seems to be suffering + // from overflow issues and not being able to handle negative numbers. + // + // One idea might be to also track the min value for the max query and vice versa + // and swap values when a negative number is found? + if (rangeUpdateFn == GenericSegmentTree2.RangeUpdateFn.MULTIPLICATION + && (combinationFn == GenericSegmentTree2.SegmentCombinationFn.MIN + || combinationFn == GenericSegmentTree2.SegmentCombinationFn.MAX)) { + continue; + } + + for (int n = 5; n < ITERATIONS; n++) { + long[] ar = TestUtils.randomLongArray(n, -100, +100); + GenericSegmentTree2 st = new GenericSegmentTree2(ar, combinationFn, rangeUpdateFn); + + for (int i = 0; i < n; i++) { + int j = TestUtils.randValue(0, n - 1); + int k = TestUtils.randValue(0, n - 1); + int i1 = Math.min(j, k); + int i2 = Math.max(j, k); + + j = TestUtils.randValue(0, n - 1); + k = TestUtils.randValue(0, n - 1); + int i3 = Math.min(j, k); + int i4 = Math.max(j, k); + + // Range update + long randValue = TestUtils.randValue(-10, 10); + // System.out.printf("UPDATE [%d, %d] with %d\n", i3, i4, randValue); + + if (rangeUpdateFn == GenericSegmentTree2.RangeUpdateFn.ADDITION) { + bruteForceSumRangeUpdate(ar, i3, i4, randValue); + } else if (rangeUpdateFn == GenericSegmentTree2.RangeUpdateFn.ASSIGN) { + bruteForceAssignRangeUpdate(ar, i3, i4, randValue); + } else if (rangeUpdateFn == GenericSegmentTree2.RangeUpdateFn.MULTIPLICATION) { + bruteForceMulRangeUpdate(ar, i3, i4, randValue); + } + + st.rangeUpdate1(i3, i4, randValue); + + long bf = 0; + + if (combinationFn == GenericSegmentTree2.SegmentCombinationFn.SUM) { + bf = bruteForceSum(ar, i1, i2); + } else if (combinationFn == GenericSegmentTree2.SegmentCombinationFn.MIN) { + bf = bruteForceMin(ar, i1, i2); + } else if (combinationFn == GenericSegmentTree2.SegmentCombinationFn.MAX) { + bf = bruteForceMax(ar, i1, i2); + } + + long segTreeAnswer = st.rangeQuery1(i1, i2); + if (bf != segTreeAnswer) { + System.out.printf( + "(%s query, %s range update) | [%d, %d], want = %d, got = %d\n", + combinationFn, rangeUpdateFn, i1, i2, bf, segTreeAnswer); + } + assertThat(bf).isEqualTo(segTreeAnswer); + } + } + } + } + } + + // Finds the sum in an array between [l, r] in the `values` array + private static long bruteForceSum(long[] values, int l, int r) { + long s = 0; + for (int i = l; i <= r; i++) { + s += values[i]; + } + return s; + } + + // Finds the min value in an array between [l, r] in the `values` array + private static long bruteForceMin(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.min(m, values[i]); + } + return m; + } + + // Finds the max value in an array between [l, r] in the `values` array + private static long bruteForceMax(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.max(m, values[i]); + } + return m; + } + + private static void bruteForceSumRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] += x; + } + } + + private static void bruteForceMulRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] *= x; + } + } + + private static void bruteForceAssignRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] = x; + } + } +} diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTreeTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTreeTest.java new file mode 100644 index 000000000..38d0b4bfe --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/GenericSegmentTreeTest.java @@ -0,0 +1,412 @@ +/** + * gradle test --info --tests + * "com.williamfiset.algorithms.datastructures.segmenttree.GenericSegmentTreeTest" + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +import static com.google.common.truth.Truth.assertThat; + +import com.williamfiset.algorithms.utils.TestUtils; +import org.junit.jupiter.api.Test; + +public class GenericSegmentTreeTest { + + static int ITERATIONS = 250; + static int MAX_N = 17; + + // @Before + // public void setup() {} + + // @Test + // public void testSumQuerySumUpdate_Simple() { + // long[] values = {1, 2, 3, 4, 5}; + // GenericSegmentTree st = + // new GenericSegmentTree( + // values, + // GenericSegmentTree.SegmentCombinationFn.SUM, + // GenericSegmentTree.RangeUpdateFn.ADDITION); + + // assertThat(st.rangeQuery1(0, 1)).isEqualTo(3); + // assertThat(st.rangeQuery1(2, 2)).isEqualTo(3); + // assertThat(st.rangeQuery1(0, 4)).isEqualTo(15); + // } + + // @Test + // public void testSumQuerySumUpdate_RangeUpdate() { + // // 0, 1, 2, 3, 4 + // long[] ar = {1, 2, 1, 2, 1}; + // GenericSegmentTree st = + // new GenericSegmentTree( + // ar, + // GenericSegmentTree.SegmentCombinationFn.SUM, + // GenericSegmentTree.RangeUpdateFn.ADDITION); + + // // Do multiple range updates + // st.rangeUpdate1(0, 1, 5); + // st.rangeUpdate1(3, 4, 2); + // st.rangeUpdate1(0, 4, 3); + + // // Point queries + // assertThat(st.rangeQuery1(0, 0)).isEqualTo(1 + 3 + 5); + // assertThat(st.rangeQuery1(1, 1)).isEqualTo(2 + 3 + 5); + // assertThat(st.rangeQuery1(2, 2)).isEqualTo(1 + 3); + // assertThat(st.rangeQuery1(3, 3)).isEqualTo(2 + 3 + 2); + // assertThat(st.rangeQuery1(4, 4)).isEqualTo(2 + 3 + 1); + + // // Range queries + // assertThat(st.rangeQuery1(0, 1)).isEqualTo(2 * 5 + 2 * 3 + 1 + 2); + // assertThat(st.rangeQuery1(0, 2)).isEqualTo(2 * 5 + 3 * 3 + 1 + 2 + 1); + // assertThat(st.rangeQuery1(3, 4)).isEqualTo(2 * 2 + 2 * 3 + 2 + 1); + // assertThat(st.rangeQuery1(0, 4)).isEqualTo(2 * 5 + 2 * 2 + 3 * 5 + 1 + 1 + 1 + 2 + 2); + // } + + // @Test + // public void testSumQueryAssignUpdate_simple() { + // long[] ar = {2, 1, 3, 4, -1}; + // GenericSegmentTree st = + // new GenericSegmentTree( + // ar, + // GenericSegmentTree.SegmentCombinationFn.SUM, + // GenericSegmentTree.RangeUpdateFn.ASSIGN); + + // st.rangeUpdate1(3, 4, 2); + + // assertThat(st.rangeQuery1(0, 4)).isEqualTo(10); + // assertThat(st.rangeQuery1(3, 4)).isEqualTo(4); + // assertThat(st.rangeQuery1(3, 3)).isEqualTo(2); + // assertThat(st.rangeQuery1(4, 4)).isEqualTo(2); + + // st.rangeUpdate1(1, 3, 4); + + // assertThat(st.rangeQuery1(0, 4)).isEqualTo(16); + // assertThat(st.rangeQuery1(0, 1)).isEqualTo(6); + // assertThat(st.rangeQuery1(3, 4)).isEqualTo(6); + // assertThat(st.rangeQuery1(1, 1)).isEqualTo(4); + // assertThat(st.rangeQuery1(2, 2)).isEqualTo(4); + // assertThat(st.rangeQuery1(3, 3)).isEqualTo(4); + // assertThat(st.rangeQuery1(1, 3)).isEqualTo(12); + // assertThat(st.rangeQuery1(2, 3)).isEqualTo(8); + // assertThat(st.rangeQuery1(1, 2)).isEqualTo(8); + + // st.rangeUpdate1(2, 2, 5); + + // assertThat(st.rangeQuery1(0, 4)).isEqualTo(17); + // assertThat(st.rangeQuery1(0, 2)).isEqualTo(11); + // assertThat(st.rangeQuery1(2, 4)).isEqualTo(11); + // assertThat(st.rangeQuery1(1, 3)).isEqualTo(13); + // assertThat(st.rangeQuery1(2, 2)).isEqualTo(5); + // } + + // @Test + // public void testSumQueryMulUpdate_simple() { + // long[] ar = {1, 4, 5, 3, 2}; + // GenericSegmentTree st = + // new GenericSegmentTree( + // ar, + // GenericSegmentTree.SegmentCombinationFn.SUM, + // GenericSegmentTree.RangeUpdateFn.MULTIPLICATION); + + // st.rangeUpdate1(1, 3, 3); + + // assertThat(st.rangeQuery1(1, 3)).isEqualTo(4 * 3 + 5 * 3 + 3 * 3); + // assertThat(st.rangeQuery1(0, 4)).isEqualTo(1 + 4 * 3 + 5 * 3 + 3 * 3 + 2); + // assertThat(st.rangeQuery1(0, 2)).isEqualTo(1 + 4 * 3 + 5 * 3); + // assertThat(st.rangeQuery1(2, 4)).isEqualTo(5 * 3 + 3 * 3 + 2); + + // st.rangeUpdate1(1, 3, 2); + // assertThat(st.rangeQuery1(1, 3)).isEqualTo(4 * 3 * 2 + 5 * 3 * 2 + 3 * 3 * 2); + // } + + // @Test + // public void minQuerySumUpdates_simple() { + // long[] ar = {2, 1, 3, 4, -1}; + // GenericSegmentTree st = + // new GenericSegmentTree( + // ar, + // GenericSegmentTree.SegmentCombinationFn.MIN, + // GenericSegmentTree.RangeUpdateFn.ADDITION); + + // st.rangeUpdate1(0, 4, 1); + + // assertThat(st.rangeQuery1(0, 4)).isEqualTo(0); + // assertThat(st.rangeQuery1(1, 3)).isEqualTo(2); + // assertThat(st.rangeQuery1(2, 4)).isEqualTo(0); + // assertThat(st.rangeQuery1(3, 3)).isEqualTo(5); + + // st.rangeUpdate1(3, 4, 4); + + // assertThat(st.rangeQuery1(0, 4)).isEqualTo(2); + // assertThat(st.rangeQuery1(0, 1)).isEqualTo(2); + // assertThat(st.rangeQuery1(3, 4)).isEqualTo(4); + // assertThat(st.rangeQuery1(1, 1)).isEqualTo(2); + // assertThat(st.rangeQuery1(2, 2)).isEqualTo(4); + // assertThat(st.rangeQuery1(3, 3)).isEqualTo(9); + // assertThat(st.rangeQuery1(1, 3)).isEqualTo(2); + // assertThat(st.rangeQuery1(2, 3)).isEqualTo(4); + // assertThat(st.rangeQuery1(1, 2)).isEqualTo(2); + + // st.rangeUpdate1(1, 3, 3); + + // assertThat(st.rangeQuery1(0, 4)).isEqualTo(3); + // assertThat(st.rangeQuery1(0, 2)).isEqualTo(3); + // assertThat(st.rangeQuery1(2, 4)).isEqualTo(4); + // assertThat(st.rangeQuery1(1, 3)).isEqualTo(5); + // assertThat(st.rangeQuery1(0, 0)).isEqualTo(3); + // assertThat(st.rangeQuery1(1, 1)).isEqualTo(5); + // assertThat(st.rangeQuery1(2, 2)).isEqualTo(7); + // assertThat(st.rangeQuery1(3, 3)).isEqualTo(12); + // assertThat(st.rangeQuery1(4, 4)).isEqualTo(4); + // } + + // @Test + // public void maxQuerySumUpdate_simple() { + // long[] ar = {2, 1, 3, 4, -1}; + // GenericSegmentTree st = + // new GenericSegmentTree( + // ar, + // GenericSegmentTree.SegmentCombinationFn.MAX, + // GenericSegmentTree.RangeUpdateFn.ADDITION); + + // st.printDebugInfo(); + // st.rangeUpdate1(0, 4, 1); + // st.printDebugInfo(); + + // assertThat(st.rangeQuery1(0, 4)).isEqualTo(5); + // assertThat(st.rangeQuery1(0, 1)).isEqualTo(3); + // assertThat(st.rangeQuery1(1, 2)).isEqualTo(4); + // assertThat(st.rangeQuery1(1, 3)).isEqualTo(5); + + // st.rangeUpdate1(3, 4, 4); + + // assertThat(st.rangeQuery1(0, 4)).isEqualTo(9); + // assertThat(st.rangeQuery1(0, 1)).isEqualTo(3); + // assertThat(st.rangeQuery1(3, 4)).isEqualTo(9); + // assertThat(st.rangeQuery1(1, 1)).isEqualTo(2); + // assertThat(st.rangeQuery1(2, 2)).isEqualTo(4); + // assertThat(st.rangeQuery1(3, 3)).isEqualTo(9); + // assertThat(st.rangeQuery1(1, 3)).isEqualTo(9); + // assertThat(st.rangeQuery1(2, 3)).isEqualTo(9); + // assertThat(st.rangeQuery1(1, 2)).isEqualTo(4); + + // st.rangeUpdate1(1, 3, 3); + + // assertThat(st.rangeQuery1(0, 4)).isEqualTo(12); + // assertThat(st.rangeQuery1(0, 2)).isEqualTo(7); + // assertThat(st.rangeQuery1(2, 4)).isEqualTo(12); + // assertThat(st.rangeQuery1(1, 3)).isEqualTo(12); + // assertThat(st.rangeQuery1(0, 0)).isEqualTo(3); + // assertThat(st.rangeQuery1(1, 1)).isEqualTo(5); + // assertThat(st.rangeQuery1(2, 2)).isEqualTo(7); + // assertThat(st.rangeQuery1(3, 3)).isEqualTo(12); + // assertThat(st.rangeQuery1(4, 4)).isEqualTo(4); + // } + + // @Test + // public void maxQueryMulUpdate_simple() { + // long[] ar = {2, 1, 3, 4, -1}; + // GenericSegmentTree st = + // new GenericSegmentTree( + // ar, + // GenericSegmentTree.SegmentCombinationFn.MAX, + // GenericSegmentTree.RangeUpdateFn.MULTIPLICATION); + + // st.rangeUpdate1(0, 4, 1); + // assertThat(st.rangeQuery1(0, 4)).isEqualTo(4); + + // // TODO(issue/208): Negative numbers are a known issue + // // st.rangeUpdate1(0, 4, -2); + // // assertThat(st.rangeQuery1(0, 4)).isEqualTo(2); // Returns -8 as max but should be 2 + // } + + @Test + public void testAllFunctionCombinations() { + GenericSegmentTree.SegmentCombinationFn[] combinationFns = { + GenericSegmentTree.SegmentCombinationFn.SUM, + GenericSegmentTree.SegmentCombinationFn.MIN, + GenericSegmentTree.SegmentCombinationFn.MAX, + GenericSegmentTree.SegmentCombinationFn.GCD, + // GenericSegmentTree.SegmentCombinationFn.PRODUCT, + }; + + GenericSegmentTree.RangeUpdateFn[] rangeUpdateFns = { + GenericSegmentTree.RangeUpdateFn.ADDITION, + GenericSegmentTree.RangeUpdateFn.ASSIGN, + GenericSegmentTree.RangeUpdateFn.MULTIPLICATION + }; + + for (GenericSegmentTree.SegmentCombinationFn combinationFn : combinationFns) { + for (GenericSegmentTree.RangeUpdateFn rangeUpdateFn : rangeUpdateFns) { + + // TODO(issue/208): The multiplication range update function seems to be suffering + // from overflow issues and not being able to handle negative numbers. + // + // One idea might be to also track the min value for the max query and vice versa + // and swap values when a negative number is found? + if (rangeUpdateFn == GenericSegmentTree.RangeUpdateFn.MULTIPLICATION + && (combinationFn == GenericSegmentTree.SegmentCombinationFn.MIN + || combinationFn == GenericSegmentTree.SegmentCombinationFn.MAX)) { + continue; + } + + if (combinationFn == GenericSegmentTree.SegmentCombinationFn.GCD + && rangeUpdateFn == GenericSegmentTree.RangeUpdateFn.ADDITION) { + // Not supported + continue; + } + + for (int n = 5, loop = 0; loop < ITERATIONS; loop++, n++) { + + // Prevent overflow for gcd multiplication tests + if (n > MAX_N && combinationFn == GenericSegmentTree.SegmentCombinationFn.GCD) { + n = MAX_N; + } + + long[] ar = generateRandomArrayByTestType(n, combinationFn); + GenericSegmentTree st = new GenericSegmentTree(ar, combinationFn, rangeUpdateFn); + + for (int i = 0; i < n; i++) { + // System.out.printf("i = %d\n", i); + int j = TestUtils.randValue(0, n - 1); + int k = TestUtils.randValue(0, n - 1); + int i1 = Math.min(j, k); + int i2 = Math.max(j, k); + + j = TestUtils.randValue(0, n - 1); + k = TestUtils.randValue(0, n - 1); + int i3 = Math.min(j, k); + int i4 = Math.max(j, k); + + // Range update + long randValue = getRandValueByTestType(combinationFn); + // System.out.printf("UPDATE [%d, %d] with %d\n", i3, i4, randValue); + + if (rangeUpdateFn == GenericSegmentTree.RangeUpdateFn.ADDITION) { + bruteForceSumRangeUpdate(ar, i3, i4, randValue); + } else if (rangeUpdateFn == GenericSegmentTree.RangeUpdateFn.ASSIGN) { + bruteForceAssignRangeUpdate(ar, i3, i4, randValue); + } else if (rangeUpdateFn == GenericSegmentTree.RangeUpdateFn.MULTIPLICATION) { + bruteForceMulRangeUpdate(ar, i3, i4, randValue); + } + + st.rangeUpdate1(i3, i4, randValue); + + // Range query + long bf = 0; + + if (combinationFn == GenericSegmentTree.SegmentCombinationFn.SUM) { + bf = bruteForceSum(ar, i1, i2); + } else if (combinationFn == GenericSegmentTree.SegmentCombinationFn.MIN) { + bf = bruteForceMin(ar, i1, i2); + } else if (combinationFn == GenericSegmentTree.SegmentCombinationFn.MAX) { + bf = bruteForceMax(ar, i1, i2); + } else if (combinationFn == GenericSegmentTree.SegmentCombinationFn.GCD) { + bf = bruteForceGcd(ar, i1, i2); + } else if (combinationFn == GenericSegmentTree.SegmentCombinationFn.PRODUCT) { + bf = bruteForceMul(ar, i1, i2); + } + + long segTreeAnswer = st.rangeQuery1(i1, i2); + if (bf != segTreeAnswer) { + + System.out.printf( + "Range query type: %s, range update type: %s, QUERY [%d, %d], want = %d, got = %d\n", + combinationFn, rangeUpdateFn, i1, i2, bf, segTreeAnswer); + + System.out.println(java.util.Arrays.toString(ar)); + } + assertThat(segTreeAnswer).isEqualTo(bf); + } + } + } + } + } + + private static long getRandValueByTestType( + GenericSegmentTree.SegmentCombinationFn combinationFn) { + if (combinationFn != GenericSegmentTree.SegmentCombinationFn.GCD) { + return TestUtils.randValue(-10, 10); + } + return TestUtils.randValue(1, 10); + } + + private static long[] generateRandomArrayByTestType( + int n, GenericSegmentTree.SegmentCombinationFn combinationFn) { + // GCD doesn't play well with negative numbers + if (combinationFn != GenericSegmentTree.SegmentCombinationFn.GCD) { + return TestUtils.randomLongArray(n, -100, +100); + } + return TestUtils.randomLongArray(n, 1, +10); + } + + // Finds the sum in an array between [l, r] in the `values` array + private static long bruteForceSum(long[] values, int l, int r) { + long s = 0; + for (int i = l; i <= r; i++) { + s += values[i]; + } + return s; + } + + // Finds the min value in an array between [l, r] in the `values` array + private static long bruteForceMin(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.min(m, values[i]); + } + return m; + } + + // Finds the max value in an array between [l, r] in the `values` array + private static long bruteForceMax(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.max(m, values[i]); + } + return m; + } + + private static long bruteForceMul(long[] values, int l, int r) { + long m = 1L; + for (int i = l; i <= r; i++) { + m *= values[i]; + } + return m; + } + + private static long gcd(long a, long b) { + long gcd = a; + while (b != 0) { + gcd = b; + b = a % b; + a = gcd; + } + return Math.abs(gcd); + } + + // Finds the sum in an array between [l, r] in the `values` array + private static long bruteForceGcd(long[] values, int l, int r) { + long s = values[l]; + for (int i = l; i <= r; i++) { + s = gcd(s, values[i]); + } + return s; + } + + private static void bruteForceSumRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] += x; + } + } + + private static void bruteForceMulRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] *= x; + } + } + + private static void bruteForceAssignRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] = x; + } + } +} diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/MaxQuerySumUpdateSegmentTreeTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/MaxQuerySumUpdateSegmentTreeTest.java new file mode 100644 index 000000000..c1ac5e1af --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/MaxQuerySumUpdateSegmentTreeTest.java @@ -0,0 +1,133 @@ +/** + * gradle test --info --tests + * "com.williamfiset.algorithms.datastructures.segmenttree.MaxQuerySumUpdateSegmentTreeTest" + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +import static com.google.common.truth.Truth.assertThat; + +import com.williamfiset.algorithms.utils.TestUtils; +import org.junit.jupiter.api.*; + +public class MaxQuerySumUpdateSegmentTreeTest { + + static int ITERATIONS = 1000; + + @BeforeEach + public void setup() {} + + @Test + public void simpleTest() { + long[] ar = {2, 1, 3, 4, -1}; + MaxQuerySumUpdateSegmentTree st = new MaxQuerySumUpdateSegmentTree(ar); + + st.rangeUpdate1(0, 4, 1); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(5); + assertThat(st.rangeQuery1(0, 1)).isEqualTo(3); + assertThat(st.rangeQuery1(1, 2)).isEqualTo(4); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(5); + + st.rangeUpdate1(3, 4, 4); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(9); + assertThat(st.rangeQuery1(0, 1)).isEqualTo(3); + assertThat(st.rangeQuery1(3, 4)).isEqualTo(9); + assertThat(st.rangeQuery1(1, 1)).isEqualTo(2); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(4); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(9); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(9); + assertThat(st.rangeQuery1(2, 3)).isEqualTo(9); + assertThat(st.rangeQuery1(1, 2)).isEqualTo(4); + + st.rangeUpdate1(1, 3, 3); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(12); + assertThat(st.rangeQuery1(0, 2)).isEqualTo(7); + assertThat(st.rangeQuery1(2, 4)).isEqualTo(12); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(12); + assertThat(st.rangeQuery1(0, 0)).isEqualTo(3); + assertThat(st.rangeQuery1(1, 1)).isEqualTo(5); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(7); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(12); + assertThat(st.rangeQuery1(4, 4)).isEqualTo(4); + } + + @Test + public void testRandomRangeSumUpdatesWithSumRangeQueries() { + for (int n = 5; n < ITERATIONS; n++) { + long[] ar = TestUtils.randomLongArray(n, -100, +100); + MaxQuerySumUpdateSegmentTree st = new MaxQuerySumUpdateSegmentTree(ar); + + for (int i = 0; i < n; i++) { + // System.out.printf("n = %d, i = %d\n", n, i); + int j = TestUtils.randValue(0, n - 1); + int k = TestUtils.randValue(0, n - 1); + int i1 = Math.min(j, k); + int i2 = Math.max(j, k); + + j = TestUtils.randValue(0, n - 1); + k = TestUtils.randValue(0, n - 1); + int i3 = Math.min(j, k); + int i4 = Math.max(j, k); + + // Range update + long randValue = TestUtils.randValue(-10, 10); + // System.out.printf("UPDATE [%d, %d] with %d\n", i3, i4, randValue); + bruteForceSumRangeUpdate(ar, i3, i4, randValue); + st.rangeUpdate1(i3, i4, randValue); + + // Range query + long bfMax = bruteForceMax(ar, i1, i2); + long segTreeMax = st.rangeQuery1(i1, i2); + // System.out.printf("QUERY [%d, %d], want = %d, got = %d\n", i1, i2, bfMax, segTreeMax); + assertThat(bfMax).isEqualTo(segTreeMax); + } + } + } + + // Finds the sum in an array between [l, r] in the `values` array + private static long bruteForceSum(long[] values, int l, int r) { + long s = 0; + for (int i = l; i <= r; i++) { + s += values[i]; + } + return s; + } + + // Finds the min value in an array between [l, r] in the `values` array + private static long bruteForceMin(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.min(m, values[i]); + } + return m; + } + + // Finds the max value in an array between [l, r] in the `values` array + private static long bruteForceMax(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.max(m, values[i]); + } + return m; + } + + private static void bruteForceSumRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] += x; + } + } + + private static void bruteForceMulRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] *= x; + } + } + + private static void bruteForceAssignRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] = x; + } + } +} diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/MinQueryAssignUpdateSegmentTreeTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/MinQueryAssignUpdateSegmentTreeTest.java new file mode 100644 index 000000000..e12fd4269 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/MinQueryAssignUpdateSegmentTreeTest.java @@ -0,0 +1,123 @@ +/** + * gradle test --info --tests + * "com.williamfiset.algorithms.datastructures.segmenttree.MinQueryAssignUpdateSegmentTreeTest" + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +import static com.google.common.truth.Truth.assertThat; + +import com.williamfiset.algorithms.utils.TestUtils; +import org.junit.jupiter.api.*; + +public class MinQueryAssignUpdateSegmentTreeTest { + + static int ITERATIONS = 500; + + @BeforeEach + public void setup() {} + + @Test + public void testRandomRangeAssignUpdates1WithMinRangeQueries1() { + for (int n = 5; n < ITERATIONS; n++) { + long[] ar = TestUtils.randomLongArray(n, -1000, +1000); + MinQueryAssignUpdateSegmentTree st = new MinQueryAssignUpdateSegmentTree(ar); + + for (int i = 0; i < n; i++) { + // System.out.printf("n = %d, i = %d\n", n, i); + int j = TestUtils.randValue(0, n - 1); + int k = TestUtils.randValue(0, n - 1); + int i1 = Math.min(j, k); + int i2 = Math.max(j, k); + + // Range query + long bfMin = bruteForceMin(ar, i1, i2); + long segTreeMin = st.rangeQuery1(i1, i2); + assertThat(bfMin).isEqualTo(segTreeMin); + + // Range update + j = TestUtils.randValue(0, n - 1); + k = TestUtils.randValue(0, n - 1); + int i3 = Math.min(j, k); + int i4 = Math.max(j, k); + long randValue = TestUtils.randValue(-1000, 1000); + st.rangeUpdate1(i3, i4, randValue); + bruteForceAssignRangeUpdate(ar, i3, i4, randValue); + } + } + } + + // @Test + // public void testRandomRangeAssignUpdates2WithMinRangeQueries1() { + // for (int n = 5; n < ITERATIONS; n++) { + // long[] ar = TestUtils.randomLongArray(n, -1000, +1000); + // MinQueryAssignUpdateSegmentTree st = new MinQueryAssignUpdateSegmentTree(ar); + + // for (int i = 0; i < n; i++) { + // // System.out.printf("n = %d, i = %d\n", n, i); + // int j = TestUtils.randValue(0, n - 1); + // int k = TestUtils.randValue(0, n - 1); + // int i1 = Math.min(j, k); + // int i2 = Math.max(j, k); + + // // Range query + // long bfMin = bruteForceMin(ar, i1, i2); + // long segTreeMin = st.rangeQuery2(i1, i2); + // assertThat(bfMin).isEqualTo(segTreeMin); + + // // Range update + // j = TestUtils.randValue(0, n - 1); + // k = TestUtils.randValue(0, n - 1); + // int i3 = Math.min(j, k); + // int i4 = Math.max(j, k); + // long randValue = TestUtils.randValue(-1000, 1000); + // st.rangeUpdate2(i3, i4, randValue); + // bruteForceAssignRangeUpdate(ar, i3, i4, randValue); + // } + // } + // } + + // Finds the sum in an array between [l, r] in the `values` array + private static long bruteForceSum(long[] values, int l, int r) { + long s = 0; + for (int i = l; i <= r; i++) { + s += values[i]; + } + return s; + } + + // Finds the min value in an array between [l, r] in the `values` array + private static long bruteForceMin(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.min(m, values[i]); + } + return m; + } + + // Finds the max value in an array between [l, r] in the `values` array + private static long bruteForceMax(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.max(m, values[i]); + } + return m; + } + + private static void bruteForceSumRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] += x; + } + } + + private static void bruteForceMulRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] *= x; + } + } + + private static void bruteForceAssignRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] = x; + } + } +} diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/MinQuerySumUpdateSegmentTreeTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/MinQuerySumUpdateSegmentTreeTest.java new file mode 100644 index 000000000..31b3498a9 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/MinQuerySumUpdateSegmentTreeTest.java @@ -0,0 +1,133 @@ +/** + * gradle test --info --tests + * "com.williamfiset.algorithms.datastructures.segmenttree.MinQuerySumUpdateSegmentTreeTest" + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +import static com.google.common.truth.Truth.assertThat; + +import com.williamfiset.algorithms.utils.TestUtils; +import org.junit.jupiter.api.*; + +public class MinQuerySumUpdateSegmentTreeTest { + + static int ITERATIONS = 1000; + + @BeforeEach + public void setup() {} + + @Test + public void simpleTest() { + long[] ar = {2, 1, 3, 4, -1}; + MinQuerySumUpdateSegmentTree st = new MinQuerySumUpdateSegmentTree(ar); + + st.rangeUpdate1(0, 4, 1); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(0); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(2); + assertThat(st.rangeQuery1(2, 4)).isEqualTo(0); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(5); + + st.rangeUpdate1(3, 4, 4); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(2); + assertThat(st.rangeQuery1(0, 1)).isEqualTo(2); + assertThat(st.rangeQuery1(3, 4)).isEqualTo(4); + assertThat(st.rangeQuery1(1, 1)).isEqualTo(2); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(4); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(9); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(2); + assertThat(st.rangeQuery1(2, 3)).isEqualTo(4); + assertThat(st.rangeQuery1(1, 2)).isEqualTo(2); + + st.rangeUpdate1(1, 3, 3); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(3); + assertThat(st.rangeQuery1(0, 2)).isEqualTo(3); + assertThat(st.rangeQuery1(2, 4)).isEqualTo(4); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(5); + assertThat(st.rangeQuery1(0, 0)).isEqualTo(3); + assertThat(st.rangeQuery1(1, 1)).isEqualTo(5); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(7); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(12); + assertThat(st.rangeQuery1(4, 4)).isEqualTo(4); + } + + @Test + public void testRandomRangeSumUpdatesWithSumRangeQueries() { + for (int n = 5; n < ITERATIONS; n++) { + long[] ar = TestUtils.randomLongArray(n, -100, +100); + MinQuerySumUpdateSegmentTree st = new MinQuerySumUpdateSegmentTree(ar); + + for (int i = 0; i < n; i++) { + // System.out.printf("n = %d, i = %d\n", n, i); + int j = TestUtils.randValue(0, n - 1); + int k = TestUtils.randValue(0, n - 1); + int i1 = Math.min(j, k); + int i2 = Math.max(j, k); + + j = TestUtils.randValue(0, n - 1); + k = TestUtils.randValue(0, n - 1); + int i3 = Math.min(j, k); + int i4 = Math.max(j, k); + + // Range update + long randValue = TestUtils.randValue(-10, 10); + // System.out.printf("UPDATE [%d, %d] with %d\n", i3, i4, randValue); + bruteForceSumRangeUpdate(ar, i3, i4, randValue); + st.rangeUpdate1(i3, i4, randValue); + + // Range query + long bfMin = bruteForceMin(ar, i1, i2); + long segTreeMin = st.rangeQuery1(i1, i2); + // System.out.printf("QUERY [%d, %d], want = %d, got = %d\n", i1, i2, bfMin, segTreeMin); + assertThat(bfMin).isEqualTo(segTreeMin); + } + } + } + + // Finds the sum in an array between [l, r] in the `values` array + private static long bruteForceSum(long[] values, int l, int r) { + long s = 0; + for (int i = l; i <= r; i++) { + s += values[i]; + } + return s; + } + + // Finds the min value in an array between [l, r] in the `values` array + private static long bruteForceMin(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.min(m, values[i]); + } + return m; + } + + // Finds the max value in an array between [l, r] in the `values` array + private static long bruteForceMax(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.max(m, values[i]); + } + return m; + } + + private static void bruteForceSumRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] += x; + } + } + + private static void bruteForceMulRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] *= x; + } + } + + private static void bruteForceAssignRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] = x; + } + } +} diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/SegmentTreeWithPointersTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/SegmentTreeWithPointersTest.java new file mode 100644 index 000000000..9d9f8374c --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/SegmentTreeWithPointersTest.java @@ -0,0 +1,70 @@ +// gradle test --info --tests +// "com.williamfiset.algorithms.datastructures.segmenttree.SegmentTreeWithPointersTest" +package com.williamfiset.algorithms.datastructures.segmenttree; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.williamfiset.algorithms.utils.TestUtils; +import org.junit.jupiter.api.*; + +public class SegmentTreeWithPointersTest { + + @BeforeEach + public void setup() {} + + @Test + public void testIllegalSegmentTreeCreation1() { + assertThrows( + IllegalArgumentException.class, + () -> { + Node tree = new Node(null); + }); + } + + @Test + public void testIllegalSegmentTreeCreation2() { + assertThrows( + IllegalArgumentException.class, + () -> { + int size = -10; + Node tree = new Node(size); + }); + } + + @Test + public void testSumQuery() { + int[] values = {1, 2, 3, 4, 5}; + Node tree = new Node(values); + + assertThat(tree.sum(0, 1)).isEqualTo(1); + assertThat(tree.sum(1, 2)).isEqualTo(2); + assertThat(tree.sum(2, 3)).isEqualTo(3); + assertThat(tree.sum(3, 4)).isEqualTo(4); + assertThat(tree.sum(4, 5)).isEqualTo(5); + } + + @Test + public void testAllSumQueries() { + int n = 100; + int[] ar = TestUtils.randomIntegerArray(n, -1000, +1000); + Node tree = new Node(ar); + + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + long bfSum = bruteForceSum(ar, i, j); + long segTreeSum = tree.sum(i, j); + assertThat(bfSum).isEqualTo(segTreeSum); + } + } + } + + // Finds the sum in an array between [l, r) in the `values` array + private static long bruteForceSum(int[] values, int l, int r) { + long s = 0; + for (int i = l; i < r; i++) { + s += values[i]; + } + return s; + } +} diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQueryAssignUpdateSegmentTreeTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQueryAssignUpdateSegmentTreeTest.java new file mode 100644 index 000000000..74ac738ea --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQueryAssignUpdateSegmentTreeTest.java @@ -0,0 +1,127 @@ +/** + * gradle test --info --tests + * "com.williamfiset.algorithms.datastructures.segmenttree.SumQueryAssignUpdateSegmentTreeTest" + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +import static com.google.common.truth.Truth.assertThat; + +import com.williamfiset.algorithms.utils.TestUtils; +import org.junit.jupiter.api.*; + +public class SumQueryAssignUpdateSegmentTreeTest { + + static int ITERATIONS = 100; + + @BeforeEach + public void setup() {} + + @Test + public void simpleTest() { + long[] ar = {2, 1, 3, 4, -1}; + SumQueryAssignUpdateSegmentTree st = new SumQueryAssignUpdateSegmentTree(ar); + + st.rangeUpdate1(3, 4, 2); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(10); + assertThat(st.rangeQuery1(3, 4)).isEqualTo(4); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(2); + assertThat(st.rangeQuery1(4, 4)).isEqualTo(2); + + st.rangeUpdate1(1, 3, 4); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(16); + assertThat(st.rangeQuery1(0, 1)).isEqualTo(6); + assertThat(st.rangeQuery1(3, 4)).isEqualTo(6); + assertThat(st.rangeQuery1(1, 1)).isEqualTo(4); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(4); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(4); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(12); + assertThat(st.rangeQuery1(2, 3)).isEqualTo(8); + assertThat(st.rangeQuery1(1, 2)).isEqualTo(8); + + st.rangeUpdate1(2, 2, 5); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(17); + assertThat(st.rangeQuery1(0, 2)).isEqualTo(11); + assertThat(st.rangeQuery1(2, 4)).isEqualTo(11); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(13); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(5); + } + + @Test + public void testRandomRangeAssignUpdatesWithSumRangeQueries() { + for (int n = 5; n < ITERATIONS; n++) { + long[] ar = TestUtils.randomLongArray(n, -100, +100); + SumQueryAssignUpdateSegmentTree st = new SumQueryAssignUpdateSegmentTree(ar); + + for (int i = 0; i < n; i++) { + // System.out.printf("n = %d, i = %d\n", n, i); + int j = TestUtils.randValue(0, n - 1); + int k = TestUtils.randValue(0, n - 1); + int i1 = Math.min(j, k); + int i2 = Math.max(j, k); + + // Range query + long bfSum = bruteForceSum(ar, i1, i2); + long segTreeSum = st.rangeQuery1(i1, i2); + assertThat(bfSum).isEqualTo(segTreeSum); + + // Range update + j = TestUtils.randValue(0, n - 1); + k = TestUtils.randValue(0, n - 1); + int i3 = Math.min(j, k); + int i4 = Math.max(j, k); + long randValue = TestUtils.randValue(-100, 100); + // System.out.printf("Update [%d, %d] to %d\n", i3, i4, randValue); + st.rangeUpdate1(i3, i4, randValue); + bruteForceAssignRangeUpdate(ar, i3, i4, randValue); + } + } + } + + // Finds the sum in an array between [l, r] in the `values` array + private static long bruteForceSum(long[] values, int l, int r) { + long s = 0; + for (int i = l; i <= r; i++) { + s += values[i]; + } + return s; + } + + // Finds the min value in an array between [l, r] in the `values` array + private static long bruteForceMin(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.min(m, values[i]); + } + return m; + } + + // Finds the max value in an array between [l, r] in the `values` array + private static long bruteForceMax(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.max(m, values[i]); + } + return m; + } + + private static void bruteForceSumRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] += x; + } + } + + private static void bruteForceMulRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] *= x; + } + } + + private static void bruteForceAssignRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] = x; + } + } +} diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQueryMultiplicationUpdateSegmentTreeTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQueryMultiplicationUpdateSegmentTreeTest.java new file mode 100644 index 000000000..432fbd98b --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQueryMultiplicationUpdateSegmentTreeTest.java @@ -0,0 +1,113 @@ +/** + * gradle test --info --tests + * "com.williamfiset.algorithms.datastructures.segmenttree.SumQueryMultiplicationUpdateSegmentTreeTest" + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +import static com.google.common.truth.Truth.assertThat; + +import com.williamfiset.algorithms.utils.TestUtils; +import org.junit.jupiter.api.*; + +public class SumQueryMultiplicationUpdateSegmentTreeTest { + + static int ITERATIONS = 100; + static int MAX_N = 28; + + @BeforeEach + public void setup() {} + + @Test + public void simpleTest() { + long[] ar = {1, 4, 5, 3, 2}; + SumQueryMultiplicationUpdateSegmentTree st = new SumQueryMultiplicationUpdateSegmentTree(ar); + + st.rangeUpdate1(1, 3, 3); + + assertThat(st.rangeQuery1(1, 3)).isEqualTo(4 * 3 + 5 * 3 + 3 * 3); + assertThat(st.rangeQuery1(0, 4)).isEqualTo(1 + 4 * 3 + 5 * 3 + 3 * 3 + 2); + assertThat(st.rangeQuery1(0, 2)).isEqualTo(1 + 4 * 3 + 5 * 3); + assertThat(st.rangeQuery1(2, 4)).isEqualTo(5 * 3 + 3 * 3 + 2); + + st.rangeUpdate1(1, 3, 2); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(4 * 3 * 2 + 5 * 3 * 2 + 3 * 3 * 2); + } + + @Test + public void testRandomRangeSumUpdatesWithSumRangeQueries() { + for (int n = 5; n < MAX_N; n++) { + long[] ar = TestUtils.randomLongArray(n, -10, +10); + SumQueryMultiplicationUpdateSegmentTree st = + new SumQueryMultiplicationUpdateSegmentTree(ar.clone()); + + for (int i = 0; i < ITERATIONS; i++) { + int j = TestUtils.randValue(0, n - 1); + int k = TestUtils.randValue(0, n - 1); + int i1 = Math.min(j, k); + int i2 = Math.max(j, k); + + j = TestUtils.randValue(0, n - 1); + k = TestUtils.randValue(0, n - 1); + int i3 = Math.min(j, k); + int i4 = Math.max(j, k); + + // Range update. + // Yes, these values will likely cause overflow through excessive + // multiplication of segment values + long randValue = TestUtils.randValue(-100, +100); + bruteForceMulRangeUpdate(ar, i3, i4, randValue); + st.rangeUpdate1(i3, i4, randValue); + + // Range query + long bfSum = bruteForceSum(ar, i1, i2); + long segTreeSum = st.rangeQuery1(i1, i2); + assertThat(bfSum).isEqualTo(segTreeSum); + } + } + } + + // Finds the sum in an array between [l, r] in the `values` array + private static long bruteForceSum(long[] values, int l, int r) { + long s = 0; + for (int i = l; i <= r; i++) { + s += values[i]; + } + return s; + } + + // Finds the min value in an array between [l, r] in the `values` array + private static long bruteForceMin(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.min(m, values[i]); + } + return m; + } + + // Finds the max value in an array between [l, r] in the `values` array + private static long bruteForceMax(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.max(m, values[i]); + } + return m; + } + + private static void bruteForceSumRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] += x; + } + } + + private static void bruteForceMulRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] *= x; + } + } + + private static void bruteForceAssignRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] = x; + } + } +} diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQuerySumUpdateSegmentTreeTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQuerySumUpdateSegmentTreeTest.java new file mode 100644 index 000000000..f388d1750 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/segmenttree/SumQuerySumUpdateSegmentTreeTest.java @@ -0,0 +1,181 @@ +/** + * gradle test --info --tests + * "com.williamfiset.algorithms.datastructures.segmenttree.SumQuerySumUpdateSegmentTreeTest" + */ +package com.williamfiset.algorithms.datastructures.segmenttree; + +import static com.google.common.truth.Truth.assertThat; + +import com.williamfiset.algorithms.utils.TestUtils; +import org.junit.jupiter.api.*; + +public class SumQuerySumUpdateSegmentTreeTest { + + static int ITERATIONS = 100; + + @BeforeEach + public void setup() {} + + @Test + public void simpleTest() { + long[] ar = {2, 1, 3, 4, -1}; + SumQuerySumUpdateSegmentTree st = new SumQuerySumUpdateSegmentTree(ar); + + st.rangeUpdate1(0, 4, 1); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(14); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(11); + assertThat(st.rangeQuery1(2, 4)).isEqualTo(9); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(5); + + st.rangeUpdate1(3, 4, 4); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(3 + 2 + 4 + 9 + 4); + assertThat(st.rangeQuery1(0, 1)).isEqualTo(3 + 2); + assertThat(st.rangeQuery1(3, 4)).isEqualTo(9 + 4); + assertThat(st.rangeQuery1(1, 1)).isEqualTo(2); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(4); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(9); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(15); + assertThat(st.rangeQuery1(2, 3)).isEqualTo(13); + assertThat(st.rangeQuery1(1, 2)).isEqualTo(6); + + st.rangeUpdate1(1, 3, 3); + + assertThat(st.rangeQuery1(0, 4)).isEqualTo(3 + 5 + 7 + 12 + 4); + assertThat(st.rangeQuery1(0, 2)).isEqualTo(3 + 5 + 7); + assertThat(st.rangeQuery1(2, 4)).isEqualTo(7 + 12 + 4); + assertThat(st.rangeQuery1(1, 3)).isEqualTo(5 + 7 + 12); + assertThat(st.rangeQuery1(0, 0)).isEqualTo(3); + assertThat(st.rangeQuery1(1, 1)).isEqualTo(5); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(7); + assertThat(st.rangeQuery1(3, 3)).isEqualTo(12); + assertThat(st.rangeQuery1(4, 4)).isEqualTo(4); + } + + @Test + public void simpleTest2() { + long[] ar = {0, 0, 0, 0, 0}; + SumQuerySumUpdateSegmentTree st = new SumQuerySumUpdateSegmentTree(ar); + + st.rangeUpdate1(1, 2, 7); + assertThat(st.rangeQuery1(0, 1)).isEqualTo(7); + + st.rangeUpdate1(0, 1, -1); + assertThat(st.rangeQuery1(0, 1)).isEqualTo(5); + } + + @Test + public void simpleTest3() { + long[] ar = {0, 0, 0, 0, 0}; + SumQuerySumUpdateSegmentTree st = new SumQuerySumUpdateSegmentTree(ar); + + st.rangeUpdate1(2, 3, 6); + assertThat(st.rangeQuery1(0, 0)).isEqualTo(0); + + st.rangeUpdate1(0, 0, -5); + assertThat(st.rangeQuery1(2, 3)).isEqualTo(12); + + st.rangeUpdate1(1, 3, -4); + assertThat(st.rangeQuery1(1, 2)).isEqualTo(-2); + } + + @Test + public void simpleTest4() { + long[] ar = {0, 0, 0, 0, 0}; + SumQuerySumUpdateSegmentTree st = new SumQuerySumUpdateSegmentTree(ar); + + st.rangeUpdate1(0, 2, 2); + assertThat(st.rangeQuery1(0, 1)).isEqualTo(4); + + st.rangeUpdate1(0, 2, -3); + assertThat(st.rangeQuery1(0, 2)).isEqualTo(-3); + } + + @Test + public void simpleTest5() { + long[] ar = {0, 0, 0, 0, 0}; + SumQuerySumUpdateSegmentTree st = new SumQuerySumUpdateSegmentTree(ar); + + st.rangeUpdate1(1, 2, -8); + assertThat(st.rangeQuery1(2, 2)).isEqualTo(-8); + + st.rangeUpdate1(0, 3, -4); + assertThat(st.rangeQuery1(2, 3)).isEqualTo(-16); + } + + @Test + public void testRandomRangeSumUpdatesWithSumRangeQueries() { + for (int n = 5; n < ITERATIONS; n++) { + long[] ar = TestUtils.randomLongArray(n, -100, +100); + SumQuerySumUpdateSegmentTree st = new SumQuerySumUpdateSegmentTree(ar.clone()); + + for (int i = 0; i < n; i++) { + int j = TestUtils.randValue(0, n - 1); + int k = TestUtils.randValue(0, n - 1); + int i1 = Math.min(j, k); + int i2 = Math.max(j, k); + + j = TestUtils.randValue(0, n - 1); + k = TestUtils.randValue(0, n - 1); + int i3 = Math.min(j, k); + int i4 = Math.max(j, k); + + // Range update + long randValue = TestUtils.randValue(-10, 10); + bruteForceSumRangeUpdate(ar, i3, i4, randValue); + st.rangeUpdate1(i3, i4, randValue); + + // Range query + long bfSum = bruteForceSum(ar, i1, i2); + long segTreeSum = st.rangeQuery1(i1, i2); + assertThat(bfSum).isEqualTo(segTreeSum); + } + } + } + + // Finds the sum in an array between [l, r] in the `values` array + private static long bruteForceSum(long[] values, int l, int r) { + long s = 0; + for (int i = l; i <= r; i++) { + s += values[i]; + } + return s; + } + + // Finds the min value in an array between [l, r] in the `values` array + private static long bruteForceMin(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.min(m, values[i]); + } + return m; + } + + // Finds the max value in an array between [l, r] in the `values` array + private static long bruteForceMax(long[] values, int l, int r) { + long m = values[l]; + for (int i = l; i <= r; i++) { + m = Math.max(m, values[i]); + } + return m; + } + + private static void bruteForceSumRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] += x; + } + } + + private static void bruteForceMulRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] *= x; + } + } + + private static void bruteForceAssignRangeUpdate(long[] values, int l, int r, long x) { + for (int i = l; i <= r; i++) { + values[i] = x; + } + } +} diff --git a/javatests/com/williamfiset/algorithms/datastructures/set/HSetTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/set/HSetTest.java similarity index 75% rename from javatests/com/williamfiset/algorithms/datastructures/set/HSetTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/set/HSetTest.java index ce6b163b1..40031a0ba 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/set/HSetTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/set/HSetTest.java @@ -1,15 +1,13 @@ -package javatests.com.williamfiset.algorithms.datastructures.set; +package com.williamfiset.algorithms.datastructures.set; -import static java.lang.Math.*; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.datastructures.set.HSet; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Random; -import org.junit.*; +import org.junit.jupiter.api.*; // You can set the hash value of this object to be whatever you want // This makes it great for testing special cases. @@ -28,7 +26,9 @@ public int hashCode() { @Override public boolean equals(Object o) { - return data == ((ConstObj) o).data; + if (this == o) return true; + else if (o instanceof ConstObj) return data == ((ConstObj) o).data; + else return false; } } @@ -42,7 +42,7 @@ public class HSetTest { HSet hs; - @Before + @BeforeEach public void setup() { hs = new HSet<>(); } @@ -50,17 +50,17 @@ public void setup() { @Test public void testAddRemove() { hs.add(5); - assertEquals(hs.size(), 1); + assertThat(hs.size()).isEqualTo(1); hs.remove(5); - assertEquals(hs.size(), 0); + assertThat(hs.size()).isEqualTo(0); hs.add(0); - assertEquals(hs.size(), 1); + assertThat(hs.size()).isEqualTo(1); hs.remove(0); - assertEquals(hs.size(), 0); + assertThat(hs.size()).isEqualTo(0); hs.add(-5); - assertEquals(hs.size(), 1); + assertThat(hs.size()).isEqualTo(1); hs.remove(-5); - assertEquals(hs.size(), 0); + assertThat(hs.size()).isEqualTo(0); } @Test @@ -75,7 +75,7 @@ public void randomizedSetTest() { for (int i = 0; i < TEST_SZ; i++) { int num = nums.get(i); - // assertEquals( hs.add(num), s.add(num) ); + // assertThat(hs.add(num)).isEqualTo(s.add(num)); hs.add(num); s.add(num); @@ -83,7 +83,7 @@ public void randomizedSetTest() { for (Integer n : s) hs.contains(n); for (Integer n : hs) s.contains(n); - assertEquals(s.size(), hs.size()); + assertThat(s.size()).isEqualTo(hs.size()); } } } @@ -105,7 +105,7 @@ public void randomizedSetTest2() { int num = nums.get(i); ConstObj obj = new ConstObj(java.util.Objects.hash(num), num); - // assertEquals( hs.add(num), s.add(num) ); + // assertThat(hs.add(num)).isEqualTo(s.add(num)); hs.add(obj); s.add(obj); @@ -113,7 +113,7 @@ public void randomizedSetTest2() { for (ConstObj n : s) hs.contains(n); for (ConstObj n : hs) s.contains(n); - assertEquals(s.size(), hs.size()); + assertThat(s.size()).isEqualTo(hs.size()); } } } @@ -127,13 +127,13 @@ public void t() { HSet s = new HSet(); s.add(ch1); - assertEquals(1, s.size()); + assertThat(s.size()).isEqualTo(1); s.remove(ch1); - assertEquals(0, s.size()); + assertThat(s.size()).isEqualTo(0); s.add(ch2); - assertEquals(1, s.size()); + assertThat(s.size()).isEqualTo(1); s.remove(ch2); - assertEquals(0, s.size()); + assertThat(s.size()).isEqualTo(0); } // Generate a list of random numbers diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/skiplist/SkipListTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/skiplist/SkipListTest.java new file mode 100644 index 000000000..750148920 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/skiplist/SkipListTest.java @@ -0,0 +1,160 @@ +/** + * Tests the SkipList data structure implementation. + * + * @author Daniel Gustafsson + * @author Timmy Lindholm + * @author Anja Studic + * @author Christian Stjernberg + */ +package com.williamfiset.algorithms.datastructures.skiplist; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.*; + +public class SkipListTest { + + @Test + // Tests the getIndex method, supposed to return the index of an element + // in the list + public void testIndex() { + SkipList sl = new SkipList(4, 1, 45); + + sl.insert(10); + sl.insert(25); + assertEquals(2, sl.getIndex(25), "Index should be 2"); + + sl.insert(13); + sl.insert(14); + assertEquals(3, sl.getIndex(14), "Index should be 3"); + } + + @Test + // GetIndex shall return -1 if trying to get the index of a non existing object + public void testIndexWithNonExistingValue() { + SkipList sl = new SkipList(4, 1, 45); + + assertEquals(-1, sl.getIndex(44), "Index should be -1"); + } + + @Test + // Insert shall return false if trying to insert an object with a + // key that is greater than the initialized MAX value + public void testInsertGreaterThanMaxValue() { + SkipList sl = new SkipList(3, 1, 65); + + assertFalse(sl.insert(66), "Insert should return false"); + assertFalse(sl.insert(103), "Insert should return false"); + assertFalse(sl.insert(67), "Insert should return false"); + } + + @Test + // Insert shall return false if trying to insert an object with a + // key that is lesser than the initialized MIN value + public void testInsertLesserThanMinValue() { + SkipList sl = new SkipList(3, 10, 83); + + assertFalse(sl.insert(5), "Insert should return false"); + assertFalse(sl.insert(4), "Insert should return false"); + assertFalse(sl.insert(3), "Insert should return false"); + assertFalse(sl.insert(2), "Insert should return false"); + } + + @Test + // Tests the basic functionlity of the data structure + public void testSimpleFunctionality() { + SkipList sl = new SkipList(1, 0, 10); + sl.insert(5); + sl.insert(8); + + assertEquals(4, sl.size(), "Size should be 4"); + assertTrue(sl.find(5), "Object with key 5 should be found"); + assertTrue(sl.find(8), "Object with key 8 should be found"); + + sl.remove(5); + + assertEquals(3, sl.size(), "Size should be 3"); + assertFalse(sl.find(5), "Object with key 5 shouldn't be found"); + } + + @Test + // Tests the size method, should initialy be 2 becuase of min and max. + public void testSize() { + SkipList sl = new SkipList(3, 1, 10); + + assertEquals(2, sl.size(), "Size should be initialized to 2"); + + sl.insert(3); + sl.insert(4); + sl.insert(5); + + assertEquals(5, sl.size(), "Size should be 5"); + assertNotEquals(4, sl.size(), "Size shouldn't be 4"); + + sl.remove(3); + sl.remove(4); + + assertEquals(3, sl.size(), "Size should be 3"); + } + + @Test + // Tests the find method, find should return true when given an existing + // element key and return false when given a key of a non existing element + public void testFind() { + SkipList sl = new SkipList(4, 1, 45); + sl.insert(9); + sl.insert(18); + sl.insert(2); + sl.insert(6); + sl.insert(43); + sl.insert(36); + sl.insert(20); + sl.insert(30); + sl.insert(24); + + assertEquals(11, sl.size(), "Size should be 11"); + assertTrue(sl.find(43), "Object with key 43 should be found"); + assertFalse(sl.find(44), "Object with key 44 shouldn't be found"); + + sl.remove(43); + + assertFalse(sl.find(43), "Object with key 43 shouldn't be found"); + } + + @Test + // Insert shall return false if trying to insert an object with the + // same key as an already existing object + public void testDuplicate() { + SkipList sl = new SkipList(2, 2, 5); + sl.insert(4); + + assertFalse(sl.insert(4), "Duplicate insert should return false"); + assertEquals(3, sl.size(), "Duplicate should not exist, size should be 3"); + + sl.remove(4); + + assertFalse(sl.find(4), "Element exist after removal"); + } + + @Test + // Tests removing non-existing key + public void testRemoveNonExisting() { + SkipList sl = new SkipList(2, 2, 5); + + assertFalse(sl.remove(4), "Remove should return false when Object does not exist"); + + sl.insert(4); + + assertTrue(sl.remove(4), "Object should be removable"); + assertFalse(sl.remove(4), "Remove should return false when it has already been removed"); + } + + @Test + // Tests removing head and tail nodes, should return false + public void testRemoveHeadTail() { + SkipList sl = new SkipList(3, 1, 10); + + assertFalse(sl.remove(1), "Head shouldn't be removable"); + assertFalse(sl.remove(10), "Tail shouldn't be removable"); + } +} diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/sparsetable/SparseTableTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/sparsetable/SparseTableTest.java new file mode 100644 index 000000000..0c614b1b5 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/sparsetable/SparseTableTest.java @@ -0,0 +1,168 @@ +package com.williamfiset.algorithms.datastructures.sparsetable; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.*; +import org.junit.jupiter.api.*; + +public class SparseTableTest { + + private void queryResultTest( + long[] values, int l, int r, long actual, int index, SparseTable.Operation op) { + if (op == SparseTable.Operation.MIN) { + minQuery(values, l, r, actual, index); + } else if (op == SparseTable.Operation.MAX) { + maxQuery(values, l, r, actual, index); + } else if (op == SparseTable.Operation.SUM) { + sumQuery(values, l, r, actual); + } else if (op == SparseTable.Operation.MULT) { + multQuery(values, l, r, actual); + } else if (op == SparseTable.Operation.GCD) { + gcdQuery(values, l, r, actual); + } + } + + private void minQuery(long[] values, int l, int r, long actual, int index) { + long m = Long.MAX_VALUE; + for (int i = l; i <= r; i++) m = Math.min(m, values[i]); + assertThat(actual).isEqualTo(m); + assertThat(values[index]).isEqualTo(m); + } + + private void maxQuery(long[] values, int l, int r, long actual, int index) { + long m = Long.MIN_VALUE; + for (int i = l; i <= r; i++) m = Math.max(m, values[i]); + assertThat(actual).isEqualTo(m); + assertThat(values[index]).isEqualTo(m); + } + + private void sumQuery(long[] values, int l, int r, long actual) { + long m = 0; + for (int i = l; i <= r; i++) m += values[i]; + assertThat(m).isEqualTo(actual); + } + + private void multQuery(long[] values, int l, int r, long actual) { + long m = 1; + for (int i = l; i <= r; i++) m *= values[i]; + assertThat(m).isEqualTo(actual); + } + + // Computes the Greatest Common Divisor (GCD) of a & b + // This method ensures that the value returned is non negative + public static long gcd(long a, long b) { + return b == 0 ? (a < 0 ? -a : a) : gcd(b, a % b); + } + + private void gcdQuery(long[] values, int l, int r, long actual) { + long m = values[l]; + for (int i = l; i <= r; i++) m = gcd(m, values[i]); + assertThat(m).isEqualTo(actual); + } + + private void testAllOperations(long[] values) { + SparseTable min_st = new SparseTable(values, SparseTable.Operation.MIN); + SparseTable max_st = new SparseTable(values, SparseTable.Operation.MAX); + SparseTable sum_st = new SparseTable(values, SparseTable.Operation.SUM); + SparseTable mult_st = new SparseTable(values, SparseTable.Operation.MULT); + SparseTable gcd_st = new SparseTable(values, SparseTable.Operation.GCD); + + for (int i = 0; i < values.length; i++) { + for (int j = i; j < values.length; j++) { + queryResultTest( + values, i, j, min_st.query(i, j), min_st.queryIndex(i, j), SparseTable.Operation.MIN); + queryResultTest( + values, i, j, max_st.query(i, j), max_st.queryIndex(i, j), SparseTable.Operation.MAX); + queryResultTest( + values, i, j, sum_st.query(i, j), -1 /* unused */, SparseTable.Operation.SUM); + queryResultTest( + values, i, j, mult_st.query(i, j), -1 /* unused */, SparseTable.Operation.MULT); + queryResultTest( + values, i, j, gcd_st.query(i, j), -1 /* unused */, SparseTable.Operation.GCD); + } + } + } + + @Test + public void simple() { + long[] values = {1, 0, 1, -2, 3, -4, 5, -6, 7, -8, 9}; + testAllOperations(values); + } + + @Test + public void smallRangeRandomArrayTests() { + for (int i = 1; i < 100; i++) { + long[] values = genRandArray(i, -10, 10); + testAllOperations(values); + } + } + + @Test + public void randomArrayTests() { + for (int i = 1; i < 100; i++) { + long[] values = genRandArray(i, -100000, 100000); + testAllOperations(values); + } + } + + @Test + public void verifyIndexIsAlwaysLeftmostPositionWhenThereAreCollisions() { + long[] values = {5, 4, 3, 3, 3, 3, 3, 5, 6, 7}; + SparseTable st = new SparseTable(values, SparseTable.Operation.MIN); + + for (int i = 0; i < values.length; i++) { + for (int j = i; j < values.length; j++) { + long min = Long.MAX_VALUE; + int minIndex = 0; + for (int k = i; k <= j; k++) { + if (values[k] < min) { + min = values[k]; + minIndex = k; + } + } + + assertThat(st.query(i, j)).isEqualTo(min); + assertThat(i <= minIndex && minIndex <= j).isEqualTo(true); + assertThat(st.queryIndex(i, j)).isEqualTo(minIndex); + } + } + } + + @Test + public void verifyIndexIsAlwaysLeftmostPosition_randomized() { + for (int loop = 2; loop < 100; loop++) { + long[] values = genRandArray(loop, -100, +100); + SparseTable min_st = new SparseTable(values, SparseTable.Operation.MIN); + SparseTable max_st = new SparseTable(values, SparseTable.Operation.MAX); + + for (int i = 0; i < values.length; i++) { + for (int j = i; j < values.length; j++) { + long min = Long.MAX_VALUE, max = Long.MIN_VALUE; + int minIndex = 0, maxIndex = 0; + ; + for (int k = i; k <= j; k++) { + if (values[k] < min) { + min = values[k]; + minIndex = k; + } + if (values[k] > max) { + max = values[k]; + maxIndex = k; + } + } + assertThat(min_st.query(i, j)).isEqualTo(min); + assertThat(i <= minIndex && minIndex <= j).isEqualTo(true); + assertThat(min_st.queryIndex(i, j)).isEqualTo(minIndex); + + assertThat(max_st.query(i, j)).isEqualTo(max); + assertThat(i <= maxIndex && maxIndex <= j).isEqualTo(true); + assertThat(max_st.queryIndex(i, j)).isEqualTo(maxIndex); + } + } + } + } + + private static long[] genRandArray(int n, int lo, int hi) { + return new Random().longs(n, lo, hi).toArray(); + } +} diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/stack/StackTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/stack/StackTest.java new file mode 100644 index 000000000..a10cd14c9 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/stack/StackTest.java @@ -0,0 +1,81 @@ +package com.williamfiset.algorithms.datastructures.stack; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +public class StackTest { + + private static List> inputs() { + List> stacks = new ArrayList<>(); + stacks.add(new ListStack()); + stacks.add(new ArrayStack()); + stacks.add(new IntStack(2)); + return stacks; + } + + @ParameterizedTest + @MethodSource("inputs") + public void testEmptyStack(Stack stack) { + assertThat(stack.isEmpty()).isTrue(); + assertThat(stack.size()).isEqualTo(0); + } + + @ParameterizedTest + @MethodSource("inputs") + public void testPopOnEmpty(Stack stack) { + assertThrows(Exception.class, () -> stack.pop()); + } + + @ParameterizedTest + @MethodSource("inputs") + public void testPeekOnEmpty(Stack stack) { + assertThrows(Exception.class, () -> stack.peek()); + } + + @ParameterizedTest + @MethodSource("inputs") + public void testPush(Stack stack) { + stack.push(2); + assertThat(stack.size()).isEqualTo(1); + } + + @ParameterizedTest + @MethodSource("inputs") + public void testPeek(Stack stack) { + stack.push(2); + assertThat((int) (Integer) stack.peek()).isEqualTo(2); + assertThat(stack.size()).isEqualTo(1); + } + + @ParameterizedTest + @MethodSource("inputs") + public void testPop(Stack stack) { + stack.push(2); + assertThat((int) stack.pop()).isEqualTo(2); + assertThat(stack.size()).isEqualTo(0); + } + + @ParameterizedTest + @MethodSource("inputs") + public void testExhaustively(Stack stack) { + assertThat(stack.isEmpty()).isTrue(); + stack.push(1); + assertThat(stack.isEmpty()).isFalse(); + stack.push(2); + assertThat(stack.size()).isEqualTo(2); + assertThat((int) stack.peek()).isEqualTo(2); + assertThat(stack.size()).isEqualTo(2); + assertThat((int) stack.pop()).isEqualTo(2); + assertThat(stack.size()).isEqualTo(1); + assertThat((int) stack.peek()).isEqualTo(1); + assertThat(stack.size()).isEqualTo(1); + assertThat((int) stack.pop()).isEqualTo(1); + assertThat(stack.size()).isEqualTo(0); + assertThat(stack.isEmpty()).isTrue(); + } +} diff --git a/javatests/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayTest.java similarity index 78% rename from javatests/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayTest.java rename to src/test/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayTest.java index 91d8c3280..b8f7c0dc2 100644 --- a/javatests/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/suffixarray/SuffixArrayTest.java @@ -1,14 +1,10 @@ -package javatests.com.williamfiset.algorithms.datastructures.suffixarray; +package com.williamfiset.algorithms.datastructures.suffixarray; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.datastructures.suffixarray.SuffixArray; -import com.williamfiset.algorithms.datastructures.suffixarray.SuffixArrayFast; -import com.williamfiset.algorithms.datastructures.suffixarray.SuffixArrayMed; -import com.williamfiset.algorithms.datastructures.suffixarray.SuffixArraySlow; import java.security.SecureRandom; import java.util.Random; -import org.junit.*; +import org.junit.jupiter.api.*; public class SuffixArrayTest { @@ -22,7 +18,7 @@ public class SuffixArrayTest { String ASCII_LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - @Before + @BeforeEach public void setup() {} @Test @@ -33,9 +29,9 @@ public void suffixArrayLength() { SuffixArray sa2 = new SuffixArrayMed(str); SuffixArray sa3 = new SuffixArrayFast(str); - assertEquals(str.length(), sa1.getSa().length); - assertEquals(str.length(), sa2.getSa().length); - assertEquals(str.length(), sa3.getSa().length); + assertThat(sa1.getSa().length).isEqualTo(str.length()); + assertThat(sa2.getSa().length).isEqualTo(str.length()); + assertThat(sa3.getSa().length).isEqualTo(str.length()); } @Test @@ -49,7 +45,7 @@ public void lcsUniqueCharacters() { for (SuffixArray sa : suffixArrays) { for (int i = 0; i < sa.getSa().length; i++) { - assertEquals(0, sa.getLcpArray()[i]); + assertThat(sa.getLcpArray()[i]).isEqualTo(0); } } } @@ -67,7 +63,7 @@ public void increasingLCPTest() { for (SuffixArray sa : suffixArrays) { for (int i = 0; i < sa.getSa().length; i++) { - assertEquals(i, sa.getLcpArray()[i]); + assertThat(sa.getLcpArray()[i]).isEqualTo(i); } } } @@ -86,7 +82,7 @@ public void lcpTest1() { for (SuffixArray sa : suffixArrays) { for (int i = 0; i < sa.getSa().length; i++) { - assertEquals(lcpValues[i], sa.getLcpArray()[i]); + assertThat(lcpValues[i]).isEqualTo(sa.getLcpArray()[i]); } } } @@ -104,7 +100,7 @@ public void lcpTest2() { for (SuffixArray sa : suffixArrays) { for (int i = 0; i < sa.getSa().length; i++) { - assertEquals(lcpValues[i], sa.getLcpArray()[i]); + assertThat(lcpValues[i]).isEqualTo(sa.getLcpArray()[i]); } } } @@ -125,7 +121,7 @@ public void saConstruction() { SuffixArray s1 = suffixArrays[i]; SuffixArray s2 = suffixArrays[j]; for (int k = 0; k < s1.getSa().length; k++) { - assertEquals(s1.getSa()[k], s2.getSa()[k]); + assertThat(s1.getSa()[k]).isEqualTo(s2.getSa()[k]); } } } diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/trie/TrieTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/trie/TrieTest.java new file mode 100644 index 000000000..b44e4b00c --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/trie/TrieTest.java @@ -0,0 +1,429 @@ +package com.williamfiset.algorithms.datastructures.trie; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.*; + +public class TrieTest { + + // @BeforeEach public void setup() { } + + @Test + public void testBadTrieDelete1() { + assertThrows( + IllegalArgumentException.class, + () -> { + Trie t = new Trie(); + t.insert("some string"); + t.delete("some string", 0); + }); + } + + @Test + public void testBadTrieDelete2() { + assertThrows( + IllegalArgumentException.class, + () -> { + Trie t = new Trie(); + t.insert("some string"); + t.delete("some string", -1); + }); + } + + @Test + public void testBadTrieDelete3() { + assertThrows( + IllegalArgumentException.class, + () -> { + Trie t = new Trie(); + t.insert("some string"); + t.delete("some string", -345); + }); + } + + @Test + public void testBadTrieInsert() { + assertThrows( + IllegalArgumentException.class, + () -> { + (new Trie()).insert(null); + }); + } + + @Test + public void testBadTrieCount() { + assertThrows( + IllegalArgumentException.class, + () -> { + (new Trie()).count(null); + }); + } + + @Test + public void testBadTrieContains() { + assertThrows( + IllegalArgumentException.class, + () -> { + (new Trie()).contains(null); + }); + } + + @Test + public void testContains() { + + Trie t1 = new Trie(); + + // This implementation doesn't count the empty string as + // a valid string to be inserted into the trie (although it + // would be easy to account for) + t1.insert(""); + assertThat(t1.contains("")).isFalse(); + t1.insert(""); + assertThat(t1.contains("")).isFalse(); + t1.insert(""); + assertThat(t1.contains("")).isFalse(); + + Trie t2 = new Trie(); + t2.insert("aaaaa"); + t2.insert("aaaaa"); + t2.insert("aaaaa"); + t2.insert("aaaaa"); + t2.insert("aaaaa"); + assertThat(t2.contains("aaaaa")).isTrue(); + assertThat(t2.contains("aaaa")).isTrue(); + assertThat(t2.contains("aaa")).isTrue(); + assertThat(t2.contains("aa")).isTrue(); + assertThat(t2.contains("a")).isTrue(); + + Trie t3 = new Trie(); + + t3.insert("AE"); + t3.insert("AE"); + t3.insert("AH"); + t3.insert("AH"); + t3.insert("AH7"); + t3.insert("A7"); + t3.insert("7"); + t3.insert("7"); + t3.insert("B"); + t3.insert("B"); + t3.insert("B"); + t3.insert("B"); + + assertThat(t3.contains("A")).isTrue(); + assertThat(t3.contains("AH")).isTrue(); + assertThat(t3.contains("A7")).isTrue(); + assertThat(t3.contains("AE")).isTrue(); + assertThat(t3.contains("AH7")).isTrue(); + assertThat(t3.contains("7")).isTrue(); + assertThat(t3.contains("B")).isTrue(); + + assertThat(t3.contains("Ar")).isFalse(); + assertThat(t3.contains("A8")).isFalse(); + assertThat(t3.contains("AH6")).isFalse(); + assertThat(t3.contains("C")).isFalse(); + } + + @Test + public void testCount() { + + Trie t1 = new Trie(); + + // This implementation doesn't count the empty string as + // a valid string to be inserted into the trie (although it + // would be easy to account for) + t1.insert(""); + assertThat(t1.count("")).isEqualTo(0); + t1.insert(""); + assertThat(t1.count("")).isEqualTo(0); + t1.insert(""); + assertThat(t1.count("")).isEqualTo(0); + + Trie t2 = new Trie(); + t2.insert("aaaaa"); + t2.insert("aaaaa"); + t2.insert("aaaaa"); + t2.insert("aaaaa"); + t2.insert("aaaaa"); + assertThat(t2.count("aaaaa")).isEqualTo(5); + assertThat(t2.count("aaaa")).isEqualTo(5); + assertThat(t2.count("aaa")).isEqualTo(5); + assertThat(t2.count("aa")).isEqualTo(5); + assertThat(t2.count("a")).isEqualTo(5); + + Trie t3 = new Trie(); + + t3.insert("AE"); + t3.insert("AE"); + t3.insert("AH"); + t3.insert("AH"); + t3.insert("AH7"); + t3.insert("A7"); + + t3.insert("7"); + t3.insert("7"); + + t3.insert("B"); + t3.insert("B"); + t3.insert("B"); + t3.insert("B"); + + assertThat(t3.count("A")).isEqualTo(6); + assertThat(t3.count("AH")).isEqualTo(3); + assertThat(t3.count("A7")).isEqualTo(1); + assertThat(t3.count("AE")).isEqualTo(2); + assertThat(t3.count("AH7")).isEqualTo(1); + assertThat(t3.count("7")).isEqualTo(2); + assertThat(t3.count("B")).isEqualTo(4); + assertThat(t3.count("Ar")).isEqualTo(0); + assertThat(t3.count("A8")).isEqualTo(0); + assertThat(t3.count("AH6")).isEqualTo(0); + assertThat(t3.count("C")).isEqualTo(0); + } + + @Test + public void testInsert() { + + Trie t = new Trie(); + assertThat(t.insert("a")).isFalse(); + assertThat(t.insert("b")).isFalse(); + assertThat(t.insert("c")).isFalse(); + assertThat(t.insert("d")).isFalse(); + assertThat(t.insert("x")).isFalse(); + + assertThat(t.insert("ab")).isTrue(); + assertThat(t.insert("xkcd")).isTrue(); + assertThat(t.insert("dogs")).isTrue(); + assertThat(t.insert("bears")).isTrue(); + + assertThat(t.insert("mo")).isFalse(); + assertThat(t.insert("mooooose")).isTrue(); + + t.clear(); + + assertThat(t.insert("aaaa", 4)).isFalse(); + assertThat(t.count("aaaa")).isEqualTo(4); + + assertThat(t.insert("aaa", 3)).isTrue(); + assertThat(t.count("a")).isEqualTo(7); + assertThat(t.count("aa")).isEqualTo(7); + assertThat(t.count("aaa")).isEqualTo(7); + assertThat(t.count("aaaa")).isEqualTo(4); + assertThat(t.count("aaaaa")).isEqualTo(0); + + assertThat(t.insert("a", 5)).isTrue(); + assertThat(t.count("a")).isEqualTo(12); + assertThat(t.count("aa")).isEqualTo(7); + assertThat(t.count("aaa")).isEqualTo(7); + assertThat(t.count("aaaa")).isEqualTo(4); + assertThat(t.count("aaaaa")).isEqualTo(0); + } + + @Test + public void testClear() { + + Trie t = new Trie(); + + assertThat(t.insert("a")).isFalse(); + assertThat(t.insert("b")).isFalse(); + assertThat(t.insert("c")).isFalse(); + + assertThat(t.contains("a")).isTrue(); + assertThat(t.contains("b")).isTrue(); + assertThat(t.contains("c")).isTrue(); + + t.clear(); + + assertThat(t.contains("a")).isFalse(); + assertThat(t.contains("b")).isFalse(); + assertThat(t.contains("c")).isFalse(); + + t.insert("aaaa"); + t.insert("aaab"); + t.insert("aaab5"); + t.insert("aaac"); + t.insert("aaacb"); + + assertThat(t.contains("aaa")).isTrue(); + assertThat(t.contains("aaacb")).isTrue(); + assertThat(t.contains("aaab5")).isTrue(); + + t.clear(); + + assertThat(t.contains("aaaa")).isFalse(); + assertThat(t.contains("aaab")).isFalse(); + assertThat(t.contains("aaab5")).isFalse(); + assertThat(t.contains("aaac")).isFalse(); + assertThat(t.contains("aaacb")).isFalse(); + } + + @Test + public void testDelete() { + + Trie t = new Trie(); + t.insert("AAC"); + t.insert("AA"); + t.insert("A"); + + assertThat(t.delete("AAC")).isTrue(); + assertThat(t.contains("AAC")).isFalse(); + assertThat(t.contains("AA")).isTrue(); + assertThat(t.contains("A")).isTrue(); + + assertThat(t.delete("AA")).isTrue(); + assertThat(t.contains("AAC")).isFalse(); + assertThat(t.contains("AA")).isFalse(); + assertThat(t.contains("A")).isTrue(); + + assertThat(t.delete("A")).isTrue(); + assertThat(t.contains("AAC")).isFalse(); + assertThat(t.contains("AA")).isFalse(); + assertThat(t.contains("A")).isFalse(); + + t.clear(); + + t.insert("AAC"); + t.insert("AA"); + t.insert("A"); + + assertThat(t.delete("AA")).isTrue(); + assertThat(t.delete("AA")).isTrue(); + + assertThat(t.contains("AAC")).isFalse(); + assertThat(t.contains("AA")).isFalse(); + assertThat(t.contains("A")).isTrue(); + + t.clear(); + + t.insert("$A"); + t.insert("$B"); + t.insert("$C"); + + assertThat(t.delete("$", 3)).isTrue(); + + assertThat(t.delete("$")).isFalse(); + assertThat(t.contains("$")).isFalse(); + assertThat(t.contains("$A")).isFalse(); + assertThat(t.contains("$B")).isFalse(); + assertThat(t.contains("$C")).isFalse(); + assertThat(t.delete("$A")).isFalse(); + assertThat(t.delete("$B")).isFalse(); + assertThat(t.delete("$C")).isFalse(); + + t.clear(); + + t.insert("$A"); + t.insert("$B"); + t.insert("$C"); + + assertThat(t.delete("$", 2)).isTrue(); + assertThat(t.delete("$")).isTrue(); + + assertThat(t.contains("$")).isFalse(); + assertThat(t.contains("$A")).isFalse(); + assertThat(t.contains("$B")).isFalse(); + assertThat(t.contains("$C")).isFalse(); + assertThat(t.delete("$A")).isFalse(); + assertThat(t.delete("$B")).isFalse(); + assertThat(t.delete("$C")).isFalse(); + + t.clear(); + + t.insert("$A"); + t.insert("$B"); + t.insert("$C"); + + assertThat(t.delete("$", 2)).isTrue(); + + assertThat(t.contains("$")).isTrue(); + assertThat(t.contains("$A")).isTrue(); + assertThat(t.contains("$B")).isTrue(); + assertThat(t.contains("$C")).isTrue(); + assertThat(t.delete("$A")).isTrue(); + assertThat(t.delete("$B")).isFalse(); + assertThat(t.delete("$C")).isFalse(); + + t.clear(); + + t.insert("CAT", 3); + t.insert("DOG", 3); + + assertThat(t.delete("parrot", 50)).isFalse(); + + t.clear(); + + t.insert("1234"); + t.insert("122", 2); + t.insert("123", 3); + + assertThat(t.delete("12", 6)).isTrue(); + assertThat(t.delete("12")).isFalse(); + assertThat(t.delete("1")).isFalse(); + assertThat(t.contains("1234")).isFalse(); + assertThat(t.contains("123")).isFalse(); + assertThat(t.contains("12")).isFalse(); + assertThat(t.contains("1")).isFalse(); + + t.clear(); + + t.insert("1234"); + t.insert("122", 2); + t.insert("123", 3); + + t.delete("12", 999999); + + assertThat(t.contains("1234")).isFalse(); + assertThat(t.contains("123")).isFalse(); + assertThat(t.contains("12")).isFalse(); + assertThat(t.contains("1")).isFalse(); + + t.clear(); + + t.insert("1234"); + t.insert("122", 2); + t.insert("123", 3); + + t.delete("12", 999999); + + assertThat(t.contains("1234")).isFalse(); + assertThat(t.contains("123")).isFalse(); + assertThat(t.contains("12")).isFalse(); + assertThat(t.contains("1")).isFalse(); + + t.clear(); + + t.insert("1234"); + t.insert("122", 2); + t.insert("123", 3); + + assertThat(t.delete("1234")).isTrue(); + assertThat(t.delete("123", 4)).isTrue(); + assertThat(t.delete("122", 2)).isTrue(); + + assertThat(t.contains("1")).isFalse(); + assertThat(t.contains("12")).isFalse(); + assertThat(t.contains("122")).isFalse(); + assertThat(t.contains("123")).isFalse(); + assertThat(t.contains("1234")).isFalse(); + } + + @Test + public void testEdgeCases() { + + Trie t = new Trie(); + assertThat(t.count("")).isEqualTo(0); + assertThat(t.count("\0")).isEqualTo(0); + assertThat(t.count("\0\0")).isEqualTo(0); + assertThat(t.count("\0\0\0")).isEqualTo(0); + + for (char c = 0; c < 128; c++) assertThat(t.count("" + c)).isEqualTo(0); + + assertThat(t.contains("")).isFalse(); + assertThat(t.contains("\0")).isFalse(); + assertThat(t.contains("\0\0")).isFalse(); + assertThat(t.contains("\0\0\0")).isFalse(); + + for (char c = 0; c < 128; c++) assertThat(t.contains("" + c)).isFalse(); + } +} diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/unionfind/UnionFindTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/unionfind/UnionFindTest.java new file mode 100644 index 000000000..bf1bfca95 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/datastructures/unionfind/UnionFindTest.java @@ -0,0 +1,229 @@ +package com.williamfiset.algorithms.datastructures.unionfind; + +// import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class UnionFindTest { + + @Test + public void testNumComponents() { + + UnionFind uf = new UnionFind(5); + assertThat(uf.components()).isEqualTo(5); + + uf.unify(0, 1); + assertThat(uf.components()).isEqualTo(4); + + uf.unify(1, 0); + assertThat(uf.components()).isEqualTo(4); + + uf.unify(1, 2); + assertThat(uf.components()).isEqualTo(3); + + uf.unify(0, 2); + assertThat(uf.components()).isEqualTo(3); + + uf.unify(2, 1); + assertThat(uf.components()).isEqualTo(3); + + uf.unify(3, 4); + assertThat(uf.components()).isEqualTo(2); + + uf.unify(4, 3); + assertThat(uf.components()).isEqualTo(2); + + uf.unify(1, 3); + assertThat(uf.components()).isEqualTo(1); + + uf.unify(4, 0); + assertThat(uf.components()).isEqualTo(1); + } + + @Test + public void testComponentSize() { + + UnionFind uf = new UnionFind(5); + assertThat(uf.componentSize(0)).isEqualTo(1); + assertThat(uf.componentSize(1)).isEqualTo(1); + assertThat(uf.componentSize(2)).isEqualTo(1); + assertThat(uf.componentSize(3)).isEqualTo(1); + assertThat(uf.componentSize(4)).isEqualTo(1); + + uf.unify(0, 1); + assertThat(uf.componentSize(0)).isEqualTo(2); + assertThat(uf.componentSize(1)).isEqualTo(2); + assertThat(uf.componentSize(2)).isEqualTo(1); + assertThat(uf.componentSize(3)).isEqualTo(1); + assertThat(uf.componentSize(4)).isEqualTo(1); + + uf.unify(1, 0); + assertThat(uf.componentSize(0)).isEqualTo(2); + assertThat(uf.componentSize(1)).isEqualTo(2); + assertThat(uf.componentSize(2)).isEqualTo(1); + assertThat(uf.componentSize(3)).isEqualTo(1); + assertThat(uf.componentSize(4)).isEqualTo(1); + + uf.unify(1, 2); + assertThat(uf.componentSize(0)).isEqualTo(3); + assertThat(uf.componentSize(1)).isEqualTo(3); + assertThat(uf.componentSize(2)).isEqualTo(3); + assertThat(uf.componentSize(3)).isEqualTo(1); + assertThat(uf.componentSize(4)).isEqualTo(1); + + uf.unify(0, 2); + assertThat(uf.componentSize(0)).isEqualTo(3); + assertThat(uf.componentSize(1)).isEqualTo(3); + assertThat(uf.componentSize(2)).isEqualTo(3); + assertThat(uf.componentSize(3)).isEqualTo(1); + assertThat(uf.componentSize(4)).isEqualTo(1); + + uf.unify(2, 1); + assertThat(uf.componentSize(0)).isEqualTo(3); + assertThat(uf.componentSize(1)).isEqualTo(3); + assertThat(uf.componentSize(2)).isEqualTo(3); + assertThat(uf.componentSize(3)).isEqualTo(1); + assertThat(uf.componentSize(4)).isEqualTo(1); + + uf.unify(3, 4); + assertThat(uf.componentSize(0)).isEqualTo(3); + assertThat(uf.componentSize(1)).isEqualTo(3); + assertThat(uf.componentSize(2)).isEqualTo(3); + assertThat(uf.componentSize(3)).isEqualTo(2); + assertThat(uf.componentSize(4)).isEqualTo(2); + + uf.unify(4, 3); + assertThat(uf.componentSize(0)).isEqualTo(3); + assertThat(uf.componentSize(1)).isEqualTo(3); + assertThat(uf.componentSize(2)).isEqualTo(3); + assertThat(uf.componentSize(3)).isEqualTo(2); + assertThat(uf.componentSize(4)).isEqualTo(2); + + uf.unify(1, 3); + assertThat(uf.componentSize(0)).isEqualTo(5); + assertThat(uf.componentSize(1)).isEqualTo(5); + assertThat(uf.componentSize(2)).isEqualTo(5); + assertThat(uf.componentSize(3)).isEqualTo(5); + assertThat(uf.componentSize(4)).isEqualTo(5); + + uf.unify(4, 0); + assertThat(uf.componentSize(0)).isEqualTo(5); + assertThat(uf.componentSize(1)).isEqualTo(5); + assertThat(uf.componentSize(2)).isEqualTo(5); + assertThat(uf.componentSize(3)).isEqualTo(5); + assertThat(uf.componentSize(4)).isEqualTo(5); + } + + @Test + public void testConnectivity() { + + int sz = 7; + UnionFind uf = new UnionFind(sz); + + for (int i = 0; i < sz; i++) assertThat(uf.connected(i, i)).isTrue(); + + uf.unify(0, 2); + + assertThat(uf.connected(0, 2)).isTrue(); + assertThat(uf.connected(2, 0)).isTrue(); + + assertThat(uf.connected(0, 1)).isFalse(); + assertThat(uf.connected(3, 1)).isFalse(); + assertThat(uf.connected(6, 4)).isFalse(); + assertThat(uf.connected(5, 0)).isFalse(); + + for (int i = 0; i < sz; i++) assertThat(uf.connected(i, i)).isTrue(); + + uf.unify(3, 1); + + assertThat(uf.connected(0, 2)).isTrue(); + assertThat(uf.connected(2, 0)).isTrue(); + assertThat(uf.connected(1, 3)).isTrue(); + assertThat(uf.connected(3, 1)).isTrue(); + + assertThat(uf.connected(0, 1)).isFalse(); + assertThat(uf.connected(1, 2)).isFalse(); + assertThat(uf.connected(2, 3)).isFalse(); + assertThat(uf.connected(1, 0)).isFalse(); + assertThat(uf.connected(2, 1)).isFalse(); + assertThat(uf.connected(3, 2)).isFalse(); + + assertThat(uf.connected(1, 4)).isFalse(); + assertThat(uf.connected(2, 5)).isFalse(); + assertThat(uf.connected(3, 6)).isFalse(); + + for (int i = 0; i < sz; i++) assertThat(uf.connected(i, i)).isTrue(); + + uf.unify(2, 5); + assertThat(uf.connected(0, 2)).isTrue(); + assertThat(uf.connected(2, 0)).isTrue(); + assertThat(uf.connected(1, 3)).isTrue(); + assertThat(uf.connected(3, 1)).isTrue(); + assertThat(uf.connected(0, 5)).isTrue(); + assertThat(uf.connected(5, 0)).isTrue(); + assertThat(uf.connected(5, 2)).isTrue(); + assertThat(uf.connected(2, 5)).isTrue(); + + assertThat(uf.connected(0, 1)).isFalse(); + assertThat(uf.connected(1, 2)).isFalse(); + assertThat(uf.connected(2, 3)).isFalse(); + assertThat(uf.connected(1, 0)).isFalse(); + assertThat(uf.connected(2, 1)).isFalse(); + assertThat(uf.connected(3, 2)).isFalse(); + + assertThat(uf.connected(4, 6)).isFalse(); + assertThat(uf.connected(4, 5)).isFalse(); + assertThat(uf.connected(1, 6)).isFalse(); + + for (int i = 0; i < sz; i++) assertThat(uf.connected(i, i)).isTrue(); + + // Connect everything + uf.unify(1, 2); + uf.unify(3, 4); + uf.unify(4, 6); + + for (int i = 0; i < sz; i++) { + for (int j = 0; j < sz; j++) { + assertThat(uf.connected(i, j)).isTrue(); + } + } + } + + @Test + public void testSize() { + + UnionFind uf = new UnionFind(5); + assertThat(uf.size()).isEqualTo(5); + uf.unify(0, 1); + uf.find(3); + assertThat(uf.size()).isEqualTo(5); + uf.unify(1, 2); + assertThat(uf.size()).isEqualTo(5); + uf.unify(0, 2); + uf.find(1); + assertThat(uf.size()).isEqualTo(5); + uf.unify(2, 1); + assertThat(uf.size()).isEqualTo(5); + uf.unify(3, 4); + uf.find(0); + assertThat(uf.size()).isEqualTo(5); + uf.unify(4, 3); + uf.find(3); + assertThat(uf.size()).isEqualTo(5); + uf.unify(1, 3); + assertThat(uf.size()).isEqualTo(5); + uf.find(2); + uf.unify(4, 0); + assertThat(uf.size()).isEqualTo(5); + } + + @ParameterizedTest(name = "Size: {0}") + @ValueSource(ints = {-1, -3463, 0}) + public void testBadUnionFindCreation(int size) { + assertThrows(IllegalArgumentException.class, () -> new UnionFind(size)); + } +} diff --git a/src/test/java/com/williamfiset/algorithms/dp/CoinChangeTest.java b/src/test/java/com/williamfiset/algorithms/dp/CoinChangeTest.java new file mode 100644 index 000000000..028352bf0 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/dp/CoinChangeTest.java @@ -0,0 +1,82 @@ +package com.williamfiset.algorithms.dp; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.primitives.Ints; +import com.williamfiset.algorithms.utils.TestUtils; +import java.util.*; +import org.junit.jupiter.api.*; + +public class CoinChangeTest { + + static final int LOOPS = 100; + + @Test + public void testCoinChange() { + for (int i = 1; i < LOOPS; i++) { + List values = TestUtils.randomIntegerList(i, 1, 1000); + int[] coinValues = Ints.toArray(values); + + int amount = TestUtils.randValue(1, 1000); + + CoinChange.Solution solution1 = CoinChange.coinChange(coinValues, amount); + CoinChange.Solution solution2 = CoinChange.coinChangeSpaceEfficient(coinValues, amount); + int v1 = solution1.minCoins.isPresent() ? solution1.minCoins.get() : -1; + int v2 = solution2.minCoins.isPresent() ? solution2.minCoins.get() : -1; + int v3 = CoinChange.coinChangeRecursive(coinValues, amount); + + assertThat(v1).isEqualTo(v2); + assertThat(v2).isEqualTo(v3); + } + } + + @Test + public void testCoinChangeSelectedCoins() { + for (int i = 1; i < LOOPS; i++) { + List values = TestUtils.randomIntegerList(i, 1, 1000); + int[] coinValues = Ints.toArray(values); + + int amount = TestUtils.randValue(1, 1000); + + CoinChange.Solution solution = CoinChange.coinChange(coinValues, amount); + int selectedCoinsSum = 0; + for (int v : solution.selectedCoins) { + selectedCoinsSum += v; + } + if (!solution.minCoins.isPresent()) { + assertThat(solution.selectedCoins.size()).isEqualTo(0); + } else { + // Verify that the size of the selected coins is equal to the optimal solution. + assertThat(solution.selectedCoins.size()).isEqualTo(solution.minCoins.get()); + + // Further verify that the sum of the selected coins equals the amount we want to make. + assertThat(selectedCoinsSum).isEqualTo(amount); + } + } + } + + @Test + public void testCoinChangeSpaceEfficientSelectedCoins() { + for (int i = 1; i < LOOPS; i++) { + List values = TestUtils.randomIntegerList(i, 1, 1000); + int[] coinValues = Ints.toArray(values); + + int amount = TestUtils.randValue(1, 1000); + + CoinChange.Solution solution = CoinChange.coinChangeSpaceEfficient(coinValues, amount); + int selectedCoinsSum = 0; + for (int v : solution.selectedCoins) { + selectedCoinsSum += v; + } + if (!solution.minCoins.isPresent()) { + assertThat(solution.selectedCoins.size()).isEqualTo(0); + } else { + // Verify that the size of the selected coins is equal to the optimal solution. + assertThat(solution.selectedCoins.size()).isEqualTo(solution.minCoins.get()); + + // Further verify that the sum of the selected coins equals the amount we want to make. + assertThat(selectedCoinsSum).isEqualTo(amount); + } + } + } +} diff --git a/src/test/java/com/williamfiset/algorithms/dp/WeightedMaximumCardinalityMatchingTest.java b/src/test/java/com/williamfiset/algorithms/dp/WeightedMaximumCardinalityMatchingTest.java new file mode 100644 index 000000000..b378b3802 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/dp/WeightedMaximumCardinalityMatchingTest.java @@ -0,0 +1,450 @@ +package com.williamfiset.algorithms.dp; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.*; +import org.junit.jupiter.api.*; + +public class WeightedMaximumCardinalityMatchingTest { + + static final int LOOPS = 50; + static final double INF = 987654321.0; + + static class BruteForceMwpm { + private int n; + private Double[][] matrix; + private double minWeightMatching = Double.POSITIVE_INFINITY; + + public BruteForceMwpm(Double[][] matrix) { + this.matrix = matrix; + this.n = matrix.length; + } + + public double getMinWeightCost() { + solve(); + return minWeightMatching; + } + + public double computeMatchingCost(int[] p) { + double t = 0; + for (int i = 0; i < n / 2; i++) { + int ii = p[2 * i]; + int jj = p[2 * i + 1]; + t += matrix[ii][jj]; + } + return t; + } + + public void solve() { + int[] permutation = new int[n]; + for (int i = 0; i < n; i++) permutation[i] = i; + + // Try all matchings + do { + double matchingCost = computeMatchingCost(permutation); + if (matchingCost < minWeightMatching) { + minWeightMatching = matchingCost; + } + } while (nextPermutation(permutation)); + } + + // Generates the next ordered permutation in-place (skips repeated permutations). + // Calling this when the array is already at the highest permutation returns false. + // Recommended usage is to start with the smallest permutations and use a do while + // loop to generate each successive permutations (see main for example). + public static boolean nextPermutation(int[] sequence) { + int first = getFirst(sequence); + if (first == -1) return false; + int toSwap = sequence.length - 1; + while (sequence[first] >= sequence[toSwap]) --toSwap; + swap(sequence, first++, toSwap); + toSwap = sequence.length - 1; + while (first < toSwap) swap(sequence, first++, toSwap--); + return true; + } + + private static int getFirst(int[] sequence) { + for (int i = sequence.length - 2; i >= 0; --i) if (sequence[i] < sequence[i + 1]) return i; + return -1; + } + + private static void swap(int[] sequence, int i, int j) { + int tmp = sequence[i]; + sequence[i] = sequence[j]; + sequence[j] = tmp; + } + } + + private static MwpmInterface[] getImplementations(Double[][] costMatrix) { + return new MwpmInterface[] {new WeightedMaximumCardinalityMatchingRecursive(costMatrix) + // new WeightedMaximumCardinalityMatchingIterative(costMatrix) + }; + } + + private static Double[][] createEmptyMatrix(int n) { + Double[][] costMatrix = new Double[n][n]; + for (int i = 0; i < n; ++i) { + for (int j = 0; j < n; ++j) { + if (i == j) continue; + costMatrix[i][j] = null; + } + } + return costMatrix; + } + + private static void addUndirectedWeightedEdge(Double[][] g, int from, int to, double weight) { + g[from][to] = weight; + g[to][from] = weight; + } + + @Test + public void testSmallGraph_oddSize() { + int n = 5; + Double[][] g = createEmptyMatrix(n); + // 0, 2; 3, 4 + addUndirectedWeightedEdge(g, 0, 1, 8); + addUndirectedWeightedEdge(g, 0, 2, 1); + addUndirectedWeightedEdge(g, 1, 3, 8); + addUndirectedWeightedEdge(g, 2, 3, 8); + addUndirectedWeightedEdge(g, 2, 4, 8); + addUndirectedWeightedEdge(g, 3, 4, 2); + + MwpmInterface mwpm = new WeightedMaximumCardinalityMatchingRecursive(g); + double cost = mwpm.getMinWeightCost(); + assertThat(cost).isEqualTo(3.0); + + int[] matching = mwpm.getMatching(); + int[] expectedMatching = {0, 2, 3, 4}; + assertThat(matching).isEqualTo(expectedMatching); + } + + @Test + public void testSmallestMatrix1() { + // nodes 0 & 1 make the mwpm + Double[][] costMatrix = { + {0.0, 1.0}, + {1.0, 0.0}, + }; + MwpmInterface[] impls = getImplementations(costMatrix); + for (MwpmInterface mwpm : impls) { + double cost = mwpm.getMinWeightCost(); + assertThat(cost).isEqualTo(1.0); + + int[] matching = mwpm.getMatching(); + int[] expectedMatching = {0, 1}; + assertThat(matching).isEqualTo(expectedMatching); + } + } + + @Test + public void testSmallMatrix1() { + // nodes 0 & 2 and 1 & 3 make the mwpm + Double[][] costMatrix = { + {0.0, 2.0, 1.0, 2.0}, + {2.0, 0.0, 2.0, 1.0}, + {1.0, 2.0, 0.0, 2.0}, + {2.0, 1.0, 2.0, 0.0}, + }; + + MwpmInterface[] impls = getImplementations(costMatrix); + for (MwpmInterface mwpm : impls) { + double cost = mwpm.getMinWeightCost(); + assertThat(cost).isEqualTo(2.0); + + int[] matching = mwpm.getMatching(); + int[] expectedMatching = {0, 2, 1, 3}; + assertThat(matching).isEqualTo(expectedMatching); + } + } + + @Test + public void testSmallMatrix2() { + // nodes 0 & 1 and 2 & 3 make the mwpm + Double[][] costMatrix = { + {0.0, 1.0, 2.0, 2.0}, + {1.0, 0.0, 2.0, 2.0}, + {2.0, 2.0, 0.0, 1.0}, + {2.0, 2.0, 1.0, 0.0}, + }; + + MwpmInterface[] impls = getImplementations(costMatrix); + for (MwpmInterface mwpm : impls) { + double cost = mwpm.getMinWeightCost(); + assertThat(cost).isEqualTo(2.0); + + int[] matching = mwpm.getMatching(); + int[] expectedMatching = {0, 1, 2, 3}; + assertThat(matching).isEqualTo(expectedMatching); + } + } + + @Test + public void testMediumMatrix1() { + // mwpm between 0 & 5, 1 & 2, 3 & 4 + Double[][] costMatrix = { + {0.0, 9.0, 9.0, 9.0, 9.0, 1.0}, + {9.0, 0.0, 1.0, 9.0, 9.0, 9.0}, + {9.0, 1.0, 0.0, 9.0, 9.0, 9.0}, + {9.0, 9.0, 9.0, 0.0, 1.0, 9.0}, + {9.0, 9.0, 9.0, 1.0, 0.0, 9.0}, + {1.0, 9.0, 9.0, 9.0, 9.0, 0.0}, + }; + + MwpmInterface[] impls = getImplementations(costMatrix); + for (MwpmInterface mwpm : impls) { + double cost = mwpm.getMinWeightCost(); + assertThat(cost).isEqualTo(3.0); + + int[] matching = mwpm.getMatching(); + int[] expectedMatching = {0, 5, 1, 2, 3, 4}; + assertThat(matching).isEqualTo(expectedMatching); + } + } + + @Test + public void testMediumMatrix2() { + // mwpm between 0 & 1, 2 & 4, 3 & 5 + Double[][] costMatrix = { + {0.0, 1.0, 9.0, 9.0, 9.0, 9.0}, + {1.0, 0.0, 9.0, 9.0, 9.0, 9.0}, + {9.0, 9.0, 0.0, 9.0, 1.0, 9.0}, + {9.0, 9.0, 9.0, 0.0, 9.0, 1.0}, + {9.0, 9.0, 1.0, 9.0, 0.0, 9.0}, + {9.0, 9.0, 9.0, 1.0, 9.0, 0.0}, + }; + + MwpmInterface[] impls = getImplementations(costMatrix); + for (MwpmInterface mwpm : impls) { + double cost = mwpm.getMinWeightCost(); + assertThat(cost).isEqualTo(3.0); + + int[] matching = mwpm.getMatching(); + int[] expectedMatching = {0, 1, 2, 4, 3, 5}; + assertThat(matching).isEqualTo(expectedMatching); + } + } + + @Test + public void testMediumGraph_evenSize_fromSlides() { + int n = 6; + Double[][] g = createEmptyMatrix(n); + + addUndirectedWeightedEdge(g, 0, 1, 7); + addUndirectedWeightedEdge(g, 0, 2, 6); + addUndirectedWeightedEdge(g, 0, 4, -1); + addUndirectedWeightedEdge(g, 1, 3, 1); + addUndirectedWeightedEdge(g, 1, 4, 3); + addUndirectedWeightedEdge(g, 1, 5, 5); + addUndirectedWeightedEdge(g, 2, 4, 5); + addUndirectedWeightedEdge(g, 3, 5, 3); + addUndirectedWeightedEdge(g, 4, 5, 8); + + MwpmInterface mwpm = new WeightedMaximumCardinalityMatchingRecursive(g); + double cost = mwpm.getMinWeightCost(); + assertThat(cost).isEqualTo(12); + + int[] matching = mwpm.getMatching(); + + int[] expectedMatching = {0, 2, 1, 4, 3, 5}; + assertThat(matching).isEqualTo(expectedMatching); + } + + @Test + public void testMediumGraph_evenSize_nonPerfectMatchingFromSlides() { + int n = 6; + Double[][] g = createEmptyMatrix(n); + + addUndirectedWeightedEdge(g, 0, 1, 6); + addUndirectedWeightedEdge(g, 1, 2, 7); + addUndirectedWeightedEdge(g, 1, 5, 8); + addUndirectedWeightedEdge(g, 1, 4, 9); + addUndirectedWeightedEdge(g, 1, 3, 10); + addUndirectedWeightedEdge(g, 3, 4, 11); + + MwpmInterface mwpm = new WeightedMaximumCardinalityMatchingRecursive(g); + double cost = mwpm.getMinWeightCost(); + assertThat(cost).isEqualTo(17); + + int[] matching = mwpm.getMatching(); + + int[] expectedMatching = {0, 1, 3, 4}; + assertThat(matching).isEqualTo(expectedMatching); + } + + @Test + public void testNegativeEdgeWeights() { + int n = 6; + Double[][] g = createEmptyMatrix(n); + addUndirectedWeightedEdge(g, 0, 1, -1); // selected + addUndirectedWeightedEdge(g, 1, 2, -2); + addUndirectedWeightedEdge(g, 2, 3, -3); // selected + addUndirectedWeightedEdge(g, 3, 4, -4); + addUndirectedWeightedEdge(g, 4, 5, -5); // selected + + MwpmInterface mwpm = new WeightedMaximumCardinalityMatchingRecursive(g); + double cost = mwpm.getMinWeightCost(); + assertThat(cost).isEqualTo(-1 + -3 + -5); + + int[] matching = mwpm.getMatching(); + int[] expectedMatching = {0, 1, 2, 3, 4, 5}; + assertThat(matching).isEqualTo(expectedMatching); + } + + @Test + public void testNegativeEdge_smallerThanINFWeights() { + int n = 6; + Double[][] g = createEmptyMatrix(n); + addUndirectedWeightedEdge(g, 0, 1, -1 - 50 * INF); // selected + addUndirectedWeightedEdge(g, 1, 2, -2 - 50 * INF); + addUndirectedWeightedEdge(g, 2, 3, -3 - 50 * INF); // selected + addUndirectedWeightedEdge(g, 3, 4, -4 - 50 * INF); + addUndirectedWeightedEdge(g, 4, 5, -5 - 50 * INF); // selected + + MwpmInterface mwpm = new WeightedMaximumCardinalityMatchingRecursive(g); + double cost = mwpm.getMinWeightCost(); + double expectedCost = -1.0 + -3.0 + -5.0 + (-3 * 50 * INF); + assertThat(cost).isEqualTo(expectedCost); + + int[] matching = mwpm.getMatching(); + int[] expectedMatching = {0, 1, 2, 3, 4, 5}; + assertThat(matching).isEqualTo(expectedMatching); + assertOptimalMatching(matching, g, expectedCost); + } + + @Test + public void testDisjointGraph() { + int n = 8; + Double[][] g = createEmptyMatrix(n); + addUndirectedWeightedEdge(g, 0, 1, 3); + addUndirectedWeightedEdge(g, 0, 2, 5); + addUndirectedWeightedEdge(g, 1, 2, 1); + addUndirectedWeightedEdge(g, 1, 3, 4); + addUndirectedWeightedEdge(g, 2, 3, 2); + + addUndirectedWeightedEdge(g, 4, 5, 3); + addUndirectedWeightedEdge(g, 4, 6, 5); + addUndirectedWeightedEdge(g, 5, 6, 1); + addUndirectedWeightedEdge(g, 5, 7, 4); + addUndirectedWeightedEdge(g, 6, 7, 2); + + MwpmInterface mwpm = new WeightedMaximumCardinalityMatchingRecursive(g); + double cost = mwpm.getMinWeightCost(); + assertThat(cost).isEqualTo(10); + + int[] matching = mwpm.getMatching(); + assertOptimalMatching(matching, g, 10); + } + + @Test + public void testHarderWmcm_fromSlides() { + int n = 11; + Double[][] g = createEmptyMatrix(n); + addUndirectedWeightedEdge(g, 0, 1, 1); + addUndirectedWeightedEdge(g, 0, 3, 8); + addUndirectedWeightedEdge(g, 0, 4, 9); + addUndirectedWeightedEdge(g, 0, 5, 7); // selected + addUndirectedWeightedEdge(g, 1, 2, 1); + addUndirectedWeightedEdge(g, 1, 6, 7); // selected + addUndirectedWeightedEdge(g, 1, 7, 8); + addUndirectedWeightedEdge(g, 1, 8, 9); + addUndirectedWeightedEdge(g, 2, 9, 9); + addUndirectedWeightedEdge(g, 2, 10, 7); // selected + + MwpmInterface mwpm = new WeightedMaximumCardinalityMatchingRecursive(g); + double cost = mwpm.getMinWeightCost(); + assertThat(cost).isEqualTo(7 + 7 + 7); + + int[] matching = mwpm.getMatching(); + int[] expectedMatching = {0, 5, 1, 6, 2, 10}; + assertThat(matching).isEqualTo(expectedMatching); + } + + @Test + public void testMatchingOutputsUniqueNodes() { + for (int loop = 0; loop < LOOPS; loop++) { + int n = Math.max(1, (int) (Math.random() * 11)) * 2; // n is either 2,4,6,8,10,12,14,16,18,20 + Double[][] costMatrix = new Double[n][n]; + randomFillSymmetricMatrix(costMatrix, 100); + + MwpmInterface[] impls = getImplementations(costMatrix); + for (MwpmInterface mwpm : impls) { + int[] matching = mwpm.getMatching(); + Set set = new HashSet<>(); + for (int i = 0; i < matching.length; i++) { + set.add(matching[i]); + } + + assertThat(set.size()).isEqualTo(matching.length); + } + } + } + + @Test + public void testMatchingAndCostAreConsistent() { + for (int loop = 0; loop < LOOPS; loop++) { + int n = Math.max(1, (int) (Math.random() * 11)) * 2; // n is either 2,4,6,8,10,12,14,16,18,20 + Double[][] costMatrix = new Double[n][n]; + randomFillSymmetricMatrix(costMatrix, 100); + + MwpmInterface[] impls = getImplementations(costMatrix); + for (MwpmInterface mwpm : impls) { + assertOptimalMatching(mwpm.getMatching(), costMatrix, mwpm.getMinWeightCost()); + } + } + } + + @Test + public void testAgainstBruteForce_largeValues() { + for (int loop = 0; loop < LOOPS; loop++) { + int n = Math.max(1, (int) (Math.random() * 6)) * 2; // n is either 2,4,6,8, or 10 + Double[][] costMatrix = new Double[n][n]; + randomFillSymmetricMatrix(costMatrix, /* maxValue= */ 10000); + + MwpmInterface[] impls = getImplementations(costMatrix); + for (MwpmInterface mwpm : impls) { + int[] matching = mwpm.getMatching(); + BruteForceMwpm bfMwpm = new BruteForceMwpm(costMatrix); + double dpSoln = mwpm.getMinWeightCost(); + double bfSoln = bfMwpm.getMinWeightCost(); + assertThat(dpSoln).isEqualTo(bfSoln); + } + } + } + + @Test + public void testAgainstBruteForce_smallValues() { + for (int loop = 0; loop < LOOPS; loop++) { + int n = Math.max(1, (int) (Math.random() * 6)) * 2; // n is either 2,4,6,8, or 10 + Double[][] costMatrix = new Double[n][n]; + randomFillSymmetricMatrix(costMatrix, /* maxValue= */ 3); + + MwpmInterface[] impls = getImplementations(costMatrix); + for (MwpmInterface mwpm : impls) { + + BruteForceMwpm bfMwpm = new BruteForceMwpm(costMatrix); + double dpSoln = mwpm.getMinWeightCost(); + double bfSoln = bfMwpm.getMinWeightCost(); + + assertThat(dpSoln).isEqualTo(bfSoln); + } + } + } + + public void randomFillSymmetricMatrix(Double[][] dist, int maxValue) { + for (int i = 0; i < dist.length; i++) { + for (int j = i + 1; j < dist.length; j++) { + double val = (int) (Math.random() * maxValue); + dist[i][j] = dist[j][i] = val; + } + } + } + + private void assertOptimalMatching( + int[] matching, Double[][] costMatrix, double expectedMatchingCost) { + double total = 0; + for (int i = 0; i < matching.length; i += 2) { + total += costMatrix[matching[i]][matching[i + 1]]; + } + assertThat(total).isEqualTo(expectedMatchingCost); + } +} diff --git a/javatests/com/williamfiset/algorithms/geometry/ConvexHullMonotoneChainsAlgorithmTest.java b/src/test/java/com/williamfiset/algorithms/geometry/ConvexHullMonotoneChainsAlgorithmTest.java similarity index 94% rename from javatests/com/williamfiset/algorithms/geometry/ConvexHullMonotoneChainsAlgorithmTest.java rename to src/test/java/com/williamfiset/algorithms/geometry/ConvexHullMonotoneChainsAlgorithmTest.java index 5c1fece92..08cdaf9d4 100644 --- a/javatests/com/williamfiset/algorithms/geometry/ConvexHullMonotoneChainsAlgorithmTest.java +++ b/src/test/java/com/williamfiset/algorithms/geometry/ConvexHullMonotoneChainsAlgorithmTest.java @@ -1,12 +1,10 @@ -package javatests.com.williamfiset.algorithms.geometry; +package com.williamfiset.algorithms.geometry; import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableList; -import com.williamfiset.algorithms.geometry.ConvexHullMonotoneChainsAlgorithm; import java.awt.geom.*; -import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class ConvexHullMonotoneChainsAlgorithmTest { diff --git a/src/test/java/com/williamfiset/algorithms/geometry/MinimumCostConvexPolygonTriangulationTest.java b/src/test/java/com/williamfiset/algorithms/geometry/MinimumCostConvexPolygonTriangulationTest.java new file mode 100644 index 000000000..ecda84dc1 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/geometry/MinimumCostConvexPolygonTriangulationTest.java @@ -0,0 +1,51 @@ +package com.williamfiset.algorithms.geometry; + +import static com.google.common.truth.Truth.assertThat; + +import java.awt.geom.*; +import org.junit.jupiter.api.*; + +public class MinimumCostConvexPolygonTriangulationTest { + + private static final double TOLERANCE = 1e-3; + + @Test + public void MinimumCostConvexPolygonTriangulationBasicTest() { + Point2D[] pts = new Point2D[5]; + + pts[0] = new Point2D.Double(0, 0); + pts[1] = new Point2D.Double(1, 0); + pts[2] = new Point2D.Double(2, 1); + pts[3] = new Point2D.Double(1, 2); + pts[4] = new Point2D.Double(0, 2); + + double cost = MinimumCostConvexPolygonTriangulation.minimumCostTriangulation(pts); + assertThat(cost).isWithin(TOLERANCE).of(15.3); + } + + @Test + public void MinimumCostConvexPolygonTriangulationInvalidTest() { + Point2D[] pts = new Point2D[2]; + + pts[0] = new Point2D.Double(0, 0); + pts[1] = new Point2D.Double(1, 0); + + double cost = MinimumCostConvexPolygonTriangulation.minimumCostTriangulation(pts); + assertThat(cost).isEqualTo(0); + } + + @Test + public void MinimumCostConvexPolygonTriangulationConvex() { + Point2D[] pts = new Point2D[6]; + + pts[0] = new Point2D.Double(0, 0); + pts[1] = new Point2D.Double(4, 0); + pts[2] = new Point2D.Double(4, 2); + pts[3] = new Point2D.Double(1, 3); + pts[4] = new Point2D.Double(0, 2); + pts[5] = new Point2D.Double(0, 1); + + double cost = MinimumCostConvexPolygonTriangulation.minimumCostTriangulation(pts); + assertThat(cost).isWithin(TOLERANCE).of(31.386); + } +} diff --git a/javatests/com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyListTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyListTest.java similarity index 97% rename from javatests/com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyListTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyListTest.java index bbc884ec7..b7b3dfdad 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyListTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/ArticulationPointsAdjacencyListTest.java @@ -1,10 +1,9 @@ -package javatests.com.williamfiset.algorithms.graphtheory; +package com.williamfiset.algorithms.graphtheory; import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.graphtheory.ArticulationPointsAdjacencyList; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class ArticulationPointsAdjacencyListTest { diff --git a/javatests/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java similarity index 89% rename from javatests/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java index cbd3efe31..917ef9b92 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/BreadthFirstSearchAdjacencyListIterativeTest.java @@ -1,4 +1,4 @@ -package javatests.com.williamfiset.algorithms.graphtheory; +package com.williamfiset.algorithms.graphtheory; import static com.google.common.truth.Truth.assertThat; import static com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyListIterative.Edge; @@ -6,27 +6,26 @@ import static com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyListIterative.createEmptyGraph; import static java.lang.Math.max; import static java.lang.Math.random; +import static org.junit.jupiter.api.Assertions.assertThrows; -import com.williamfiset.algorithms.graphtheory.BellmanFordAdjacencyMatrix; -import com.williamfiset.algorithms.graphtheory.BreadthFirstSearchAdjacencyListIterative; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.*; public class BreadthFirstSearchAdjacencyListIterativeTest { BreadthFirstSearchAdjacencyListIterative solver; - @Before + @BeforeEach public void setup() { solver = null; } - @Test(expected = IllegalArgumentException.class) + @Test public void testNullGraphInput() { - new BreadthFirstSearchAdjacencyListIterative(null); + assertThrows( + IllegalArgumentException.class, () -> new BreadthFirstSearchAdjacencyListIterative(null)); } @Test diff --git a/javatests/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterativeTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterativeTest.java similarity index 90% rename from javatests/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterativeTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterativeTest.java index e35083ce9..8e9de87c0 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterativeTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListIterativeTest.java @@ -1,12 +1,11 @@ -package javatests.com.williamfiset.algorithms.graphtheory; +package com.williamfiset.algorithms.graphtheory; import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableList; -import com.williamfiset.algorithms.graphtheory.BridgesAdjacencyList; import java.util.*; import org.apache.commons.lang3.tuple.Pair; -import org.junit.*; +import org.junit.jupiter.api.*; public class BridgesAdjacencyListIterativeTest { @@ -44,7 +43,7 @@ public void testTreeCase() { BridgesAdjacencyList solver = new BridgesAdjacencyList(graph, n); List> sortedBridges = getSortedBridges(solver.findBridges()); - List expected = + List> expected = ImmutableList.of( Pair.of(0, 1), Pair.of(0, 2), @@ -82,10 +81,9 @@ public void graphWithCyclesTest() { addEdge(graph, 11, 6); BridgesAdjacencyList solver = new BridgesAdjacencyList(graph, n); - List bridges = solver.findBridges(); List> sortedBridges = getSortedBridges(solver.findBridges()); - List expected = + List> expected = ImmutableList.of(Pair.of(3, 7), Pair.of(7, 8), Pair.of(7, 9), Pair.of(4, 10)); assertThat(sortedBridges).containsExactlyElementsIn(expected); @@ -107,10 +105,10 @@ public void testGraphInSlides() { addEdge(graph, 8, 5); BridgesAdjacencyList solver = new BridgesAdjacencyList(graph, n); - List bridges = solver.findBridges(); List> sortedBridges = getSortedBridges(solver.findBridges()); - List expected = ImmutableList.of(Pair.of(2, 3), Pair.of(3, 4), Pair.of(2, 5)); + List> expected = + ImmutableList.of(Pair.of(2, 3), Pair.of(3, 4), Pair.of(2, 5)); assertThat(sortedBridges).containsExactlyElementsIn(expected); } @@ -134,7 +132,7 @@ public void testDisconnectedGraph() { BridgesAdjacencyList solver = new BridgesAdjacencyList(graph, n); List> sortedBridges = getSortedBridges(solver.findBridges()); - List expected = + List> expected = ImmutableList.of( Pair.of(0, 1), Pair.of(1, 2), @@ -151,7 +149,7 @@ private static List> getSortedBridges(List bridg for (int i = 0; i < bridgeNodes.size(); i += 2) { int node1 = bridgeNodes.get(i); int node2 = bridgeNodes.get(i + 1); - Pair pair; + Pair pair; if (node1 < node2) { pair = Pair.of(node1, node2); } else { diff --git a/javatests/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListTest.java similarity index 90% rename from javatests/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListTest.java index 62023daa7..595b024ff 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/BridgesAdjacencyListTest.java @@ -1,12 +1,11 @@ -package javatests.com.williamfiset.algorithms.graphtheory; +package com.williamfiset.algorithms.graphtheory; import static com.google.common.truth.Truth.assertThat; import com.google.common.collect.ImmutableList; -import com.williamfiset.algorithms.graphtheory.BridgesAdjacencyList; import java.util.*; import org.apache.commons.lang3.tuple.Pair; -import org.junit.*; +import org.junit.jupiter.api.*; public class BridgesAdjacencyListTest { @@ -44,7 +43,7 @@ public void testTreeCase() { BridgesAdjacencyList solver = new BridgesAdjacencyList(graph, n); List> sortedBridges = getSortedBridges(solver.findBridges()); - List expected = + List> expected = ImmutableList.of( Pair.of(0, 1), Pair.of(0, 2), @@ -82,10 +81,9 @@ public void graphWithCyclesTest() { addEdge(graph, 11, 6); BridgesAdjacencyList solver = new BridgesAdjacencyList(graph, n); - List bridges = solver.findBridges(); List> sortedBridges = getSortedBridges(solver.findBridges()); - List expected = + List> expected = ImmutableList.of(Pair.of(3, 7), Pair.of(7, 8), Pair.of(7, 9), Pair.of(4, 10)); assertThat(sortedBridges).containsExactlyElementsIn(expected); @@ -107,10 +105,10 @@ public void testGraphInSlides() { addEdge(graph, 8, 5); BridgesAdjacencyList solver = new BridgesAdjacencyList(graph, n); - List bridges = solver.findBridges(); List> sortedBridges = getSortedBridges(solver.findBridges()); - List expected = ImmutableList.of(Pair.of(2, 3), Pair.of(3, 4), Pair.of(2, 5)); + List> expected = + ImmutableList.of(Pair.of(2, 3), Pair.of(3, 4), Pair.of(2, 5)); assertThat(sortedBridges).containsExactlyElementsIn(expected); } @@ -134,7 +132,7 @@ public void testDisconnectedGraph() { BridgesAdjacencyList solver = new BridgesAdjacencyList(graph, n); List> sortedBridges = getSortedBridges(solver.findBridges()); - List expected = + List> expected = ImmutableList.of( Pair.of(0, 1), Pair.of(1, 2), @@ -151,7 +149,7 @@ private static List> getSortedBridges(List bridg for (int i = 0; i < bridgeNodes.size(); i += 2) { int node1 = bridgeNodes.get(i); int node2 = bridgeNodes.get(i + 1); - Pair pair; + Pair pair; if (node1 < node2) { pair = Pair.of(node1, node2); } else { diff --git a/javatests/com/williamfiset/algorithms/graphtheory/EulerianPathDirectedEdgesAdjacencyListTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/EulerianPathDirectedEdgesAdjacencyListTest.java similarity index 90% rename from javatests/com/williamfiset/algorithms/graphtheory/EulerianPathDirectedEdgesAdjacencyListTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/EulerianPathDirectedEdgesAdjacencyListTest.java index ca4a0cef6..8375c6b01 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/EulerianPathDirectedEdgesAdjacencyListTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/EulerianPathDirectedEdgesAdjacencyListTest.java @@ -1,16 +1,15 @@ -package javatests.com.williamfiset.algorithms.graphtheory; +package com.williamfiset.algorithms.graphtheory; import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.graphtheory.EulerianPathDirectedEdgesAdjacencyList; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class EulerianPathDirectedEdgesAdjacencyListTest { EulerianPathDirectedEdgesAdjacencyList solver; - @Before + @BeforeEach public void setUp() { solver = null; } @@ -270,6 +269,30 @@ public void testPathUniqueStartAndEndNodes() { verifyEulerianPath(graph); } + @Test + public void testGraphWithUniquePath() { + int n = 10; + List> graph = initializeEmptyGraph(n); + addDirectedEdge(graph, 0, 2); + addDirectedEdge(graph, 1, 3); + addDirectedEdge(graph, 2, 1); + addDirectedEdge(graph, 3, 0); + addDirectedEdge(graph, 3, 4); + addDirectedEdge(graph, 6, 3); + addDirectedEdge(graph, 6, 7); + addDirectedEdge(graph, 7, 8); + addDirectedEdge(graph, 8, 9); + addDirectedEdge(graph, 9, 6); + + verifyEulerianPath(graph); + + EulerianPathDirectedEdgesAdjacencyList solver; + solver = new EulerianPathDirectedEdgesAdjacencyList(graph); + int[] path = solver.getEulerianPath(); + int[] expected = {6, 7, 8, 9, 6, 3, 0, 2, 1, 3, 4}; + assertThat(path).isEqualTo(expected); + } + // There should be an Eulerian path on this directed graph from node 1 to node 0; @Test public void testSomewhatComplexPath() { diff --git a/javatests/com/williamfiset/algorithms/graphtheory/FloydWarshallSolverTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/FloydWarshallSolverTest.java similarity index 90% rename from javatests/com/williamfiset/algorithms/graphtheory/FloydWarshallSolverTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/FloydWarshallSolverTest.java index b55deda48..fbb65afdf 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/FloydWarshallSolverTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/FloydWarshallSolverTest.java @@ -1,12 +1,9 @@ -package javatests.com.williamfiset.algorithms.graphtheory; +package com.williamfiset.algorithms.graphtheory; import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.graphtheory.BellmanFordAdjacencyMatrix; -import com.williamfiset.algorithms.graphtheory.FloydWarshallSolver; import java.util.*; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.*; public class FloydWarshallSolverTest { @@ -15,7 +12,7 @@ public class FloydWarshallSolverTest { static double[][] matrix1, matrix2, matrix3; - @Before + @BeforeEach public void setup() { matrix1 = new double[][] { @@ -208,4 +205,23 @@ public void testNegativeCyclePropagation() { List fwPath = fw.reconstructShortestPath(s, e); assertThat(fwPath).isNull(); } + + @Test + public void testSingleNodeNegativeCycleDetection() { + int n = 3, s = 0, e = n - 1; + double[][] m = createMatrix(n); + m[1][2] = 1000; + m[2][2] = -1; + m[1][0] = 1; + m[2][0] = 1; + + FloydWarshallSolver solver = new FloydWarshallSolver(m); + double[][] soln = solver.getApspMatrix(); + + // 1 reaches 2 with cost 1000 and then it can go through the edge from 2 to 2 (which is -1) as + // many times as wanted and + // thus reach 2 with arbitrarily little cost + assertThat(soln[1][2]).isEqualTo(NEG_INF); + assertThat(soln[1][0]).isEqualTo(NEG_INF); + } } diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/KahnsTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/KahnsTest.java new file mode 100644 index 000000000..e58f1c327 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/KahnsTest.java @@ -0,0 +1,83 @@ +package com.williamfiset.algorithms.graphtheory; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.williamfiset.algorithms.utils.graphutils.GraphGenerator; +import com.williamfiset.algorithms.utils.graphutils.Utils; +import java.util.*; +import org.junit.jupiter.api.*; + +public class KahnsTest { + + private static boolean find(List> g, boolean[] vis, int at, int target) { + List edges = g.get(at); + if (edges.size() == 0 || vis[at]) { + return false; + } + if (at == target) { + return true; + } + vis[at] = true; + for (int next : edges) { + if (find(g, vis, next, target)) { + return true; + } + } + return false; + } + + // Verifies that the topological ordering is not violated, O(n^2) + private static boolean isTopsortOrdering(List> g, int[] order) { + int n = g.size(); + for (int i = 0; i < n; i++) { + for (int j = i + 1; j < n; j++) { + // Check that `node j` appears before `node i` + if (find(g, new boolean[n], order[j], order[i])) { + return false; + } + } + } + return true; + } + + @Test + public void cycleInGraph() { + List> g = Utils.createEmptyAdjacencyList(4); + Utils.addDirectedEdge(g, 0, 1); + Utils.addDirectedEdge(g, 1, 2); + Utils.addDirectedEdge(g, 2, 3); + Utils.addDirectedEdge(g, 3, 0); + Kahns solver = new Kahns(); + assertThrows(IllegalArgumentException.class, () -> solver.kahns(g)); + } + + @Test + public void verifyIsTopsortOrdering() { + List> g = Utils.createEmptyAdjacencyList(3); + Utils.addDirectedEdge(g, 0, 1); + Utils.addDirectedEdge(g, 1, 2); + int[] order = {2, 1, 0}; + assertThat(isTopsortOrdering(g, order)).isEqualTo(false); + } + + @Test + public void randomTest() { + GraphGenerator.DagGenerator dagGen = new GraphGenerator.DagGenerator(10, 10, 5, 5, 0.86); + List> g = dagGen.createDag(); + Kahns solver = new Kahns(); + int[] order = solver.kahns(g); + assertThat(isTopsortOrdering(g, order)).isEqualTo(true); + } + + @Test + public void randomTests() { + for (double p = 0.7; p <= 1.0; p += 0.02) { + GraphGenerator.DagGenerator dagGen = new GraphGenerator.DagGenerator(2, 20, 4, 15, p); + List> g = dagGen.createDag(); + Kahns solver = new Kahns(); + int[] order = solver.kahns(g); + assertThat(isTopsortOrdering(g, order)).isEqualTo(true); + } + } +} diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/KosarajuTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/KosarajuTest.java new file mode 100644 index 000000000..3ce6974c7 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/KosarajuTest.java @@ -0,0 +1,240 @@ +/** + * Tests for Kosaraju's algorithm + * + *

gradle test --info --tests "com.williamfiset.algorithms.graphtheory.KosarajuTest" + */ +package com.williamfiset.algorithms.graphtheory; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; + +import com.google.common.collect.ImmutableList; +import java.util.*; +import org.junit.jupiter.api.*; + +public class KosarajuTest { + + // Initialize graph with 'n' nodes. + public static List> createGraph(int n) { + List> graph = new ArrayList<>(); + for (int i = 0; i < n; i++) graph.add(new ArrayList<>()); + return graph; + } + + // Add directed edge to graph. + public static void addEdge(List> graph, int from, int to) { + graph.get(from).add(to); + } + + @Test + public void nullGraphConstructor() { + assertThrowsExactly(IllegalArgumentException.class, () -> new Kosaraju(null)); + } + + @Test + public void singletonCase() { + int n = 1; + List> g = createGraph(n); + + Kosaraju solver = new Kosaraju(g); + + int[] actual = solver.getSccs(); + int[] expected = new int[n]; + assertThat(actual).isEqualTo(expected); + assertThat(solver.sccCount()).isEqualTo(1); + } + + @Test + public void testTwoDisjointComponents() { + int n = 5; + List> g = createGraph(n); + + addEdge(g, 0, 1); + addEdge(g, 1, 0); + + addEdge(g, 2, 3); + addEdge(g, 3, 4); + addEdge(g, 4, 2); + + Kosaraju solver = new Kosaraju(g); + + List> expectedSccs = + ImmutableList.of(ImmutableList.of(0, 1), ImmutableList.of(2, 3, 4)); + + assertThat(solver.sccCount()).isEqualTo(expectedSccs.size()); + assertThat(isScc(solver.getSccs(), expectedSccs)).isTrue(); + } + + @Test + public void testButterflyCase() { + int n = 5; + List> g = createGraph(n); + + addEdge(g, 0, 1); + addEdge(g, 1, 2); + addEdge(g, 2, 3); + addEdge(g, 3, 1); + addEdge(g, 1, 4); + addEdge(g, 4, 0); + + Kosaraju solver = new Kosaraju(g); + + List> expectedSccs = ImmutableList.of(ImmutableList.of(0, 1, 2, 3, 4)); + + assertThat(solver.sccCount()).isEqualTo(expectedSccs.size()); + assertThat(isScc(solver.getSccs(), expectedSccs)).isTrue(); + } + + @Test + public void testDisjointTree() { + int n = 7; + List> g = createGraph(n); + + addEdge(g, 0, 1); + addEdge(g, 1, 2); + + addEdge(g, 4, 3); + addEdge(g, 5, 3); + addEdge(g, 6, 3); + + Kosaraju solver = new Kosaraju(g); + + List> expectedSccs = + ImmutableList.of( + ImmutableList.of(0), + ImmutableList.of(1), + ImmutableList.of(2), + ImmutableList.of(3), + ImmutableList.of(4), + ImmutableList.of(5), + ImmutableList.of(6)); + + assertThat(solver.sccCount()).isEqualTo(expectedSccs.size()); + assertThat(isScc(solver.getSccs(), expectedSccs)).isTrue(); + } + + @Test + public void testDisjointTreeFromHackerrank() { + // https://www.hackerearth.com/practice/algorithms/graphs/strongly-connected-components/tutorial + int n = 16; // node 0 not used since these are 1 based + List> g = createGraph(n); + + addEdge(g, 3, 1); + addEdge(g, 12, 11); + addEdge(g, 10, 9); + addEdge(g, 8, 5); + addEdge(g, 1, 12); + addEdge(g, 10, 2); + addEdge(g, 1, 14); + addEdge(g, 7, 9); + addEdge(g, 10, 13); + addEdge(g, 11, 1); + addEdge(g, 6, 5); + addEdge(g, 1, 15); + addEdge(g, 2, 11); + addEdge(g, 2, 6); + addEdge(g, 9, 11); + + Kosaraju solver = new Kosaraju(g); + + List> expectedSccs = + ImmutableList.of( + ImmutableList.of(0), + ImmutableList.of(2), + ImmutableList.of(3), + ImmutableList.of(4), + ImmutableList.of(5), + ImmutableList.of(6), + ImmutableList.of(7), + ImmutableList.of(8), + ImmutableList.of(9), + ImmutableList.of(10), + ImmutableList.of(1, 11, 12), + ImmutableList.of(13), + ImmutableList.of(14), + ImmutableList.of(15)); + + assertThat(solver.sccCount()).isEqualTo(expectedSccs.size()); + assertThat(isScc(solver.getSccs(), expectedSccs)).isTrue(); + } + + @Test + public void testFirstGraphInSlides() { + int n = 9; + List> g = createGraph(n); + + addEdge(g, 0, 1); + addEdge(g, 1, 0); + addEdge(g, 0, 8); + addEdge(g, 8, 0); + addEdge(g, 8, 7); + addEdge(g, 7, 6); + addEdge(g, 6, 7); + addEdge(g, 1, 7); + addEdge(g, 2, 1); + addEdge(g, 2, 6); + addEdge(g, 5, 6); + addEdge(g, 2, 5); + addEdge(g, 5, 3); + addEdge(g, 3, 2); + addEdge(g, 4, 3); + addEdge(g, 4, 5); + + Kosaraju solver = new Kosaraju(g); + + List> expectedSccs = + ImmutableList.of( + ImmutableList.of(0, 1, 8), + ImmutableList.of(7, 6), + ImmutableList.of(2, 3, 5), + ImmutableList.of(4)); + + assertThat(solver.sccCount()).isEqualTo(expectedSccs.size()); + assertThat(isScc(solver.getSccs(), expectedSccs)).isTrue(); + } + + @Test + public void testLastGraphInSlides() { + int n = 8; + List> g = createGraph(n); + + addEdge(g, 0, 1); + addEdge(g, 1, 2); + addEdge(g, 2, 0); + addEdge(g, 3, 4); + addEdge(g, 3, 7); + addEdge(g, 4, 5); + addEdge(g, 5, 0); + addEdge(g, 5, 6); + addEdge(g, 6, 0); + addEdge(g, 6, 2); + addEdge(g, 6, 4); + addEdge(g, 7, 3); + addEdge(g, 7, 5); + + Kosaraju solver = new Kosaraju(g); + + List> expectedSccs = + ImmutableList.of( + ImmutableList.of(6, 5, 4), ImmutableList.of(3, 7), ImmutableList.of(0, 2, 1)); + assertThat(solver.sccCount()).isEqualTo(expectedSccs.size()); + assertThat(isScc(solver.getSccs(), expectedSccs)).isTrue(); + } + + private static boolean isScc(int[] ids, List> expectedSccs) { + Set set = new HashSet<>(); + Set sccComponentIds = new HashSet<>(); + for (List indexes : expectedSccs) { + set.clear(); + int componentId = 0; + for (int index : indexes) { + componentId = ids[index]; + set.add(componentId); + } + if (sccComponentIds.contains(componentId)) return false; + if (set.size() != 1) return false; + sccComponentIds.add(componentId); + } + return true; + } +} diff --git a/javatests/com/williamfiset/algorithms/graphtheory/SteinerTreeTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/SteinerTreeTest.java similarity index 95% rename from javatests/com/williamfiset/algorithms/graphtheory/SteinerTreeTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/SteinerTreeTest.java index c9d6bc980..d4713841d 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/SteinerTreeTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/SteinerTreeTest.java @@ -1,10 +1,8 @@ -package javatests.com.williamfiset.algorithms.graphtheory; +package com.williamfiset.algorithms.graphtheory; import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.graphtheory.SteinerTree; -import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class SteinerTreeTest { diff --git a/javatests/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyListTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyListTest.java similarity index 64% rename from javatests/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyListTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyListTest.java index e408e76ed..cab07eebf 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyListTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/TarjanSccSolverAdjacencyListTest.java @@ -1,11 +1,11 @@ -package javatests.com.williamfiset.algorithms.graphtheory; +package com.williamfiset.algorithms.graphtheory; import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.common.collect.ImmutableList; -import com.williamfiset.algorithms.graphtheory.TarjanSccSolverAdjacencyList; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class TarjanSccSolverAdjacencyListTest { @@ -21,9 +21,9 @@ public static void addEdge(List> graph, int from, int to) { graph.get(from).add(to); } - @Test(expected = IllegalArgumentException.class) + @Test public void nullGraphConstructor() { - new TarjanSccSolverAdjacencyList(null); + assertThrows(IllegalArgumentException.class, () -> new TarjanSccSolverAdjacencyList(null)); } @Test @@ -83,6 +83,81 @@ public void testButterflyCase() { assertThat(isScc(solver.getSccs(), expectedSccs)).isTrue(); } + @Test + public void testDisjointTree() { + int n = 7; + List> g = createGraph(n); + + addEdge(g, 0, 1); + addEdge(g, 1, 2); + + addEdge(g, 4, 3); + addEdge(g, 5, 3); + addEdge(g, 6, 3); + + TarjanSccSolverAdjacencyList solver = new TarjanSccSolverAdjacencyList(g); + solver.solve(); + + List> expectedSccs = + ImmutableList.of( + ImmutableList.of(0), + ImmutableList.of(1), + ImmutableList.of(2), + ImmutableList.of(3), + ImmutableList.of(4), + ImmutableList.of(5), + ImmutableList.of(6)); + + assertThat(solver.sccCount()).isEqualTo(expectedSccs.size()); + assertThat(isScc(solver.getSccs(), expectedSccs)).isTrue(); + } + + @Test + public void testDisjointTreeFromHackerrank() { + // https://www.hackerearth.com/practice/algorithms/graphs/strongly-connected-components/tutorial + int n = 16; // node 0 not used since these are 1 based + List> g = createGraph(n); + + addEdge(g, 3, 1); + addEdge(g, 12, 11); + addEdge(g, 10, 9); + addEdge(g, 8, 5); + addEdge(g, 1, 12); + addEdge(g, 10, 2); + addEdge(g, 1, 14); + addEdge(g, 7, 9); + addEdge(g, 10, 13); + addEdge(g, 11, 1); + addEdge(g, 6, 5); + addEdge(g, 1, 15); + addEdge(g, 2, 11); + addEdge(g, 2, 6); + addEdge(g, 9, 11); + + TarjanSccSolverAdjacencyList solver = new TarjanSccSolverAdjacencyList(g); + solver.solve(); + + List> expectedSccs = + ImmutableList.of( + ImmutableList.of(0), + ImmutableList.of(2), + ImmutableList.of(3), + ImmutableList.of(4), + ImmutableList.of(5), + ImmutableList.of(6), + ImmutableList.of(7), + ImmutableList.of(8), + ImmutableList.of(9), + ImmutableList.of(10), + ImmutableList.of(1, 11, 12), + ImmutableList.of(13), + ImmutableList.of(14), + ImmutableList.of(15)); + + assertThat(solver.sccCount()).isEqualTo(expectedSccs.size()); + assertThat(isScc(solver.getSccs(), expectedSccs)).isTrue(); + } + @Test public void testFirstGraphInSlides() { int n = 9; @@ -124,19 +199,19 @@ public void testLastGraphInSlides() { int n = 8; List> g = createGraph(n); - addEdge(g, 6, 0); - addEdge(g, 6, 2); - addEdge(g, 3, 4); - addEdge(g, 6, 4); - addEdge(g, 2, 0); addEdge(g, 0, 1); + addEdge(g, 1, 2); + addEdge(g, 2, 0); + addEdge(g, 3, 4); + addEdge(g, 3, 7); addEdge(g, 4, 5); + addEdge(g, 5, 0); addEdge(g, 5, 6); - addEdge(g, 3, 7); - addEdge(g, 7, 5); - addEdge(g, 1, 2); + addEdge(g, 6, 0); + addEdge(g, 6, 2); + addEdge(g, 6, 4); addEdge(g, 7, 3); - addEdge(g, 5, 0); + addEdge(g, 7, 5); TarjanSccSolverAdjacencyList solver = new TarjanSccSolverAdjacencyList(g); solver.solve(); @@ -144,7 +219,6 @@ public void testLastGraphInSlides() { List> expectedSccs = ImmutableList.of( ImmutableList.of(6, 5, 4), ImmutableList.of(3, 7), ImmutableList.of(0, 2, 1)); - assertThat(solver.sccCount()).isEqualTo(expectedSccs.size()); assertThat(isScc(solver.getSccs(), expectedSccs)).isTrue(); } diff --git a/javatests/com/williamfiset/algorithms/graphtheory/TravelingSalesmanProblemTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/TravelingSalesmanProblemTest.java similarity index 86% rename from javatests/com/williamfiset/algorithms/graphtheory/TravelingSalesmanProblemTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/TravelingSalesmanProblemTest.java index 89505c738..5e8909bdd 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/TravelingSalesmanProblemTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/TravelingSalesmanProblemTest.java @@ -1,71 +1,71 @@ -package javatests.com.williamfiset.algorithms.graphtheory; +package com.williamfiset.algorithms.graphtheory; import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; -import com.williamfiset.algorithms.graphtheory.TspBruteForce; -import com.williamfiset.algorithms.graphtheory.TspDynamicProgrammingIterative; -import com.williamfiset.algorithms.graphtheory.TspDynamicProgrammingRecursive; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class TravelingSalesmanProblemTest { private static final double EPS = 1e-5; - @Test(expected = IllegalArgumentException.class) + @Test public void testTspRecursiveInvalidStartNode() { double[][] dist = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; - new TspDynamicProgrammingRecursive(321, dist); + assertThrows( + IllegalArgumentException.class, () -> new TspDynamicProgrammingRecursive(321, dist)); } - @Test(expected = IllegalArgumentException.class) + @Test public void testTspIterativeInvalidStartNode() { double[][] dist = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; - new TspDynamicProgrammingIterative(321, dist); + assertThrows( + IllegalArgumentException.class, () -> new TspDynamicProgrammingIterative(321, dist)); } - @Test(expected = IllegalStateException.class) + @Test public void testTspRecursiveNonSquareMatrix() { double[][] dist = { {1, 2, 3}, {4, 5, 6} }; - new TspDynamicProgrammingRecursive(dist); + assertThrows(IllegalStateException.class, () -> new TspDynamicProgrammingRecursive(dist)); } - @Test(expected = IllegalStateException.class) + @Test public void testTspIterativeNonSquareMatrix() { double[][] dist = { {1, 2, 3}, {4, 5, 6} }; - new TspDynamicProgrammingIterative(dist); + assertThrows(IllegalStateException.class, () -> new TspDynamicProgrammingIterative(dist)); } - @Test(expected = IllegalStateException.class) + @Test public void testTspRecursiveSmallGraph() { double[][] dist = { {0, 1}, {1, 0} }; - new TspDynamicProgrammingRecursive(dist); + assertThrows(IllegalStateException.class, () -> new TspDynamicProgrammingRecursive(dist)); } - @Test(expected = IllegalStateException.class) + @Test public void testTspIterativeSmallGraph() { double[][] dist = { {0, 1}, {1, 0} }; - new TspDynamicProgrammingIterative(dist); + assertThrows(IllegalStateException.class, () -> new TspDynamicProgrammingIterative(dist)); } @Test diff --git a/javatests/com/williamfiset/algorithms/graphtheory/TwoSatSolverAdjacencyListTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/TwoSatSolverAdjacencyListTest.java similarity index 92% rename from javatests/com/williamfiset/algorithms/graphtheory/TwoSatSolverAdjacencyListTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/TwoSatSolverAdjacencyListTest.java index 4ec75b87d..29eaa0e4b 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/TwoSatSolverAdjacencyListTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/TwoSatSolverAdjacencyListTest.java @@ -1,10 +1,9 @@ -package javatests.com.williamfiset.algorithms.graphtheory; +package com.williamfiset.algorithms.graphtheory; import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.graphtheory.TwoSatSolverAdjacencyList; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class TwoSatSolverAdjacencyListTest { diff --git a/javatests/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyListTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyListTest.java similarity index 92% rename from javatests/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyListTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyListTest.java index 20e742e16..4768393ae 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyListTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/BipartiteGraphCheckAdjacencyListTest.java @@ -1,18 +1,17 @@ -package javatests.com.williamfiset.algorithms.graphtheory.networkflow; +package com.williamfiset.algorithms.graphtheory.networkflow; import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.graphtheory.networkflow.BipartiteGraphCheckAdjacencyList; import com.williamfiset.algorithms.utils.graphutils.Utils; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class BipartiteGraphCheckAdjacencyListTest { private List> graph; private BipartiteGraphCheckAdjacencyList solver; - @Before + @BeforeEach public void setUp() {} @Test diff --git a/javatests/com/williamfiset/algorithms/graphtheory/networkflow/MaxFlowTests.java b/src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/MaxFlowTests.java similarity index 85% rename from javatests/com/williamfiset/algorithms/graphtheory/networkflow/MaxFlowTests.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/MaxFlowTests.java index 7e35648ec..3c9f23f12 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/networkflow/MaxFlowTests.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/MaxFlowTests.java @@ -1,23 +1,16 @@ -package javatests.com.williamfiset.algorithms.graphtheory.networkflow; +package com.williamfiset.algorithms.graphtheory.networkflow; import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.graphtheory.networkflow.CapacityScalingSolverAdjacencyList; -import com.williamfiset.algorithms.graphtheory.networkflow.Dinics; -import com.williamfiset.algorithms.graphtheory.networkflow.EdmondsKarpAdjacencyList; -import com.williamfiset.algorithms.graphtheory.networkflow.FordFulkersonDfsSolverAdjacencyList; -import com.williamfiset.algorithms.graphtheory.networkflow.MinCostMaxFlowJohnsons; -import com.williamfiset.algorithms.graphtheory.networkflow.MinCostMaxFlowWithBellmanFord; -import com.williamfiset.algorithms.graphtheory.networkflow.NetworkFlowSolverBase; import com.williamfiset.algorithms.graphtheory.networkflow.NetworkFlowSolverBase.Edge; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class MaxFlowTests { List solvers; - @Before + @BeforeEach public void setUp() { solvers = new ArrayList<>(); } diff --git a/javatests/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowTests.java b/src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowTests.java similarity index 80% rename from javatests/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowTests.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowTests.java index 45955897c..c250a99e2 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowTests.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/networkflow/MinCostMaxFlowTests.java @@ -1,17 +1,15 @@ -package javatests.com.williamfiset.algorithms.graphtheory.networkflow; +package com.williamfiset.algorithms.graphtheory.networkflow; import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.graphtheory.networkflow.MinCostMaxFlowJohnsons; -import com.williamfiset.algorithms.graphtheory.networkflow.NetworkFlowSolverBase; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class MinCostMaxFlowTests { List solvers; - @Before + @BeforeEach public void setUp() { solvers = new ArrayList<>(); } diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorEulerTourTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorEulerTourTest.java new file mode 100644 index 000000000..f837cb168 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorEulerTourTest.java @@ -0,0 +1,106 @@ +package com.williamfiset.algorithms.graphtheory.treealgorithms; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.*; +import org.junit.jupiter.api.*; + +public class LowestCommonAncestorEulerTourTest { + + private LowestCommonAncestorEulerTour.TreeNode createFirstTreeFromSlides() { + int n = 17; + List> tree = LowestCommonAncestorEulerTour.createEmptyGraph(n); + + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 0, 1); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 0, 2); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 1, 3); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 1, 4); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 2, 5); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 2, 6); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 2, 7); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 3, 8); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 3, 9); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 5, 10); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 5, 11); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 7, 12); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 7, 13); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 11, 14); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 11, 15); + LowestCommonAncestorEulerTour.addUndirectedEdge(tree, 11, 16); + + return LowestCommonAncestorEulerTour.TreeNode.rootTree(tree, 0); + } + + @Test + public void testLcaTreeFromSlides1() { + LowestCommonAncestorEulerTour.TreeNode root = createFirstTreeFromSlides(); + LowestCommonAncestorEulerTour fastSolver = new LowestCommonAncestorEulerTour(root); + assertThat(fastSolver.lca(14, 13).index()).isEqualTo(2); + assertThat(fastSolver.lca(10, 16).index()).isEqualTo(5); + assertThat(fastSolver.lca(9, 11).index()).isEqualTo(0); + } + + @Test + public void testLcaTreeFromSlides2() { + LowestCommonAncestorEulerTour.TreeNode root = createFirstTreeFromSlides(); + LowestCommonAncestorEulerTour fastSolver = new LowestCommonAncestorEulerTour(root); + assertThat(fastSolver.lca(8, 9).index()).isEqualTo(3); + assertThat(fastSolver.lca(4, 8).index()).isEqualTo(1); + assertThat(fastSolver.lca(6, 13).index()).isEqualTo(2); + assertThat(fastSolver.lca(7, 13).index()).isEqualTo(7); + assertThat(fastSolver.lca(10, 5).index()).isEqualTo(5); + assertThat(fastSolver.lca(2, 16).index()).isEqualTo(2); + } + + @Test + public void testLcaOfTheSameNodeIsItself() { + LowestCommonAncestorEulerTour.TreeNode root = createFirstTreeFromSlides(); + LowestCommonAncestorEulerTour fastSolver = new LowestCommonAncestorEulerTour(root); + + // Try all nodes + for (int id = 0; id < root.size(); id++) { + assertThat(fastSolver.lca(id, id).index()).isEqualTo(id); + } + } + + @Test + public void randomizedLcaQueriesVsOtherImpl() { + for (int n = 1; n < 1000; n++) { + List> g = generateRandomTree(n); + + LowestCommonAncestor.TreeNode root1 = LowestCommonAncestor.TreeNode.rootTree(g, 0); + LowestCommonAncestorEulerTour.TreeNode root2 = + LowestCommonAncestorEulerTour.TreeNode.rootTree(g, 0); + + LowestCommonAncestor slowSolver = new LowestCommonAncestor(root1); + LowestCommonAncestorEulerTour fastSolver = new LowestCommonAncestorEulerTour(root2); + + for (int i = 0; i < 100; i++) { + int l = (int) (Math.random() * n); + int r = (int) (Math.random() * n); + int L = Math.min(l, r); + int R = Math.max(l, r); + + LowestCommonAncestor.TreeNode lca1 = slowSolver.lca(L, R); + LowestCommonAncestorEulerTour.TreeNode lca2 = fastSolver.lca(L, R); + + assertThat(lca1).isNotNull(); + assertThat(lca2).isNotNull(); + assertThat(lca1.id()).isEqualTo(lca2.index()); + } + } + } + + public static List> generateRandomTree(int n) { + List nodes = new ArrayList<>(); + nodes.add(0); + + List> g = LowestCommonAncestorEulerTour.createEmptyGraph(n); + for (int nextNode = 1; nodes.size() != n; nextNode++) { + int randomNode = nodes.get((int) (Math.random() * nodes.size())); + LowestCommonAncestorEulerTour.addUndirectedEdge(g, randomNode, nextNode); + nodes.add(nextNode); + } + return g; + } +} diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorTest.java new file mode 100644 index 000000000..d8b1f07bb --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/LowestCommonAncestorTest.java @@ -0,0 +1,67 @@ +package com.williamfiset.algorithms.graphtheory.treealgorithms; + +import static com.google.common.truth.Truth.assertThat; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.LowestCommonAncestor.addUndirectedEdge; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.LowestCommonAncestor.createEmptyGraph; + +import com.williamfiset.algorithms.graphtheory.treealgorithms.LowestCommonAncestor.TreeNode; +import java.util.*; +import org.junit.jupiter.api.*; + +public class LowestCommonAncestorTest { + + private TreeNode createFirstTreeFromSlides() { + int n = 17; + List> tree = createEmptyGraph(n); + + addUndirectedEdge(tree, 0, 1); + addUndirectedEdge(tree, 0, 2); + addUndirectedEdge(tree, 1, 3); + addUndirectedEdge(tree, 1, 4); + addUndirectedEdge(tree, 2, 5); + addUndirectedEdge(tree, 2, 6); + addUndirectedEdge(tree, 2, 7); + addUndirectedEdge(tree, 3, 8); + addUndirectedEdge(tree, 3, 9); + addUndirectedEdge(tree, 5, 10); + addUndirectedEdge(tree, 5, 11); + addUndirectedEdge(tree, 7, 12); + addUndirectedEdge(tree, 7, 13); + addUndirectedEdge(tree, 11, 14); + addUndirectedEdge(tree, 11, 15); + addUndirectedEdge(tree, 11, 16); + + return LowestCommonAncestor.TreeNode.rootTree(tree, 0); + } + + @Test + public void testLcaTreeFromSlides1() { + TreeNode root = createFirstTreeFromSlides(); + LowestCommonAncestor solver = new LowestCommonAncestor(root); + assertThat(solver.lca(14, 13).id()).isEqualTo(2); + assertThat(solver.lca(10, 16).id()).isEqualTo(5); + assertThat(solver.lca(9, 11).id()).isEqualTo(0); + } + + @Test + public void testLcaTreeFromSlides2() { + TreeNode root = createFirstTreeFromSlides(); + LowestCommonAncestor solver = new LowestCommonAncestor(root); + assertThat(solver.lca(8, 9).id()).isEqualTo(3); + assertThat(solver.lca(4, 8).id()).isEqualTo(1); + assertThat(solver.lca(6, 13).id()).isEqualTo(2); + assertThat(solver.lca(7, 13).id()).isEqualTo(7); + assertThat(solver.lca(10, 5).id()).isEqualTo(5); + assertThat(solver.lca(2, 16).id()).isEqualTo(2); + } + + @Test + public void testLcaOfTheSameNodeIsItself() { + TreeNode root = createFirstTreeFromSlides(); + LowestCommonAncestor solver = new LowestCommonAncestor(root); + // Try all nodes + for (int id = 0; id < root.size(); id++) { + assertThat(solver.lca(id, id).id()).isEqualTo(id); + } + } +} diff --git a/javatests/com/williamfiset/algorithms/graphtheory/treealgorithms/RootingTreeTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/RootingTreeTest.java similarity index 93% rename from javatests/com/williamfiset/algorithms/graphtheory/treealgorithms/RootingTreeTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/RootingTreeTest.java index d00e0f5d3..b22412620 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/treealgorithms/RootingTreeTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/RootingTreeTest.java @@ -1,11 +1,10 @@ -package javatests.com.williamfiset.algorithms.graphtheory.treealgorithms; +package com.williamfiset.algorithms.graphtheory.treealgorithms; import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.graphtheory.treealgorithms.RootingTree; import com.williamfiset.algorithms.graphtheory.treealgorithms.RootingTree.TreeNode; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class RootingTreeTest { diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenterLongestPathImplTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenterLongestPathImplTest.java new file mode 100644 index 000000000..866dc4613 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenterLongestPathImplTest.java @@ -0,0 +1,72 @@ +// To run this test in isolation from root folder: +// +// $ gradle test --tests +// com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCenterLongestPathImplTest + +package com.williamfiset.algorithms.graphtheory.treealgorithms; + +import static com.google.common.truth.Truth.assertThat; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCenterLongestPathImpl.addUndirectedEdge; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCenterLongestPathImpl.createEmptyTree; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCenterLongestPathImpl.findTreeCenters; + +import java.util.*; +import org.junit.jupiter.api.*; + +public class TreeCenterLongestPathImplTest { + + @Test + public void simpleTest1() { + List> graph = createEmptyTree(9); + addUndirectedEdge(graph, 0, 1); + addUndirectedEdge(graph, 2, 1); + addUndirectedEdge(graph, 2, 3); + addUndirectedEdge(graph, 3, 4); + addUndirectedEdge(graph, 5, 3); + addUndirectedEdge(graph, 2, 6); + addUndirectedEdge(graph, 6, 7); + addUndirectedEdge(graph, 6, 8); + assertThat(findTreeCenters(graph)).containsExactly(2); + } + + @Test + public void singleton() { + assertThat(findTreeCenters(createEmptyTree(1))).containsExactly(0); + } + + @Test + public void twoNodeTree() { + List> graph = createEmptyTree(2); + addUndirectedEdge(graph, 0, 1); + assertThat(findTreeCenters(graph)).containsExactly(0, 1); + } + + @Test + public void simpleTest2() { + List> graph = createEmptyTree(3); + addUndirectedEdge(graph, 0, 1); + addUndirectedEdge(graph, 1, 2); + assertThat(findTreeCenters(graph)).containsExactly(1); + } + + @Test + public void simpleTest3() { + List> graph = createEmptyTree(4); + addUndirectedEdge(graph, 0, 1); + addUndirectedEdge(graph, 1, 2); + addUndirectedEdge(graph, 2, 3); + assertThat(findTreeCenters(graph)).containsExactly(1, 2); + } + + @Test + public void simpleTest4() { + List> graph = createEmptyTree(7); + addUndirectedEdge(graph, 0, 1); + addUndirectedEdge(graph, 1, 2); + addUndirectedEdge(graph, 2, 3); + addUndirectedEdge(graph, 3, 4); + addUndirectedEdge(graph, 4, 5); + addUndirectedEdge(graph, 4, 6); + assertThat(findTreeCenters(graph)).containsExactly(2, 3); + } +} diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenterTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenterTest.java new file mode 100644 index 000000000..56723126a --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCenterTest.java @@ -0,0 +1,101 @@ +// To run this test in isolation from root folder: +// +// $ gradle test --tests +// com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCenterTest + +package com.williamfiset.algorithms.graphtheory.treealgorithms; + +import static com.google.common.truth.Truth.assertThat; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCenter.addUndirectedEdge; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCenter.createEmptyTree; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCenter.findTreeCenters; + +import java.util.*; +import org.junit.jupiter.api.*; + +public class TreeCenterTest { + + @Test + public void simpleTest1() { + List> graph = createEmptyTree(9); + addUndirectedEdge(graph, 0, 1); + addUndirectedEdge(graph, 2, 1); + addUndirectedEdge(graph, 2, 3); + addUndirectedEdge(graph, 3, 4); + addUndirectedEdge(graph, 5, 3); + addUndirectedEdge(graph, 2, 6); + addUndirectedEdge(graph, 6, 7); + addUndirectedEdge(graph, 6, 8); + assertThat(findTreeCenters(graph)).containsExactly(2); + } + + @Test + public void singleton() { + assertThat(findTreeCenters(createEmptyTree(1))).containsExactly(0); + } + + @Test + public void twoNodeTree() { + List> graph = createEmptyTree(2); + addUndirectedEdge(graph, 0, 1); + assertThat(findTreeCenters(graph)).containsExactly(0, 1); + } + + @Test + public void simpleTest2() { + List> graph = createEmptyTree(3); + addUndirectedEdge(graph, 0, 1); + addUndirectedEdge(graph, 1, 2); + assertThat(findTreeCenters(graph)).containsExactly(1); + } + + @Test + public void simpleTest3() { + List> graph = createEmptyTree(4); + addUndirectedEdge(graph, 0, 1); + addUndirectedEdge(graph, 1, 2); + addUndirectedEdge(graph, 2, 3); + assertThat(findTreeCenters(graph)).containsExactly(1, 2); + } + + @Test + public void simpleTest4() { + List> graph = createEmptyTree(7); + addUndirectedEdge(graph, 0, 1); + addUndirectedEdge(graph, 1, 2); + addUndirectedEdge(graph, 2, 3); + addUndirectedEdge(graph, 3, 4); + addUndirectedEdge(graph, 4, 5); + addUndirectedEdge(graph, 4, 6); + assertThat(findTreeCenters(graph)).containsExactly(2, 3); + } + + @Test + public void testTreeCenterVsOtherImpl() { + for (int n = 1; n < 500; n++) { + for (int loops = 0; loops < 100; loops++) { + List> tree = generateRandomTree(n); + + List impl1 = findTreeCenters(tree); + List impl2 = + com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCenterLongestPathImpl + .findTreeCenters(tree); + + assertThat(impl1).containsExactlyElementsIn(impl2); + } + } + } + + public static List> generateRandomTree(int n) { + List nodes = new ArrayList<>(); + nodes.add(0); + + List> g = createEmptyTree(n); + for (int nextNode = 1; nodes.size() != n; nextNode++) { + int randomNode = nodes.get((int) (Math.random() * nodes.size())); + addUndirectedEdge(g, randomNode, nextNode); + nodes.add(nextNode); + } + return g; + } +} diff --git a/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismTest.java new file mode 100644 index 000000000..40a3c1e0e --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismTest.java @@ -0,0 +1,165 @@ +// To run this test in isolation from root folder: +// +// $ gradle test --tests +// com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphismTest + +package com.williamfiset.algorithms.graphtheory.treealgorithms; + +import static com.google.common.truth.Truth.assertThat; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphism.addUndirectedEdge; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphism.createEmptyGraph; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphism.treesAreIsomorphic; +import static org.junit.Assert.assertThrows; + +import java.util.*; +import org.junit.jupiter.api.*; + +public class TreeIsomorphismTest { + + @Test + public void emptyTreeThrowsException() { + assertThrows( + IllegalArgumentException.class, + () -> treesAreIsomorphic(createEmptyGraph(0), createEmptyGraph(1))); + } + + @Test + public void singletonTreesAreIsomorphic() { + assertThat(treesAreIsomorphic(createEmptyGraph(1), createEmptyGraph(1))).isEqualTo(true); + } + + @Test + public void testTwoNodeTree() { + List> tree1 = createEmptyGraph(2); + List> tree2 = createEmptyGraph(2); + addUndirectedEdge(tree1, 0, 1); + addUndirectedEdge(tree2, 1, 0); + assertThat(treesAreIsomorphic(tree1, tree2)).isEqualTo(true); + } + + @Test + public void testSmall() { + List> tree1 = createEmptyGraph(5); + List> tree2 = createEmptyGraph(5); + + addUndirectedEdge(tree1, 2, 0); + addUndirectedEdge(tree1, 2, 1); + addUndirectedEdge(tree1, 2, 3); + addUndirectedEdge(tree1, 3, 4); + + addUndirectedEdge(tree2, 1, 3); + addUndirectedEdge(tree2, 1, 0); + addUndirectedEdge(tree2, 1, 2); + addUndirectedEdge(tree2, 2, 4); + + assertThat(treesAreIsomorphic(tree1, tree2)).isEqualTo(true); + } + + @Test + public void testSimilarChains() { + // Trees 1 and 3 are equal + int n = 10; + List> tree1 = createEmptyGraph(n); + List> tree2 = createEmptyGraph(n); + List> tree3 = createEmptyGraph(n); + + addUndirectedEdge(tree1, 0, 1); + addUndirectedEdge(tree1, 1, 3); + addUndirectedEdge(tree1, 3, 5); + addUndirectedEdge(tree1, 5, 7); + addUndirectedEdge(tree1, 7, 8); + addUndirectedEdge(tree1, 8, 9); + addUndirectedEdge(tree1, 2, 1); + addUndirectedEdge(tree1, 4, 3); + addUndirectedEdge(tree1, 6, 5); + + addUndirectedEdge(tree2, 0, 1); + addUndirectedEdge(tree2, 1, 3); + addUndirectedEdge(tree2, 3, 5); + addUndirectedEdge(tree2, 5, 6); + addUndirectedEdge(tree2, 6, 8); + addUndirectedEdge(tree2, 8, 9); + addUndirectedEdge(tree2, 6, 7); + addUndirectedEdge(tree2, 4, 3); + addUndirectedEdge(tree2, 2, 1); + + addUndirectedEdge(tree3, 0, 1); + addUndirectedEdge(tree3, 1, 8); + addUndirectedEdge(tree3, 1, 6); + addUndirectedEdge(tree3, 6, 4); + addUndirectedEdge(tree3, 6, 5); + addUndirectedEdge(tree3, 5, 3); + addUndirectedEdge(tree3, 5, 7); + addUndirectedEdge(tree3, 7, 2); + addUndirectedEdge(tree3, 2, 9); + + assertThat(treesAreIsomorphic(tree1, tree2)).isEqualTo(false); + assertThat(treesAreIsomorphic(tree1, tree3)).isEqualTo(true); + assertThat(treesAreIsomorphic(tree2, tree3)).isEqualTo(false); + } + + @Test + public void simpleTest() { + List> tree1 = createEmptyGraph(5); + List> tree2 = createEmptyGraph(5); + + addUndirectedEdge(tree1, 2, 0); + addUndirectedEdge(tree1, 3, 4); + addUndirectedEdge(tree1, 2, 1); + addUndirectedEdge(tree1, 2, 3); + + addUndirectedEdge(tree2, 1, 0); + addUndirectedEdge(tree2, 2, 4); + addUndirectedEdge(tree2, 1, 3); + addUndirectedEdge(tree2, 1, 2); + + assertThat(treesAreIsomorphic(tree1, tree2)).isEqualTo(true); + } + + @Test + public void differentNumberOfNodes() { + List> tree1 = createEmptyGraph(2); + List> tree2 = createEmptyGraph(3); + + addUndirectedEdge(tree1, 0, 1); + + addUndirectedEdge(tree2, 0, 1); + addUndirectedEdge(tree2, 1, 2); + + assertThat(treesAreIsomorphic(tree1, tree2)).isEqualTo(false); + } + + @Test + public void testIsomorphismEquivilanceAgainstOtherImpl() { + for (int n = 1; n < 50; n++) { + for (int loops = 0; loops < 1000; loops++) { + List> tree1 = generateRandomTree(n); + List> tree2 = generateRandomTree(n); + + boolean impl1 = treesAreIsomorphic(tree1, tree2); + boolean impl2 = + com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphismWithBfs + .treesAreIsomorphic(tree1, tree2); + if (impl1 != impl2) { + System.err.println("TreeIsomorphism algorithms disagree!"); + System.err.println(tree1); + System.err.println(tree2); + } + assertThat(impl1).isEqualTo(impl2); + } + } + } + + public static List> generateRandomTree(int n) { + List nodes = new ArrayList<>(); + nodes.add(0); + + List> g = createEmptyGraph(n); + for (int nextNode = 1; nodes.size() != n; nextNode++) { + int randomNode = nodes.get((int) (Math.random() * nodes.size())); + addUndirectedEdge(g, randomNode, nextNode); + nodes.add(nextNode); + } + return g; + } +} diff --git a/javatests/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCanonicalFormAdjacencyListTest.java b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismWithBfsTest.java similarity index 60% rename from javatests/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCanonicalFormAdjacencyListTest.java rename to src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismWithBfsTest.java index cca6d5a91..02453af34 100644 --- a/javatests/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeCanonicalFormAdjacencyListTest.java +++ b/src/test/java/com/williamfiset/algorithms/graphtheory/treealgorithms/TreeIsomorphismWithBfsTest.java @@ -1,35 +1,38 @@ -package javatests.com.williamfiset.algorithms.graphtheory.treealgorithms; +// To run this test in isolation from root folder: +// +// $ gradle test --tests +// com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphismWithBfsTest + +package com.williamfiset.algorithms.graphtheory.treealgorithms; import static com.google.common.truth.Truth.assertThat; -import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCanonicalFormAdjacencyList.addUndirectedEdge; -import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCanonicalFormAdjacencyList.createEmptyTree; -import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeCanonicalFormAdjacencyList.encodeTree; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphism.TreeNode; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphismWithBfs.addUndirectedEdge; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphismWithBfs.createEmptyTree; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphismWithBfs.encodeTree; +import static com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphismWithBfs.treesAreIsomorphic; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; -public class TreeCanonicalFormAdjacencyListTest { +public class TreeIsomorphismWithBfsTest { @Test public void testSingleton() { List> tree1 = createEmptyTree(1); List> tree2 = createEmptyTree(1); - String encoding1 = encodeTree(tree1); - String encoding2 = encodeTree(tree2); - assertThat(encoding1).isEqualTo(encoding2); - assertThat(encoding1).isEqualTo("()"); + assertThat(treesAreIsomorphic(tree1, tree2)).isEqualTo(true); } @Test public void testTwoNodeTree() { List> tree1 = createEmptyTree(2); List> tree2 = createEmptyTree(2); + addUndirectedEdge(tree1, 0, 1); addUndirectedEdge(tree2, 1, 0); - String encoding1 = encodeTree(tree1); - String encoding2 = encodeTree(tree2); - assertThat(encoding1).isEqualTo(encoding2); - assertThat(encoding1).isEqualTo("()()"); + + assertThat(treesAreIsomorphic(tree1, tree2)).isEqualTo(true); } @Test @@ -47,10 +50,7 @@ public void testSmall() { addUndirectedEdge(tree2, 1, 2); addUndirectedEdge(tree2, 2, 4); - String encoding1 = encodeTree(tree1); - String encoding2 = encodeTree(tree2); - assertThat(encoding1).isEqualTo(encoding2); - assertThat(encoding1).isEqualTo("(()())(())"); + assertThat(treesAreIsomorphic(tree1, tree2)).isEqualTo(true); } @Test @@ -91,12 +91,9 @@ public void testSimilarChains() { addUndirectedEdge(tree3, 7, 2); addUndirectedEdge(tree3, 2, 9); - String encoding1 = encodeTree(tree1); - String encoding2 = encodeTree(tree2); - String encoding3 = encodeTree(tree3); - assertThat(encoding1).isNotEqualTo(encoding2); - assertThat(encoding1).isEqualTo(encoding3); - assertThat(encoding2).isNotEqualTo(encoding3); + assertThat(treesAreIsomorphic(tree1, tree2)).isEqualTo(false); + assertThat(treesAreIsomorphic(tree1, tree3)).isEqualTo(true); + assertThat(treesAreIsomorphic(tree2, tree3)).isEqualTo(false); } @Test @@ -128,4 +125,52 @@ public void testSlidesExample() { String expectedEncoding = "(((()())(()())())((())()()())(()()()))"; assertThat(treeEncoding).isEqualTo(expectedEncoding); } + + @Test + public void t() { + List> tree = createEmptyTree(10); + + TreeNode node0 = new TreeNode(0); + TreeNode node1 = new TreeNode(1); + TreeNode node2 = new TreeNode(2); + TreeNode node3 = new TreeNode(3); + TreeNode node4 = new TreeNode(4); + TreeNode node5 = new TreeNode(5); + TreeNode node6 = new TreeNode(6); + TreeNode node7 = new TreeNode(7); + TreeNode node8 = new TreeNode(8); + TreeNode node9 = new TreeNode(9); + + node0.addChildren(node1, node2, node3); + node1.addChildren(node4, node5); + node5.addChildren(node9); + node2.addChildren(node6, node7); + node3.addChildren(node8); + + // TODO(william): finish this test to check for "(((())())(()())(()))" encoding + // System.out.println( + // com.williamfiset.algorithms.graphtheory.treealgorithms.TreeIsomorphism.encode(node0)); + + // (((())())(()())(())) + // ((())()) + // (()()) + // (()) + // + + // (()()) + // (()) + // (()) + + // ((()())(())) + // ((())()) + // + // ((()())(()))((())()) + + // (((()())(()))((())())) + // (()()) + // (()) + // + // ((())()) + // + } } diff --git a/javatests/com/williamfiset/algorithms/other/BitManipulationsTest.java b/src/test/java/com/williamfiset/algorithms/other/BitManipulationsTest.java similarity index 91% rename from javatests/com/williamfiset/algorithms/other/BitManipulationsTest.java rename to src/test/java/com/williamfiset/algorithms/other/BitManipulationsTest.java index 937a1fbb6..74c6930d5 100644 --- a/javatests/com/williamfiset/algorithms/other/BitManipulationsTest.java +++ b/src/test/java/com/williamfiset/algorithms/other/BitManipulationsTest.java @@ -1,9 +1,8 @@ -package javatests.com.williamfiset.algorithms.other; +package com.williamfiset.algorithms.other; import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.other.BitManipulations; -import org.junit.*; +import org.junit.jupiter.api.*; public class BitManipulationsTest { diff --git a/src/test/java/com/williamfiset/algorithms/other/LazyRangeAdderTest.java b/src/test/java/com/williamfiset/algorithms/other/LazyRangeAdderTest.java new file mode 100644 index 000000000..357a7bbfd --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/other/LazyRangeAdderTest.java @@ -0,0 +1,80 @@ +package com.williamfiset.algorithms.other; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.jupiter.api.Test; + +public class LazyRangeAdderTest { + + @Test + public void rangeUpdateTest1() { + int[] a = {10, 5, 20, 40}; + LazyRangeAdder lazyRangeAdder = new LazyRangeAdder(a); + lazyRangeAdder.add(0, 1, 10); + lazyRangeAdder.add(1, 3, 20); + lazyRangeAdder.add(2, 2, 30); + lazyRangeAdder.done(); + int[] expected = {20, 35, 70, 60}; + assertThat(a).isEqualTo(expected); + } + + @Test + public void rangeUpdateTest2() { + int[] a = {270, 311, 427, 535, 334, 193, 174}; + LazyRangeAdder lazyRangeAdder = new LazyRangeAdder(a); + lazyRangeAdder.add(2, 5, 32); + lazyRangeAdder.add(0, 4, 101); + lazyRangeAdder.add(5, 6, -73); + lazyRangeAdder.done(); + int[] expected = {371, 412, 560, 668, 467, 152, 101}; + assertThat(a).isEqualTo(expected); + } + + @Test + public void randomRangeAdditionTests() { + // Try several different array sizes + for (int n = 1; n < 1000; n++) { + + int[] arr1 = new int[n]; + randomFill(arr1); + int[] arr2 = arr1.clone(); + + LazyRangeAdder lazyRangeAdder = new LazyRangeAdder(arr1); + + // Do 50 random range adds + for (int i = 0; i < 50; i++) { + // Generate a random range + int l = randValue(0, n); + int r = randValue(0, n); + l = Math.min(l, r); + r = Math.max(l, r); + + int x = randValue(-100, 100); + lazyRangeAdder.add(l, r, x); + slowRangeAdd(arr2, l, r, x); + } + + lazyRangeAdder.done(); + + assertThat(arr1).isEqualTo(arr2); + } + } + + // Adds `x` to the range [l, r] in arr + private static void slowRangeAdd(int[] arr, int l, int r, int x) { + for (int i = l; i <= r; i++) { + arr[i] += x; + } + } + + private static void randomFill(int[] arr) { + for (int i = 0; i < arr.length; i++) { + arr[i] = randValue(0, 1000); + } + } + + // Generates a random number between [min, max) + private static int randValue(int min, int max) { + return min + (int) (Math.random() * ((max - min))); + } +} diff --git a/javatests/com/williamfiset/algorithms/other/SlidingWindowMaximumTest.java b/src/test/java/com/williamfiset/algorithms/other/SlidingWindowMaximumTest.java similarity index 70% rename from javatests/com/williamfiset/algorithms/other/SlidingWindowMaximumTest.java rename to src/test/java/com/williamfiset/algorithms/other/SlidingWindowMaximumTest.java index 2734009e4..dc91ee761 100644 --- a/javatests/com/williamfiset/algorithms/other/SlidingWindowMaximumTest.java +++ b/src/test/java/com/williamfiset/algorithms/other/SlidingWindowMaximumTest.java @@ -1,10 +1,8 @@ -package javatests.com.williamfiset.algorithms.other; +package com.williamfiset.algorithms.other; -import static org.junit.Assert.*; +import static com.google.common.truth.Truth.assertThat; -import com.williamfiset.algorithms.other.SlidingWindowMaximum; -import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class SlidingWindowMaximumTest { @@ -17,27 +15,27 @@ public void smallWindowTest() { SlidingWindowMaximum w = new SlidingWindowMaximum(values); w.advance(); - assertEquals(1, w.getMax()); + assertThat(w.getMax()).isEqualTo(1); w.advance(); - assertEquals(2, w.getMax()); + assertThat(w.getMax()).isEqualTo(2); w.advance(); - assertEquals(2, w.getMax()); + assertThat(w.getMax()).isEqualTo(2); w.shrink(); - assertEquals(2, w.getMax()); + assertThat(w.getMax()).isEqualTo(2); w.shrink(); - assertEquals(1, w.getMax()); + assertThat(w.getMax()).isEqualTo(1); w.advance(); - assertEquals(3, w.getMax()); + assertThat(w.getMax()).isEqualTo(3); w.advance(); - assertEquals(3, w.getMax()); + assertThat(w.getMax()).isEqualTo(3); w.advance(); - assertEquals(4, w.getMax()); + assertThat(w.getMax()).isEqualTo(4); w.shrink(); - assertEquals(4, w.getMax()); + assertThat(w.getMax()).isEqualTo(4); w.shrink(); - assertEquals(4, w.getMax()); + assertThat(w.getMax()).isEqualTo(4); w.shrink(); - assertEquals(4, w.getMax()); + assertThat(w.getMax()).isEqualTo(4); } @Test @@ -87,7 +85,7 @@ public static void randomizedTest(int n) { int max = Integer.MIN_VALUE; for (int i = lo; i < hi; i++) max = Math.max(max, ar[i]); - assertEquals(max, window.getMax()); + assertThat(window.getMax()).isEqualTo(max); } } } diff --git a/src/test/java/com/williamfiset/algorithms/search/InterpolationSearchTest.java b/src/test/java/com/williamfiset/algorithms/search/InterpolationSearchTest.java new file mode 100644 index 000000000..7aa74eed0 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/search/InterpolationSearchTest.java @@ -0,0 +1,23 @@ +package com.williamfiset.algorithms.search; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.*; + +public class InterpolationSearchTest { + + private static Arguments[] inputs() { + return new Arguments[] { + Arguments.of(2, 2), Arguments.of(5, 5), Arguments.of(-1, -1), Arguments.of(8, -1) + }; + } + + @ParameterizedTest(name = "Search value: {0}, Expected index: {1}") + @MethodSource("inputs") + public void testCoverage(int val, int expected) { + int[] arr = {0, 1, 2, 3, 4, 5}; + int index = InterpolationSearch.interpolationSearch(arr, val); + assertThat(index).isEqualTo(expected); + } +} diff --git a/src/test/java/com/williamfiset/algorithms/sorting/QuickSelectTest.java b/src/test/java/com/williamfiset/algorithms/sorting/QuickSelectTest.java new file mode 100644 index 000000000..80d51905b --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/sorting/QuickSelectTest.java @@ -0,0 +1,30 @@ +package com.williamfiset.algorithms.sorting; + +import static com.google.common.truth.Truth.assertThat; + +import com.williamfiset.algorithms.utils.TestUtils; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +public class QuickSelectTest { + + @Test + public void testQuickSelect() { + for (int size = 1; size < 500; size++) { + // Given + QuickSelect quickSelect = new QuickSelect(); + int[] values = TestUtils.randomIntegerArray(size, -100, 100); + + for (int k = 1; k <= size; k++) { + int[] copy = values.clone(); + Arrays.sort(values); + + // When + int kthLargestElement = quickSelect.quickSelect(copy, k); + + // Then + assertThat(kthLargestElement).isEqualTo(values[k - 1]); + } + } + } +} diff --git a/src/test/java/com/williamfiset/algorithms/sorting/RadixSortTest.java b/src/test/java/com/williamfiset/algorithms/sorting/RadixSortTest.java new file mode 100644 index 000000000..9f1f0cf37 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/sorting/RadixSortTest.java @@ -0,0 +1,60 @@ +package com.williamfiset.algorithms.sorting; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.Arrays; +import java.util.Random; +import org.junit.jupiter.api.Test; + +public class RadixSortTest { + static Random random = new Random(); + + @Test + public void testGetMax() { + int[] array = {5, 7, 1, 13, 1013, 287, 912}; + assertThat(RadixSort.getMax(array)).isEqualTo(1013); + } + + @Test + public void testCalculateNumberOfDigits() { + assertThat(RadixSort.calculateNumberOfDigits(1089)).isEqualTo(4); + assertThat(RadixSort.calculateNumberOfDigits(19)).isEqualTo(2); + } + + @Test + public void randomRadixSort_smallNumbers() { + for (int size = 0; size < 1000; size++) { + int[] values = new int[size]; + for (int i = 0; i < size; i++) { + values[i] = randInt(1, 50); + } + int[] copy = values.clone(); + + Arrays.sort(values); + RadixSort.radixSort(copy); + + assertThat(values).isEqualTo(copy); + } + } + + @Test + public void randomRadixSort_largeNumbers() { + for (int size = 0; size < 1000; size++) { + int[] values = new int[size]; + for (int i = 0; i < size; i++) { + values[i] = randInt(1, Integer.MAX_VALUE); + } + int[] copy = values.clone(); + + Arrays.sort(values); + RadixSort.radixSort(copy); + + assertThat(values).isEqualTo(copy); + } + } + + // return a random number between [min, max] + static int randInt(int min, int max) { + return random.nextInt((max - min) + 1) + min; + } +} diff --git a/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java b/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java new file mode 100644 index 000000000..94a5765f6 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/sorting/SortingTest.java @@ -0,0 +1,90 @@ +package com.williamfiset.algorithms.sorting; + +import static com.google.common.truth.Truth.assertThat; + +import com.williamfiset.algorithms.utils.TestUtils; +import java.util.Arrays; +import java.util.EnumSet; +import org.junit.jupiter.api.Test; + +// Test all sorting algorithms under various constraints. +// +// Not all sorting algorithms are suitable for every type for test. For instance, +// counting sort cannot sort a range of numbers between [Integer.MIN_VALUE, +// Integer.MAX_VALUE] without running out of memory. Similarly, radix sort +// doesn't play well with negative numbers, etc.. +public class SortingTest { + + enum SortingAlgorithm { + BUBBLE_SORT(new BubbleSort()), + BUCKET_SORT(new BucketSort()), + COUNTING_SORT(new CountingSort()), + HEAP_SORT(new Heapsort()), + INSERTION_SORT(new InsertionSort()), + MERGE_SORT(new MergeSort()), + QUICK_SORT(new QuickSort()), + QUICK_SORT3(new QuickSort3()), + RADIX_SORT(new RadixSort()), + SELECTION_SORT(new SelectionSort()); + + private InplaceSort algorithm; + + SortingAlgorithm(InplaceSort algorithm) { + this.algorithm = algorithm; + } + + public InplaceSort getSortingAlgorithm() { + return algorithm; + } + } + + private static final EnumSet sortingAlgorithms = + EnumSet.of( + SortingAlgorithm.BUBBLE_SORT, + SortingAlgorithm.BUCKET_SORT, + SortingAlgorithm.COUNTING_SORT, + SortingAlgorithm.HEAP_SORT, + SortingAlgorithm.INSERTION_SORT, + SortingAlgorithm.MERGE_SORT, + SortingAlgorithm.QUICK_SORT, + SortingAlgorithm.QUICK_SORT3, + SortingAlgorithm.RADIX_SORT, + SortingAlgorithm.SELECTION_SORT); + + @Test + public void verifySortingAlgorithms_smallPositiveIntegersOnly() { + for (int size = 0; size < 1000; size++) { + for (SortingAlgorithm algorithm : sortingAlgorithms) { + InplaceSort sorter = algorithm.getSortingAlgorithm(); + int[] values = TestUtils.randomIntegerArray(size, 0, 51); + int[] copy = values.clone(); + + Arrays.sort(values); + sorter.sort(copy); + + assertThat(values).isEqualTo(copy); + } + } + } + + @Test + public void verifySortingAlgorithms_smallNegativeIntegersOnly() { + for (int size = 0; size < 1000; size++) { + for (SortingAlgorithm algorithm : sortingAlgorithms) { + // Skip radix sort since our implementation doesn't handle negative + // numbers. + if (algorithm == SortingAlgorithm.RADIX_SORT) { + continue; + } + InplaceSort sorter = algorithm.getSortingAlgorithm(); + int[] values = TestUtils.randomIntegerArray(size, -50, 51); + int[] copy = values.clone(); + + Arrays.sort(values); + sorter.sort(copy); + + assertThat(values).isEqualTo(copy); + } + } + } +} diff --git a/src/test/java/com/williamfiset/algorithms/strings/BoyerMooreStringSearchTest.java b/src/test/java/com/williamfiset/algorithms/strings/BoyerMooreStringSearchTest.java new file mode 100644 index 000000000..788121c63 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/strings/BoyerMooreStringSearchTest.java @@ -0,0 +1,120 @@ +package com.williamfiset.algorithms.strings; + +import static com.google.common.truth.Truth.assertThat; +import static java.util.Objects.isNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import org.junit.jupiter.api.*; + +public class BoyerMooreStringSearchTest { + + private BoyerMooreStringSearch underTest; + private Random random; + private final int MAX_ITERATION = 20; + + @BeforeEach + public void setup() { + underTest = new BoyerMooreStringSearch(); + random = new Random(); + } + + @Test + public void shouldReturnEmptyListOnNullInput() { + assertThat(underTest.findOccurrences(null, "")).isEmpty(); + assertThat(underTest.findOccurrences("", null)).isEmpty(); + assertThat(underTest.findOccurrences(null, null)).isEmpty(); + } + + @Test + public void shouldReturnOneOccurrence() { + assertThat( + underTest.findOccurrences( + "Sample text for testing the Boyer-Moore algorithm.", "Sample")) + .containsExactly(0); + assertThat( + underTest.findOccurrences("Sample text for testing the Boyer-Moore algorithm.", "for")) + .containsExactly(12); + assertThat(underTest.findOccurrences("Sample text for testing the Boyer-Moore algorithm.", ".")) + .containsExactly(49); + } + + @Test + public void shouldReturnMultipleOccurrences() { + assertThat(underTest.findOccurrences("SAAT TE", "TE")).containsExactly(5); + assertThat( + underTest.findOccurrences("Sample text for testing the Boyer-Moore algorithm.", "te")) + .containsExactly(7, 16); + assertThat(underTest.findOccurrences("Sample text for testing the Boyer-Moore algorithm.", " ")) + .containsExactly(6, 11, 15, 23, 27, 39); + assertThat(underTest.findOccurrences("AABAACAADAABAABA", "AABA")).containsExactly(0, 9, 12); + assertThat(underTest.findOccurrences("AAAAAAA", "AA")).containsExactly(0, 1, 2, 3, 4, 5); + } + + @Test + public void shouldReturnEmptyForPatternLengthLargerThenText() { + assertThat(underTest.findOccurrences("This is a Test Text", "This is a test Pattern")) + .isEmpty(); + } + + @Test + public void shouldReturnDynamicString() { + int runLength = random.nextInt(MAX_ITERATION) + 1; + for (int run = 0; run < runLength; run++) { + int upperCharText = random.nextInt(3); + int upperCharPattern = random.nextInt(3); + int maxLengthText = + random.nextInt(1000) + 100; // random length of the string between [100=1000] + int maxLengthPattern = random.nextInt(10); + String text = generateRandomString(upperCharText, maxLengthText); + String pattern = generateRandomString(upperCharPattern, maxLengthPattern); + assertThat(underTest.findOccurrences(text, pattern)) + .containsExactlyElementsIn(getOccurrencesBruteForce(text, pattern)); + } + } + + /** + * @param text the text being searched in + * @param pattern the pattern that needs to be searched in text + * @return a list of beginning index of text where pattern exits + */ + private List getOccurrencesBruteForce(String text, String pattern) { + if (isNull(text) + || isNull(pattern) + || text.length() < pattern.length() + || pattern.length() == 0) { + return new ArrayList<>(); + } + List occurrences = new ArrayList<>(); + for (int textIndex = 0; textIndex < text.length() - pattern.length() + 1; textIndex++) { + boolean match = true; + for (int patIndex = 0; patIndex < pattern.length(); patIndex++) { + if (text.charAt(textIndex + patIndex) != pattern.charAt(patIndex)) { + match = false; + break; + } + } + if (match) { + occurrences.add(textIndex); + } + } + return occurrences; + } + + /** + * @param upperLimitAscii Largest element in the random string + * @param length Length of the random string + * @return Returns a random string containing character between [a-z] + */ + private String generateRandomString(int upperLimitAscii, int length) { + return random + .ints('a', 'a' + upperLimitAscii + 1) + .limit(length) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); + } + + // TODO(william): Add a test that compares this implementation of Boyermoore + // with that of KMP. +} diff --git a/javatests/com/williamfiset/algorithms/strings/LongestCommonSubstringTest.java b/src/test/java/com/williamfiset/algorithms/strings/LongestCommonSubstringTest.java similarity index 92% rename from javatests/com/williamfiset/algorithms/strings/LongestCommonSubstringTest.java rename to src/test/java/com/williamfiset/algorithms/strings/LongestCommonSubstringTest.java index fa2482cdf..ebee4d96a 100644 --- a/javatests/com/williamfiset/algorithms/strings/LongestCommonSubstringTest.java +++ b/src/test/java/com/williamfiset/algorithms/strings/LongestCommonSubstringTest.java @@ -1,18 +1,15 @@ /** - * Run like: $ gradle test --tests - * "javatests.com.williamfiset.algorithms.strings.LongestCommonSubstringTest" + * Run like: $ gradle test --tests "com.williamfiset.algorithms.strings.LongestCommonSubstringTest" */ -package javatests.com.williamfiset.algorithms.strings; +package com.williamfiset.algorithms.strings; -// TODO(williamfiset): Replace junit asserts with all Google truth assertions. import static com.google.common.truth.Truth.assertThat; import static com.williamfiset.algorithms.strings.LongestCommonSubstring.LcsSolver; -import static org.junit.Assert.*; import com.google.common.collect.ImmutableList; import com.williamfiset.algorithms.utils.TestUtils; import java.util.*; -import org.junit.*; +import org.junit.jupiter.api.*; public class LongestCommonSubstringTest { @@ -74,7 +71,7 @@ public void verifyMultipleKValues(String[] strings, Map LcsSolver solver = new LcsSolver(strings); TreeSet lcss = solver.getLongestCommonSubstrings(k); - assertEquals(expectedLcss, lcss); + assertThat(expectedLcss).isEqualTo(lcss); } } @@ -147,7 +144,7 @@ public void noLongestCommonSubstringTest() { LcsSolver solver = new LcsSolver(strs); TreeSet lcss = solver.getLongestCommonSubstrings(k); - assertEquals(ans.size(), lcss.size()); + assertThat(ans.size()).isEqualTo(lcss.size()); } @Test @@ -161,7 +158,7 @@ public void simple1() { LcsSolver solver = new LcsSolver(strs); TreeSet lcss = solver.getLongestCommonSubstrings(k); - assertEquals(ans, lcss); + assertThat(ans).isEqualTo(lcss); } @Test @@ -174,7 +171,7 @@ public void simple2() { LcsSolver solver = new LcsSolver(strs); TreeSet lcss = solver.getLongestCommonSubstrings(k); - assertEquals(ans, lcss); + assertThat(ans).isEqualTo(lcss); } @Test @@ -190,7 +187,7 @@ public void simple3() { LcsSolver solver = new LcsSolver(strs); TreeSet lcss = solver.getLongestCommonSubstrings(k); - assertEquals(ans, lcss); + assertThat(ans).isEqualTo(lcss); } @Test @@ -208,7 +205,7 @@ public void simple4() { LcsSolver solver = new LcsSolver(strs); TreeSet lcss = solver.getLongestCommonSubstrings(k); - assertEquals(ans, lcss); + assertThat(ans).isEqualTo(lcss); } @Test @@ -221,7 +218,7 @@ public void simple5() { LcsSolver solver = new LcsSolver(strs); TreeSet lcss = solver.getLongestCommonSubstrings(k); - assertEquals(ans, lcss); + assertThat(ans).isEqualTo(lcss); } @Test @@ -237,7 +234,7 @@ public void kValueTest() { LcsSolver solver = new LcsSolver(strs); TreeSet lcss = solver.getLongestCommonSubstrings(k); - assertEquals(ans, lcss); + assertThat(ans).isEqualTo(lcss); } @Test @@ -254,7 +251,7 @@ public void kValueTest2() { LcsSolver solver = new LcsSolver(strs); TreeSet lcss = solver.getLongestCommonSubstrings(k); - assertEquals(ans, lcss); + assertThat(ans).isEqualTo(lcss); } @Test @@ -270,7 +267,7 @@ public void kValueTest3() { LcsSolver solver = new LcsSolver(strs); TreeSet lcss = solver.getLongestCommonSubstrings(k); - assertEquals(ans, lcss); + assertThat(ans).isEqualTo(lcss); } @Test @@ -286,7 +283,7 @@ public void kValueTest4() { LcsSolver solver = new LcsSolver(strs); TreeSet lcss = solver.getLongestCommonSubstrings(k); - assertEquals(ans, lcss); + assertThat(ans).isEqualTo(lcss); } @Test @@ -306,12 +303,12 @@ public void smallStrings() { LcsSolver solver = new LcsSolver(strs); TreeSet lcss = solver.getLongestCommonSubstrings(k); - assertEquals(ans, lcss); + assertThat(ans).isEqualTo(lcss); } @Test public void randomLcssWithBruteForceSolver1() { - for (int len = 2; len < 50; len++) { + for (int len = 2; len < 20; len++) { String[] strings = createRandomStrings(len, 12, 20, 3); for (int k = 2; k <= len; k++) { LcsSolver solver = new LcsSolver(strings); @@ -343,7 +340,7 @@ public void randomLcssWithBruteForceSolver2() { @Test public void randomLcssWithBruteForceSolver3() { - for (int len = 2; len < 100; len++) { + for (int len = 2; len < 10; len++) { String[] strings = createRandomStrings(len, 6, 10, 15); for (int k = 2; k <= len; k++) { LcsSolver solver = new LcsSolver(strings); @@ -376,7 +373,7 @@ static String createString(int minSz, int maxSz, int alphabetSize) { // TODO(williamfiset): crank up the numbers once implementation is faster. @Test public void testLargeAlphabet() { - for (int k = 2; k <= 100; k++) { + for (int k = 2; k <= 10; k++) { String[] strs = new String[k]; for (int i = 0; i < k; i++) strs[i] = "ABABAB"; @@ -385,7 +382,7 @@ public void testLargeAlphabet() { LcsSolver solver = new LcsSolver(strs); TreeSet lcss = solver.getLongestCommonSubstrings(k); - assertEquals(ans, lcss); + assertThat(ans).isEqualTo(lcss); } } } diff --git a/src/test/java/com/williamfiset/algorithms/strings/ZAlgorithmTest.java b/src/test/java/com/williamfiset/algorithms/strings/ZAlgorithmTest.java new file mode 100644 index 000000000..c8dcd0f28 --- /dev/null +++ b/src/test/java/com/williamfiset/algorithms/strings/ZAlgorithmTest.java @@ -0,0 +1,39 @@ +package com.williamfiset.algorithms.strings; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.jupiter.api.*; + +public class ZAlgorithmTest { + private ZAlgorithm underTest; + + @BeforeEach + public void setup() { + underTest = new ZAlgorithm(); + } + + @Test + public void shouldReturnEmptyArrayOnNullOrEmptyInput() { + assertThat(underTest.calculateZ(null)).isEmpty(); + assertThat(underTest.calculateZ("")).isEmpty(); + } + + @Test + public void textContainsASingleCharacterRepeated() { + assertThat(underTest.calculateZ("aaaaaaa")).isEqualTo(new int[] {7, 6, 5, 4, 3, 2, 1}); + assertThat(underTest.calculateZ("bbbbbbbb")).isEqualTo(new int[] {8, 7, 6, 5, 4, 3, 2, 1}); + } + + @Test + public void textContainsAllDistinctCharacters() { + assertThat(underTest.calculateZ("abcdefgh")).isEqualTo(new int[] {8, 0, 0, 0, 0, 0, 0, 0}); + } + + @Test + public void textContainsRepeatedPattern() { + assertThat(underTest.calculateZ("abababab")).isEqualTo(new int[] {8, 0, 6, 0, 4, 0, 2, 0}); + assertThat(underTest.calculateZ("ababababa")).isEqualTo(new int[] {9, 0, 7, 0, 5, 0, 3, 0, 1}); + assertThat(underTest.calculateZ("abcabcabca")) + .isEqualTo(new int[] {10, 0, 0, 7, 0, 0, 4, 0, 0, 1}); + } +}