[Git][java-team/jheaps][upstream] New upstream version 0.16
Pierre Gruet (@pgt)
gitlab at salsa.debian.org
Tue Sep 8 19:50:18 BST 2026
Pierre Gruet pushed to branch upstream at Debian Java Maintainers / jheaps
Commits:
bf308e46 by Pierre Gruet at 2026-09-08T20:33:33+02:00
New upstream version 0.16
- - - - -
15 changed files:
- + .github/workflows/master.yaml
- + KEYS
- README.md
- + etc/release.md
- pom.xml
- + src/main/java/org/jheaps/tree/PurePairingHeap.java
- + src/main/java/org/jheaps/tree/StrictFibonacciHeap.java
- src/test/java/org/jheaps/tree/AbstractAddressableHeapTest.java
- src/test/java/org/jheaps/tree/AbstractMergeableAddressableHeapTest.java
- src/test/java/org/jheaps/tree/BinaryTreeSoftHeapTest.java
- + src/test/java/org/jheaps/tree/PurePairingHeapAddressableHeapTest.java
- + src/test/java/org/jheaps/tree/PurePairingHeapMergeableAddressableHeapTest.java
- + src/test/java/org/jheaps/tree/StrictFibonacciHeapAddressableHeapTest.java
- + src/test/java/org/jheaps/tree/StrictFibonacciHeapMergeableAddressableHeapTest.java
- + src/test/java/org/jheaps/tree/StrictFibonacciHeapValidationTest.java
Changes:
=====================================
.github/workflows/master.yaml
=====================================
@@ -0,0 +1,31 @@
+name: JHeaps
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ types: [opened, synchronize, reopened]
+ branches:
+ - master
+
+jobs:
+ build:
+ name: Build (JDK ${{ matrix.java }})
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ strategy:
+ fail-fast: false
+ matrix:
+ java: [8, 11, 17, 21, 25]
+ steps:
+ - uses: actions/checkout at v6
+ - name: Set up JDK ${{ matrix.java }}
+ uses: actions/setup-java at v5
+ with:
+ distribution: temurin
+ java-version: ${{ matrix.java }}
+ cache: maven
+ - name: Build with Maven
+ run: mvn --batch-mode --show-version verify -Pcheckstyle
=====================================
KEYS
=====================================
@@ -0,0 +1,62 @@
+JHeaps release signing public keys
+
+Historical release key
+Fingerprint: 5F459557BD5F70C12DF0BB91274E4517777B86B2
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1
+
+mQMuBFg0CZIRCACr341Ixy3n9P/EeQucxUsQ+xSHd4djV6I4AhvFWsd9Xqn6ycth
+3Q5RcblRozSmic+7cA+6KSYSoeuXrbWw0D3puJA5/ixxZPCLlNG4z7iDWzYQVbWG
+Q32qpXMzQkNN9hCOXmxN7fxNj/bEHQ3mednNTGzl0C9pIcflo88G2YEcj+K+swxb
+RNVKIHQODrVHrGbfIs/uHwluVPCmn+QOv67NGjyb6POzKS937J5i4ZH7uU8muwaa
+RMIoFoT7hXCUxbNLqOCNXBdR3Ao8OOi5cifk86vw9HpF3m34Lr0qghYGecvWdvJT
+JRD46SArhtkG70SFJdeb9NcNDTxdT7bw/kuPAQCumaG6fzBDfmvnST9ta/ZGCA28
+w/8xAMwGlUy0rUyczwf9GLAR3IWSUCxaGb2BmSc6MD17vAUlKnA+XNu2C7c+KTlp
+jX8eFiaBc1Jn9dY4atqh5MuE/n+wwuFYLCSGWjI3Y+V+9XTu4nn5ImRbMFBzQF+E
+1FAmxFalytvl2sGN3SeIr8Msgac1s+9X8/gkNWkTkTKtTsCfV6PIEzElpeK2PNFP
+JSGvRDn4SLzVF6BRpxbjnQfbgSWCNY76Uhxq5rAXzLebXqAlMM87nucRT2itZOy7
+IZqzhhacYWUKb5Z4YNlPfIglvpUKacl48Ct+j9kgez/hDxRPbt1oVKaEbVz3JvFr
+TMe3rg81Sm/8Rhhky+3SKjcQ4teaD2+RuxUSKuEOLAf/YxXu4zg4HIt6dLTgJBAP
+UIoaml+a7l9n014Yb6yW91Wvs/DrtPQeXe6VLgDfNgitRJyp4ynxvZagGCCHzjnt
+zj9C1XGg5yuBanY491QQqGqJl3rUcJ2g6Pk+kJApJB/F2spSjiEaMSUM5R4MPXG9
+7bI+wsbwpj91u2Z7sSqtMvjRsk1hY3tcXwxM003E2N7guVeYrQKEHF7o5vTm0wpJ
+1o4N7CD/X7GYez49JkOQdW6glUROTOJq+O3QJ340Iu0ERcxc4gAKGlwNjq/q8NWz
+4tAZMRs0RWCs0uYezqmwO9CjyEJXBDTvXP8bDbNHCtS8STKYGDcJYH25wxw0WSO1
+TbQvRGltaXRyaW9zIE1pY2hhaWwgPGRpbWl0cmlvcy5taWNoYWlsQGdtYWlsLmNv
+bT6IegQTEQgAIgUCWDQJkgIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQ
+J05FF3d7hrLZeQD9H7kU8JKa3wbaTbUmhO5mQhOWXDW0F28BZodU7Aj8DiAA/0nF
+KCeav9HOPR5q3iuJupVjEdRApaclKAan0kAfodeKuQINBFg0CZIQCADWL9NWC0qo
+wX/JbvbAq0ngUfJtH6ZVTKOf5jflKN0DtkVtv1kLlxEl3o4PC4kxY1K/YAVOECWE
+6Akxzp/seDYJ6BnP6c4RwUBAKagPZv4cPUZ7ews1mEHmzEaqZIp+zfoxS18UzIf6
+dWcY6lFRAeYjZZg5irLoPRTYST7hDYrhTcgXKw83R7rsFrkZLuah5CSeOD/sMRKy
+abCJQrDsszRA/hsdGlKXM9+9OFfsWZwfnr1j1CJIi1vfk8qSgI8gBgqarIX/7LvS
+cqJm352kZMOBzKDalJJUOujKQdl1MhqofDi80VQfW4juJLQmbQiKNmKogwWGdHKo
+HiDnz0CZ3m8nAAMFCACJ9wKLrl6/+7nofcsEPGeWJyZW/DGSFw7HVQbSfk12ILMl
+0a1kKoD4pToxfzgNwZ80806FcYfZ61I0Qb2wszw15r7SS3aFJ2ZOBymy0mDWGO4A
+F3kA8Cq6xOOeJ/iYdV5At0zklIjbajTxbgl41l0N8ziHUoY7qwjR4cRkXs5O45s/
+ErsHQHxnsijuSeNaY1XMpwlqWVhNYG3Gg1WoVaFQI+r5NUCvk9pD28Fh/AyFHVfS
+wf96WEQdFEAR2hmMVq94TZ/GUv/VTiIDas0n9Q0FhW04TI1mBg8NbSxdQVUxDECd
+gscA/II8uidwYFt7Xowa9G6mJvkvr0KGiU8lcQipiGEEGBEIAAkFAlg0CZICGwwA
+CgkQJ05FF3d7hrKClQD/f0rJTInvrj4tMrnToN2vVkA6TU7iwUFEWKQTXzE04AkB
+AIQQLnB9D/t6fwaEQDLJS5C4d49PtpxW4kPBB0WoxW+t
+=8Lvk
+-----END PGP PUBLIC KEY BLOCK-----
+
+Current release key
+Fingerprint: 378082934133270C0A2A3C849F3B2D205686F675
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEanIFGxYJKwYBBAHaRw8BAQdAYqs1Rxxb9qUoHp1aFlalZSs1OWgKEo59Ck/P
+RdiArQu0L0RpbWl0cmlvcyBNaWNoYWlsIDxkaW1pdHJpb3MubWljaGFpbEBnbWFp
+bC5jb20+iJkEExYKAEEWIQQ3gIKTQTMnDAoqPISfOy0gVob2dQUCanIFGwIbAwUJ
+EswDAAULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRCfOy0gVob2dW1YAQDo
+7ailJKH4++wDzeyS2sOhiCiH+fS+zQ0vvM3W9JWytwD9GK3rnW0p5TPQRADAGzq7
+TPtpFCUvNNskT5ghXUZAsAa4OARqcgUbEgorBgEEAZdVAQUBAQdAhRs9BlfYSrpL
+fjDEkx+TqfhHL8Dm0X1gh7PVsrZ0/RADAQgHiH4EGBYKACYWIQQ3gIKTQTMnDAoq
+PISfOy0gVob2dQUCanIFGwIbDAUJEswDAAAKCRCfOy0gVob2dZKAAPsHG7FSFu4e
+uc+QphVB75l2XExtN1tDOx0j2lbU35fO7AEAv3DjC+keEvt90ZJqZ4bCEaUOddcP
+6dTPQq2N5GPH1Qk=
+=2EoX
+-----END PGP PUBLIC KEY BLOCK-----
=====================================
README.md
=====================================
@@ -1,12 +1,14 @@
+[](https://github.com/d-michail/jheaps/actions/workflows/master.yaml)
+
# JHeaps Library
-Copyright (C) 2014-2020 Dimitrios Michail
+Copyright (C) 2014-2026 Dimitrios Michail
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
- http://www.apache.org/licenses/LICENSE-2.0
+ 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,
@@ -59,8 +61,99 @@ Some heaps are meldable, that is they efficiently support the union operation:
As a general rule, heaps using an array representation are not meldable.
+## Available Heaps
+
+The library contains an extensive collection of heap data structures such as:
+
+* Tree-based
+ * Fibonacci mergeable and addressable heaps
+ * Simple Fibonacci heaps
+ * Strict Fibonacci heaps
+ * Pairing mergeable and addressable heaps
+ * Pure variant of Pairing heaps
+ * Costless-meld variant of Pairing heaps
+ * Rank-Pairing (type-1) mergeable and addressable heaps
+ * Leftist mergeable and addressable heaps
+ * Explicit binary tree addressable heaps
+ * Binary tree soft heaps
+ * Skew heaps
+* Dag-based
+ * Hollow mergeable and addressable heaps
+* Double-ended mergeable and addressable heaps
+ * Reflected Fibonacci heaps
+ * Reflected Pairing heaps
+* Array-based
+ * Binary heaps
+ * Binary addressable heaps
+ * D-ary heaps
+ * D-ary addressable heaps
+ * Binary weak heaps
+ * Binary weak heaps supporting bulk insertion
+ * Highly optimized binary heaps for integer keys using the Wegener
+ bottom-up heuristic and sentinel values
+* Double-ended array-based
+ * Binary MinMax heaps
+* Monotone heaps
+ * Addressable radix heaps with double, long, int or BigInteger keys
+ * Non-addressable radix heaps with double, long, int or BigInteger keys
+
+## Cite
+
+If you use this library please cite the following paper:
+
+- D. Michail.
+ **JHeaps: An open-source library of priority queues.**
+ SoftwareX, 16:100869, 2021.
+ <i class="far fa-file-pdf"></i> [web](https://doi.org/10.1016/j.softx.2021.100869)
+
+```
+ at article{michail2021jheaps,
+ title={JHeaps: An open-source library of priority queues},
+ author={Michail, Dimitrios},
+ journal={SoftwareX},
+ volume={16},
+ pages={100869},
+ year={2021},
+ publisher={Elsevier},
+ doi={10.1016/j.softx.2021.100869},
+ url={https://doi.org/10.1016/j.softx.2021.100869},
+}
+```
+
+Citing software is just as important as citing any other important sources in your research.
+If you’re not sure whether or not to cite something, [Shouldacite](https://mr-c.github.io/shouldacite/index.html) can help
+you decide if you should.
+
## Compatibility
The library requires JDK v1.8 and above.
+## Artifact signatures
+
+Artifacts published to Maven Central are signed with an OpenPGP key. The
+trusted public keys are included in [KEYS](KEYS); compare the complete
+fingerprint reported by GnuPG with this table:
+
+| Releases | Signing key fingerprint |
+| --- | --- |
+| 0.14 | `5F459557BD5F70C12DF0BB91274E4517777B86B2` |
+| 0.15 and later | `378082934133270C0A2A3C849F3B2D205686F675` |
+
+To verify a downloaded artifact and its matching `.asc` signature:
+
+```shell
+gpg --import KEYS
+gpg --verify jheaps-0.15.jar.asc jheaps-0.15.jar
+```
+
+## Python Bindings
+
+We also provide Python bindings which compile the Java library into a native shared library using
+[GraalVM](https://www.graalvm.org/).
+The result is a native self-contained library with no dependency on the JVM! For more information
+see the following links:
+* <https://pypi.org/project/jheaps/>
+* <https://python-jheaps.readthedocs.io/en/latest/>
+* <https://github.com/d-michail/python-jheaps/>
+* <https://github.com/d-michail/jheaps-capi/>
=====================================
etc/release.md
=====================================
@@ -0,0 +1,52 @@
+# How to perform a release
+
+Releases are published through the Maven Central Publisher Portal. Sign in and
+open <https://central.sonatype.com/usertoken>, then:
+
+1. Select **Generate User Token**.
+2. Give the token a descriptive name and choose an expiration date.
+3. Save the generated username and password immediately. They cannot be
+ retrieved after the dialog is closed; generate a replacement if they are
+ lost.
+
+Add the generated credentials to `~/.m2/settings.xml`:
+
+```xml
+<settings>
+ <servers>
+ <server>
+ <id>central</id>
+ <username>token username</username>
+ <password>token password</password>
+ </server>
+ </servers>
+
+ <profiles>
+ <profile>
+ <id>jheaps-signing</id>
+ <properties>
+ <gpg.keyname>YOUR_KEY_FINGERPRINT</gpg.keyname>
+ </properties>
+ </profile>
+ </profiles>
+
+ <activeProfiles>
+ <activeProfile>jheaps-signing</activeProfile>
+ </activeProfiles>
+</settings>
+```
+
+Replace `YOUR_KEY_FINGERPRINT` with the fingerprint of the signing key available
+to GnuPG. Prime `gpg-agent` interactively before starting the Maven release.
+This keeps the passphrase out of the POM, Maven settings, environment, and shell
+history:
+
+```shell
+gpg --local-user YOUR_KEY_FINGERPRINT --sign </dev/null >/dev/null
+mvn release:prepare
+mvn release:perform
+```
+
+The release profile signs the artifacts and uploads the deployment bundle to
+the Central Publisher Portal. After validation succeeds, review and publish the
+deployment at <https://central.sonatype.com/publishing/deployments>.
=====================================
pom.xml
=====================================
@@ -3,45 +3,50 @@
<groupId>org.jheaps</groupId>
<artifactId>jheaps</artifactId>
- <version>0.14</version>
+ <version>0.16</version>
<packaging>jar</packaging>
<name>JHeaps</name>
<description>A free, production-ready, efficient Java library containing a collection of heap data-structures.</description>
- <url>http://www.jheaps.org</url>
+ <url>https://www.jheaps.org</url>
<licenses>
<license>
<name>Apache License, Version 2.0</name>
- <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+ <url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
<distribution>repo</distribution>
</license>
</licenses>
<properties>
- <java.version>1.8</java.version>
- <junit.version>4.13</junit.version>
- <checkstyle.version>8.33</checkstyle.version>
+ <java.version>8</java.version>
+ <junit.version>4.13.2</junit.version>
+ <checkstyle.version>8.41</checkstyle.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
- <maven-javadoc-plugin.version>3.2.0</maven-javadoc-plugin.version>
- <maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
- <maven-release-plugin.version>3.0.0-M1</maven-release-plugin.version>
- <maven-source-plugin.version>3.2.1</maven-source-plugin.version>
- <maven-deploy-plugin.version>3.0.0-M1</maven-deploy-plugin.version>
- <maven-checkstyle-plugin.version>3.1.1</maven-checkstyle-plugin.version>
- <maven-surefire-plugin.version>3.0.0-M5</maven-surefire-plugin.version>
- <maven-bundle-plugin.version>4.2.1</maven-bundle-plugin.version>
- <jacoco.version>0.8.5</jacoco.version>
+ <maven-clean-plugin.version>3.5.0</maven-clean-plugin.version>
+ <maven-compiler-plugin.version>3.15.0</maven-compiler-plugin.version>
+ <maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
+ <maven-javadoc-plugin.version>3.12.0</maven-javadoc-plugin.version>
+ <maven-jar-plugin.version>3.5.1</maven-jar-plugin.version>
+ <maven-install-plugin.version>3.1.4</maven-install-plugin.version>
+ <maven-release-plugin.version>3.3.1</maven-release-plugin.version>
+ <maven-source-plugin.version>3.4.0</maven-source-plugin.version>
+ <maven-deploy-plugin.version>3.1.4</maven-deploy-plugin.version>
+ <maven-checkstyle-plugin.version>3.6.0</maven-checkstyle-plugin.version>
+ <maven-surefire-plugin.version>3.5.5</maven-surefire-plugin.version>
+ <maven-bundle-plugin.version>5.1.9</maven-bundle-plugin.version>
+ <maven-gpg-plugin.version>3.2.8</maven-gpg-plugin.version>
+ <central-publishing-maven-plugin.version>0.11.0</central-publishing-maven-plugin.version>
+ <jacoco.version>0.8.14</jacoco.version>
</properties>
<scm>
<connection>scm:git:git at github.com:d-michail/jheaps.git</connection>
<url>https://github.com/d-michail/jheaps.git</url>
<developerConnection>scm:git:git at github.com:d-michail/jheaps.git</developerConnection>
- <tag>jheaps-0.14</tag>
+ <tag>jheaps-0.16</tag>
</scm>
<issueManagement>
@@ -57,31 +62,6 @@
</developer>
</developers>
- <repositories>
- <repository>
- <id>sonatype-nexus-snapshots</id>
- <name>Sonatype Nexus Snapshots</name>
- <url>https://oss.sonatype.org/content/repositories/snapshots</url>
- <releases>
- <enabled>false</enabled>
- </releases>
- <snapshots>
- <enabled>true</enabled>
- </snapshots>
- </repository>
- </repositories>
-
- <distributionManagement>
- <snapshotRepository>
- <id>ossrh</id>
- <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
- </snapshotRepository>
- <repository>
- <id>ossrh</id>
- <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
- </repository>
- </distributionManagement>
-
<dependencies>
<dependency>
<groupId>junit</groupId>
@@ -93,15 +73,24 @@
<build>
<plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-clean-plugin</artifactId>
+ <version>${maven-clean-plugin.version}</version>
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
- <source>${java.version}</source>
- <target>${java.version}</target>
+ <release>${java.version}</release>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>${maven-resources-plugin.version}</version>
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
@@ -124,8 +113,7 @@
<show>public</show>
<windowtitle>JHeaps</windowtitle>
<doclint>none</doclint>
- <source>8</source>
- <additionalJOption>--no-module-directories</additionalJOption>
+ <source>${java.version}</source>
</configuration>
</plugin>
<plugin>
@@ -147,6 +135,11 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-install-plugin</artifactId>
+ <version>${maven-install-plugin.version}</version>
+ </plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
@@ -283,7 +276,10 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
- <version>1.6</version>
+ <version>${maven-gpg-plugin.version}</version>
+ <configuration>
+ <bestPractices>true</bestPractices>
+ </configuration>
<executions>
<execution>
<id>sign-artifacts</id>
@@ -294,6 +290,15 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.sonatype.central</groupId>
+ <artifactId>central-publishing-maven-plugin</artifactId>
+ <version>${central-publishing-maven-plugin.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <publishingServerId>central</publishingServerId>
+ </configuration>
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
=====================================
src/main/java/org/jheaps/tree/PurePairingHeap.java
=====================================
@@ -0,0 +1,688 @@
+/*
+ * (C) Copyright 2014-2026, by Dimitrios Michail
+ *
+ * JHeaps Library
+ *
+ * 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
+ *
+ * http://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.
+ */
+package org.jheaps.tree;
+
+import java.io.Serializable;
+import java.util.Comparator;
+import java.util.NoSuchElementException;
+
+import org.jheaps.AddressableHeap;
+import org.jheaps.MergeableAddressableHeap;
+import org.jheaps.annotations.ConstantTime;
+import org.jheaps.annotations.LogLogTime;
+import org.jheaps.annotations.LogarithmicTime;
+
+/**
+ * Pure pairing heaps. The heap is sorted according to the {@linkplain Comparable
+ * natural ordering} of its keys, or by a {@link Comparator} provided at heap
+ * creation time, depending on which constructor is used.
+ *
+ * <p>
+ * The pure pairing heap is a simplification of the {@link PairingHeap} due to
+ * Tarjan and Xu, described in detail in the following
+ * <a href="https://arxiv.org/abs/2607.23118">paper</a>:
+ * <ul>
+ * <li>Robert E. Tarjan and Xiaoyang Xu, Pure Pairing Heaps, arXiv:2607.23118,
+ * 2026.</li>
+ * </ul>
+ * A standard pairing heap performs a {@code deleteMin} using a pairing pass
+ * followed by an assembly pass which links together all the winners of the
+ * pairing pass into a single tree. The pure pairing heap instead eliminates
+ * the assembly pass entirely: during its single pairing pass it keeps track of
+ * the winner of minimum key, and once the pass is complete, it simply makes
+ * that winner the new root and re-parents every other surviving winner as one
+ * of its children. Despite being simpler than the standard pairing heap, the
+ * pure pairing heap achieves the same amortized bounds as the more complicated
+ * multipass pairing heap.
+ *
+ * <p>
+ * This implementation provides amortized O(log(n)) time cost for the
+ * {@code deleteMin} operation, amortized O(loglog(n)) time cost for the
+ * {@code decreaseKey} operation, and amortized O(1) time cost for the
+ * {@code insert} and {@code meld} operations. Operation {@code findMin}, is a
+ * worst-case O(1) operation.
+ *
+ * <p>
+ * All the above bounds, however, assume that the user does not perform
+ * cascading melds on heaps such as:
+ *
+ * <pre>
+ * d.meld(e);
+ * c.meld(d);
+ * b.meld(c);
+ * a.meld(b);
+ * </pre>
+ *
+ * The above scenario, although efficiently supported by using union-find with
+ * path compression, invalidates the claimed bounds.
+ *
+ * <p>
+ * Note that the ordering maintained by a pure pairing heap, like any heap, and
+ * whether or not an explicit comparator is provided, must be <em>consistent
+ * with {@code equals}</em> if this heap is to correctly implement the
+ * {@code AddressableHeap} interface. (See {@code Comparable} or
+ * {@code Comparator} for a precise definition of <em>consistent with
+ * equals</em>.) This is so because the {@code AddressableHeap} interface is
+ * defined in terms of the {@code equals} operation, but a pure pairing heap
+ * performs all key comparisons using its {@code compareTo} (or
+ * {@code compare}) method, so two keys that are deemed equal by this method
+ * are, from the standpoint of this heap, equal. The behavior of a heap
+ * <em>is</em> well-defined even if its ordering is inconsistent with
+ * {@code equals}; it just fails to obey the general contract of the
+ * {@code AddressableHeap} interface.
+ *
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If
+ * multiple threads access a heap concurrently, and at least one of the threads
+ * modifies the heap structurally, it <em>must</em> be synchronized externally.
+ * (A structural modification is any operation that adds or deletes one or more
+ * elements or changing the key of some element.) This is typically accomplished
+ * by synchronizing on some object that naturally encapsulates the heap.
+ *
+ * @param <K>
+ * the type of keys maintained by this heap
+ * @param <V>
+ * the type of values maintained by this heap
+ *
+ * @author Dimitrios Michail
+ *
+ * @see PairingHeap
+ * @see RankPairingHeap
+ * @see CostlessMeldPairingHeap
+ * @see FibonacciHeap
+ */
+public class PurePairingHeap<K, V> implements MergeableAddressableHeap<K, V>, Serializable {
+
+ private final static long serialVersionUID = 1;
+
+ /**
+ * The comparator used to maintain order in this heap, or null if it uses
+ * the natural ordering of its keys.
+ *
+ * @serial
+ */
+ private final Comparator<? super K> comparator;
+
+ /**
+ * The root of the pairing heap
+ */
+ private Node<K, V> root;
+
+ /**
+ * Size of the pairing heap
+ */
+ private long size;
+
+ /**
+ * Used to reference the current heap or some other pairing heap in case of
+ * melding, so that handles remain valid even after a meld, without having
+ * to iterate over them.
+ *
+ * In order to avoid maintaining a full-fledged union-find data structure,
+ * we disallow a heap to be used in melding more than once. We use however,
+ * path-compression in case of cascading melds, that it, a handle moves from
+ * one heap to another and then another.
+ */
+ private PurePairingHeap<K, V> other;
+
+ /**
+ * Constructs a new, empty heap, using the natural ordering of its keys. All
+ * keys inserted into the heap must implement the {@link Comparable}
+ * interface. Furthermore, all such keys must be <em>mutually
+ * comparable</em>: {@code k1.compareTo(k2)} must not throw a
+ * {@code ClassCastException} for any keys {@code k1} and {@code k2} in the
+ * heap. If the user attempts to put a key into the heap that violates this
+ * constraint (for example, the user attempts to put a string key into a
+ * heap whose keys are integers), the {@code insert(Object key)} call will
+ * throw a {@code ClassCastException}.
+ */
+ @ConstantTime
+ public PurePairingHeap() {
+ this(null);
+ }
+
+ /**
+ * Constructs a new, empty heap, ordered according to the given comparator.
+ * All keys inserted into the heap must be <em>mutually comparable</em> by
+ * the given comparator: {@code comparator.compare(k1,
+ * k2)} must not throw a {@code ClassCastException} for any keys {@code k1}
+ * and {@code k2} in the heap. If the user attempts to put a key into the
+ * heap that violates this constraint, the {@code insert(Object key)} call
+ * will throw a {@code ClassCastException}.
+ *
+ * @param comparator
+ * the comparator that will be used to order this heap. If
+ * {@code null}, the {@linkplain Comparable natural ordering} of
+ * the keys will be used.
+ */
+ @ConstantTime
+ public PurePairingHeap(Comparator<? super K> comparator) {
+ this.root = null;
+ this.comparator = comparator;
+ this.size = 0;
+ this.other = this;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalStateException
+ * if the heap has already been used in the right hand side of a
+ * meld
+ */
+ @Override
+ @ConstantTime(amortized = true)
+ public AddressableHeap.Handle<K, V> insert(K key, V value) {
+ if (other != this) {
+ throw new IllegalStateException("A heap cannot be used after a meld");
+ }
+ if (key == null) {
+ throw new NullPointerException("Null keys not permitted");
+ }
+ Node<K, V> n = new Node<K, V>(this, key, value);
+ if (comparator == null) {
+ root = link(root, n);
+ } else {
+ root = linkWithComparator(root, n);
+ }
+ size++;
+ return n;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalStateException
+ * if the heap has already been used in the right hand side of a
+ * meld
+ */
+ @Override
+ @ConstantTime(amortized = true)
+ public AddressableHeap.Handle<K, V> insert(K key) {
+ return insert(key, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @ConstantTime(amortized = false)
+ public AddressableHeap.Handle<K, V> findMin() {
+ if (size == 0) {
+ throw new NoSuchElementException();
+ }
+ return root;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @LogarithmicTime(amortized = true)
+ public AddressableHeap.Handle<K, V> deleteMin() {
+ if (size == 0) {
+ throw new NoSuchElementException();
+ }
+ // assert root.o_s == null && root.y_s == null;
+
+ Handle<K, V> oldRoot = root;
+
+ // cut all children, combine them and overwrite old root
+ root = combine(cutChildren(root));
+
+ // decrease size
+ size--;
+
+ return oldRoot;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @ConstantTime
+ public boolean isEmpty() {
+ return size == 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @ConstantTime
+ public long size() {
+ return size;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Comparator<? super K> comparator() {
+ return comparator;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @ConstantTime(amortized = false)
+ public void clear() {
+ root = null;
+ size = 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @ConstantTime(amortized = true)
+ public void meld(MergeableAddressableHeap<K, V> other) {
+ PurePairingHeap<K, V> h = (PurePairingHeap<K, V>) other;
+
+ // check same comparator
+ if (comparator != null) {
+ if (h.comparator == null || !h.comparator.equals(comparator)) {
+ throw new IllegalArgumentException("Cannot meld heaps using different comparators!");
+ }
+ } else if (h.comparator != null) {
+ throw new IllegalArgumentException("Cannot meld heaps using different comparators!");
+ }
+
+ if (h.other != h) {
+ throw new IllegalStateException("A heap cannot be used after a meld.");
+ }
+
+ // perform the meld
+ size += h.size;
+ if (comparator == null) {
+ root = link(root, h.root);
+ } else {
+ root = linkWithComparator(root, h.root);
+ }
+
+ // clear other
+ h.size = 0;
+ h.root = null;
+
+ // take ownership
+ h.other = this;
+ }
+
+ // --------------------------------------------------------------------
+ static class Node<K, V> implements AddressableHeap.Handle<K, V>, Serializable {
+
+ private final static long serialVersionUID = 1;
+
+ /*
+ * We maintain explicitly the belonging heap, instead of using an inner
+ * class due to possible cascading melding.
+ */
+ PurePairingHeap<K, V> heap;
+
+ K key;
+ V value;
+ Node<K, V> o_c; // older child
+ Node<K, V> y_s; // younger sibling
+ Node<K, V> o_s; // older sibling or parent
+
+ Node(PurePairingHeap<K, V> heap, K key, V value) {
+ this.heap = heap;
+ this.key = key;
+ this.value = value;
+ this.o_c = null;
+ this.y_s = null;
+ this.o_s = null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public K getKey() {
+ return key;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public V getValue() {
+ return value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setValue(V value) {
+ this.value = value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @LogLogTime(amortized = true)
+ public void decreaseKey(K newKey) {
+ getOwner().decreaseKey(this, newKey);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @LogarithmicTime(amortized = true)
+ public void delete() {
+ getOwner().delete(this);
+ }
+
+ /*
+ * Get the owner heap of the handle. This is union-find with
+ * path-compression between heaps.
+ */
+ PurePairingHeap<K, V> getOwner() {
+ if (heap.other != heap) {
+ // find root
+ PurePairingHeap<K, V> root = heap;
+ while (root != root.other) {
+ root = root.other;
+ }
+ // path-compression
+ PurePairingHeap<K, V> cur = heap;
+ while (cur.other != root) {
+ PurePairingHeap<K, V> next = cur.other;
+ cur.other = root;
+ cur = next;
+ }
+ heap = root;
+ }
+ return heap;
+ }
+ }
+
+ /**
+ * Decrease the key of a node.
+ *
+ * @param n
+ * the node
+ * @param newKey
+ * the new key
+ */
+ @SuppressWarnings("unchecked")
+ private void decreaseKey(Node<K, V> n, K newKey) {
+ int c;
+ if (comparator == null) {
+ c = ((Comparable<? super K>) newKey).compareTo(n.key);
+ } else {
+ c = comparator.compare(newKey, n.key);
+ }
+
+ if (c > 0) {
+ throw new IllegalArgumentException("Keys can only be decreased!");
+ }
+ n.key = newKey;
+ if (c == 0 || root == n) {
+ return;
+ }
+
+ if (n.o_s == null) {
+ throw new IllegalArgumentException("Invalid handle!");
+ }
+
+ // unlink from parent
+ if (n.y_s != null) {
+ n.y_s.o_s = n.o_s;
+ }
+ if (n.o_s.o_c == n) { // I am the oldest :(
+ n.o_s.o_c = n.y_s;
+ } else { // I have an older sibling!
+ n.o_s.y_s = n.y_s;
+ }
+ n.y_s = null;
+ n.o_s = null;
+
+ // merge with root
+ if (comparator == null) {
+ root = link(root, n);
+ } else {
+ root = linkWithComparator(root, n);
+ }
+ }
+
+ /*
+ * Delete a node
+ */
+ private void delete(Node<K, V> n) {
+ if (root == n) {
+ deleteMin();
+ n.o_c = null;
+ n.y_s = null;
+ n.o_s = null;
+ return;
+ }
+
+ if (n.o_s == null) {
+ throw new IllegalArgumentException("Invalid handle!");
+ }
+
+ // unlink from parent
+ if (n.y_s != null) {
+ n.y_s.o_s = n.o_s;
+ }
+ if (n.o_s.o_c == n) { // I am the oldest :(
+ n.o_s.o_c = n.y_s;
+ } else { // I have an older sibling!
+ n.o_s.y_s = n.y_s;
+ }
+ n.y_s = null;
+ n.o_s = null;
+
+ // perform delete-min at tree rooted at this
+ Node<K, V> t = combine(cutChildren(n));
+
+ // and merge with other cut tree
+ if (comparator == null) {
+ root = link(root, t);
+ } else {
+ root = linkWithComparator(root, t);
+ }
+
+ size--;
+ }
+
+ /*
+ * Perform a delete-min on a list of roots using a single pairing pass and
+ * no assembly pass: the pairing pass links roots pairwise, left to right,
+ * while keeping track of the winner of minimum key; that winner becomes
+ * the new root, and every other surviving winner is spliced in, as a
+ * group, to the left of its pre-existing children.
+ */
+ private Node<K, V> combine(Node<K, V> l) {
+ if (l == null) {
+ return null;
+ }
+
+ assert l.o_s == null;
+
+ // single pairing pass, building the list of winners (doubly-linked via
+ // y_s/o_s, in original left-to-right order) while tracking the winner
+ // of minimum key
+ Node<K, V> headWinner = null, tailWinner = null, minWinner = null;
+ Node<K, V> it = l;
+ if (comparator == null) {
+ while (it != null) {
+ Node<K, V> a = it;
+ it = it.y_s;
+
+ Node<K, V> winner;
+ if (it == null) {
+ winner = a;
+ winner.y_s = null;
+ winner.o_s = null;
+ } else {
+ Node<K, V> b = it;
+ it = it.y_s;
+
+ // disconnect both
+ a.y_s = null;
+ a.o_s = null;
+ b.y_s = null;
+ b.o_s = null;
+
+ // link trees
+ winner = link(a, b);
+ }
+
+ // append winner to the winners list
+ if (headWinner == null) {
+ headWinner = winner;
+ } else {
+ tailWinner.y_s = winner;
+ winner.o_s = tailWinner;
+ }
+ tailWinner = winner;
+
+ if (minWinner == null || ((Comparable<? super K>) winner.key).compareTo(minWinner.key) < 0) {
+ minWinner = winner;
+ }
+ }
+ } else {
+ while (it != null) {
+ Node<K, V> a = it;
+ it = it.y_s;
+
+ Node<K, V> winner;
+ if (it == null) {
+ winner = a;
+ winner.y_s = null;
+ winner.o_s = null;
+ } else {
+ Node<K, V> b = it;
+ it = it.y_s;
+
+ // disconnect both
+ a.y_s = null;
+ a.o_s = null;
+ b.y_s = null;
+ b.o_s = null;
+
+ // link trees
+ winner = linkWithComparator(a, b);
+ }
+
+ // append winner to the winners list
+ if (headWinner == null) {
+ headWinner = winner;
+ } else {
+ tailWinner.y_s = winner;
+ winner.o_s = tailWinner;
+ }
+ tailWinner = winner;
+
+ if (minWinner == null || comparator.compare(winner.key, minWinner.key) < 0) {
+ minWinner = winner;
+ }
+ }
+ }
+
+ // remove minWinner from the winners list
+ Node<K, V> prev = minWinner.o_s;
+ Node<K, V> next = minWinner.y_s;
+ if (prev != null) {
+ prev.y_s = next;
+ } else {
+ headWinner = next;
+ }
+ if (next != null) {
+ next.o_s = prev;
+ } else {
+ tailWinner = prev;
+ }
+
+ // splice the remaining winners to the left of minWinner's existing
+ // children; this is the assembly phase of a pure pairing heap
+ if (headWinner != null) {
+ Node<K, V> minOldChild = minWinner.o_c;
+ tailWinner.y_s = minOldChild;
+ if (minOldChild != null) {
+ minOldChild.o_s = tailWinner;
+ }
+ minWinner.o_c = headWinner;
+ headWinner.o_s = minWinner;
+ }
+ minWinner.y_s = null;
+ minWinner.o_s = null;
+
+ return minWinner;
+ }
+
+ /**
+ * Cut the children of a node and return the list.
+ *
+ * @param n
+ * the node
+ * @return the first node in the children list
+ */
+ private Node<K, V> cutChildren(Node<K, V> n) {
+ Node<K, V> child = n.o_c;
+ n.o_c = null;
+ if (child != null) {
+ child.o_s = null;
+ }
+ return child;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Node<K, V> link(Node<K, V> f, Node<K, V> s) {
+ if (s == null) {
+ return f;
+ } else if (f == null) {
+ return s;
+ } else if (((Comparable<? super K>) f.key).compareTo(s.key) <= 0) {
+ s.y_s = f.o_c;
+ s.o_s = f;
+ if (f.o_c != null) {
+ f.o_c.o_s = s;
+ }
+ f.o_c = s;
+ return f;
+ } else {
+ return link(s, f);
+ }
+ }
+
+ private Node<K, V> linkWithComparator(Node<K, V> f, Node<K, V> s) {
+ if (s == null) {
+ return f;
+ } else if (f == null) {
+ return s;
+ } else if (comparator.compare(f.key, s.key) <= 0) {
+ s.y_s = f.o_c;
+ s.o_s = f;
+ if (f.o_c != null) {
+ f.o_c.o_s = s;
+ }
+ f.o_c = s;
+ return f;
+ } else {
+ return linkWithComparator(s, f);
+ }
+ }
+
+}
=====================================
src/main/java/org/jheaps/tree/StrictFibonacciHeap.java
=====================================
@@ -0,0 +1,1300 @@
+/*
+ * (C) Copyright 2014-2026, by Dimitrios Michail
+ *
+ * JHeaps Library
+ *
+ * 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
+ *
+ * http://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.
+ */
+package org.jheaps.tree;
+
+import java.io.Serializable;
+import java.util.Comparator;
+import java.util.NoSuchElementException;
+
+import org.jheaps.AddressableHeap;
+import org.jheaps.MergeableAddressableHeap;
+import org.jheaps.annotations.ConstantTime;
+import org.jheaps.annotations.LogarithmicTime;
+import org.jheaps.annotations.VisibleForTesting;
+
+/**
+ * Strict Fibonacci heaps. The heap is sorted according to the {@linkplain Comparable
+ * natural ordering} of its keys, or by a {@link Comparator} provided at heap creation
+ * time, depending on which constructor is used.
+ *
+ * <p>
+ * A strict Fibonacci heap is a pointer-based heap described in detail in the following
+ * <a href="https://doi.org/10.1145/3707692">paper</a>:
+ * <ul>
+ * <li>Gerth Stølting Brodal, George Lagogiannis, and Robert E. Tarjan, Strict
+ * Fibonacci Heaps, ACM Transactions on Algorithms 21(2), Article 15, 2025.</li>
+ * </ul>
+ * Ordinary Fibonacci heaps achieve their bounds only in the amortized sense; a single
+ * {@code decreaseKey} or {@code meld} may still be expensive. A strict Fibonacci heap
+ * matches those same bounds in the <em>worst case</em>, on a pointer machine, using
+ * linear space. Every node is either <em>active</em> (owned by a live heap) or
+ * <em>passive</em> (its subtree structure has been "forgotten" by a cheap meld); active
+ * nodes are further either <em>free</em> or <em>fixed</em>, each fixed node carrying a
+ * small integer <em>loss</em>. A heap keeps its active nodes partitioned into a
+ * constant number of groups (the "fix-list"), which lets four O(1) local
+ * transformations restore, after every operation, the invariants that keep the maximum
+ * rank, degree and total loss of the heap logarithmic in its size. Melding two heaps
+ * simply marks every active node of the smaller heap passive in O(1) time (a single
+ * flag flip shared by all its nodes), discarding the smaller heap's bookkeeping instead
+ * of merging it, which is the key simplification over earlier worst-case constant-time
+ * meldable heaps.
+ *
+ * <p>
+ * This implementation provides worst-case O(1) time cost for the operations
+ * {@code insert}, {@code findMin}, {@code meld} and {@code decreaseKey}, and worst-case
+ * O(log(n)) time cost for the operations {@code deleteMin} and {@code delete}.
+ *
+ * <p>
+ * All the above bounds, however, assume that the user does not perform cascading melds
+ * on heaps such as:
+ *
+ * <pre>
+ * d.meld(e);
+ * c.meld(d);
+ * b.meld(c);
+ * a.meld(b);
+ * </pre>
+ *
+ * The above scenario, although efficiently supported by using union-find with path
+ * compression, invalidates the claimed bounds. More precisely, {@code insert},
+ * {@code findMin}, {@code deleteMin} and {@code meld}, when invoked directly on a live
+ * heap reference, always remain worst-case as stated above, no matter how many melds
+ * that heap has previously been the receiver of. Only {@link AddressableHeap.Handle#decreaseKey(Object)}
+ * and {@link AddressableHeap.Handle#delete()}, when invoked on a handle that was
+ * absorbed into another heap through one or more melds in which its own heap was the
+ * smaller side, pay an additional amortized (not worst-case) union-find lookup in order
+ * to locate the handle's current heap.
+ *
+ * <p>
+ * Note that the ordering maintained by a strict Fibonacci heap, like any heap, and
+ * whether or not an explicit comparator is provided, must be <em>consistent with
+ * {@code equals}</em> if this heap is to correctly implement the {@code AddressableHeap}
+ * interface. (See {@code Comparable} or {@code Comparator} for a precise definition of
+ * <em>consistent with equals</em>.) This is so because the {@code AddressableHeap}
+ * interface is defined in terms of the {@code equals} operation, but a strict Fibonacci
+ * heap performs all key comparisons using its {@code compareTo} (or {@code compare})
+ * method, so two keys that are deemed equal by this method are, from the standpoint of
+ * this heap, equal. The behavior of a heap <em>is</em> well-defined even if its ordering
+ * is inconsistent with {@code equals}; it just fails to obey the general contract of the
+ * {@code AddressableHeap} interface.
+ *
+ * <p>
+ * <strong>Note that this implementation is not synchronized.</strong> If multiple
+ * threads access a heap concurrently, and at least one of the threads modifies the heap
+ * structurally, it <em>must</em> be synchronized externally. (A structural modification
+ * is any operation that adds or deletes one or more elements or changing the key of
+ * some element.) This is typically accomplished by synchronizing on some object that
+ * naturally encapsulates the heap.
+ *
+ * @param <K>
+ * the type of keys maintained by this heap
+ * @param <V>
+ * the type of values maintained by this heap
+ *
+ * @author Dimitrios Michail
+ *
+ * @see FibonacciHeap
+ * @see SimpleFibonacciHeap
+ */
+public class StrictFibonacciHeap<K, V> implements MergeableAddressableHeap<K, V>, Serializable {
+
+ private final static long serialVersionUID = 1;
+
+ /**
+ * The comparator used to maintain order in this heap, or null if it uses the
+ * natural ordering of its keys.
+ *
+ * @serial
+ */
+ private final Comparator<? super K> comparator;
+
+ /**
+ * The current heap record backing this heap. A meld may cheaply repoint this field
+ * to another, larger, heap record instead of physically merging bookkeeping; see
+ * {@link #meld(MergeableAddressableHeap)}.
+ */
+ @VisibleForTesting
+ HeapRecord<K, V> rec;
+
+ /**
+ * Constructs a new, empty heap, using the natural ordering of its keys. All keys
+ * inserted into the heap must implement the {@link Comparable} interface.
+ * Furthermore, all such keys must be <em>mutually comparable</em>:
+ * {@code k1.compareTo(k2)} must not throw a {@code ClassCastException} for any keys
+ * {@code k1} and {@code k2} in the heap. If the user attempts to put a key into the
+ * heap that violates this constraint (for example, the user attempts to put a
+ * string key into a heap whose keys are integers), the {@code insert(Object key)}
+ * call will throw a {@code ClassCastException}.
+ */
+ @ConstantTime
+ public StrictFibonacciHeap() {
+ this(null);
+ }
+
+ /**
+ * Constructs a new, empty heap, ordered according to the given comparator. All keys
+ * inserted into the heap must be <em>mutually comparable</em> by the given
+ * comparator: {@code comparator.compare(k1,
+ * k2)} must not throw a {@code ClassCastException} for any keys {@code k1} and
+ * {@code k2} in the heap. If the user attempts to put a key into the heap that
+ * violates this constraint, the {@code insert(Object key)} call will throw a
+ * {@code ClassCastException}.
+ *
+ * @param comparator
+ * the comparator that will be used to order this heap. If {@code null},
+ * the {@linkplain Comparable natural ordering} of the keys will be used.
+ */
+ @ConstantTime
+ public StrictFibonacciHeap(Comparator<? super K> comparator) {
+ this.comparator = comparator;
+ this.rec = new HeapRecord<K, V>(comparator);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalStateException
+ * if the heap has already been used in the right hand side of a meld
+ */
+ @Override
+ @ConstantTime
+ public AddressableHeap.Handle<K, V> insert(K key, V value) {
+ if (!rec.isActive()) {
+ throw new IllegalStateException("A heap cannot be used after a meld");
+ }
+ if (key == null) {
+ throw new NullPointerException("Null keys not permitted");
+ }
+ Node<K, V> n = new Node<K, V>(rec, key, value);
+ if (rec.root == null) {
+ rec.root = n;
+ } else {
+ rec.root = rec.link(rec.root, n);
+ rec.applyFreeRootReductions(3, 2);
+ }
+ return n;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalStateException
+ * if the heap has already been used in the right hand side of a meld
+ */
+ @Override
+ @ConstantTime
+ public AddressableHeap.Handle<K, V> insert(K key) {
+ return insert(key, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @ConstantTime
+ public AddressableHeap.Handle<K, V> findMin() {
+ if (rec.size == 0) {
+ throw new NoSuchElementException();
+ }
+ return rec.root;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @LogarithmicTime
+ public AddressableHeap.Handle<K, V> deleteMin() {
+ if (rec.size == 0) {
+ throw new NoSuchElementException();
+ }
+ Node<K, V> min = rec.root;
+ rec.deleteMin();
+ return min;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @ConstantTime
+ public boolean isEmpty() {
+ return rec.size == 0;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @ConstantTime
+ public long size() {
+ return rec.size;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Comparator<? super K> comparator() {
+ return comparator;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @ConstantTime
+ public void clear() {
+ rec = new HeapRecord<K, V>(comparator);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @ConstantTime
+ @SuppressWarnings("unchecked")
+ public void meld(MergeableAddressableHeap<K, V> other) {
+ StrictFibonacciHeap<K, V> o = (StrictFibonacciHeap<K, V>) other;
+
+ // check same comparator
+ if (comparator != null) {
+ if (o.comparator == null || !o.comparator.equals(comparator)) {
+ throw new IllegalArgumentException("Cannot meld heaps using different comparators!");
+ }
+ } else if (o.comparator != null) {
+ throw new IllegalArgumentException("Cannot meld heaps using different comparators!");
+ }
+
+ if (!rec.isActive() || !o.rec.isActive()) {
+ throw new IllegalStateException("A heap cannot be used after a meld.");
+ }
+
+ HeapRecord<K, V> small, large;
+ if (rec.size < o.rec.size) {
+ small = rec;
+ large = o.rec;
+ } else {
+ small = o.rec;
+ large = rec;
+ }
+
+ // the main novelty in the paper: forget the smaller heap's structure by simply
+ // marking it passive in O(1) time
+ small.other = large;
+ large.size += small.size;
+ if (small.size > 0) {
+ Node<K, V> smallRoot = small.root;
+ small.size = 0;
+ small.root = null;
+ small.rankList = null;
+ small.retireFixList();
+
+ // insert small's fix-list as passive at the front of large's fix-list
+ Node<K, V> largeHead = large.fixListHead();
+ Node<K, V> smallHead = smallRoot;
+ Node<K, V> smallTail = smallRoot.fixPrev;
+ smallHead.fixPrev = largeHead.fixPrev;
+ smallTail.fixNext = largeHead;
+ smallTail.fixNext.fixPrev = smallTail;
+ smallHead.fixPrev.fixNext = smallHead;
+ large.fixPassive = smallHead;
+
+ // link roots and apply reductions
+ large.root = large.link(large.root, smallRoot);
+ large.applyFreeRootReductions(1, 1);
+ }
+
+ // the receiver always ends up owning the winning (surviving) heap record,
+ // and the argument always ends up owning the losing (now empty, passive)
+ // one, regardless of which side actually turned out larger
+ rec = large;
+ o.rec = small;
+ }
+
+ /**
+ * The heap record backing an active (or, once melded away, permanently passive)
+ * strict Fibonacci heap. Node and rank records reference this object directly
+ * instead of the outer {@link StrictFibonacciHeap}, so that a meld can cheaply
+ * repoint the outer wrapper at whichever heap record turns out to be the larger one,
+ * without ever having to rewrite any existing node's or rank's back-pointer.
+ */
+ static class HeapRecord<K, V> implements Serializable {
+
+ private final static long serialVersionUID = 1;
+
+ // fix-list groups
+ static final int PASSIVE = 0;
+ static final int FREE_MULTIPLE = 1;
+ static final int FREE_SINGLE = 2;
+ static final int LOSS_ZERO = 3;
+ static final int LOSS_ONE_MULTIPLE = 4;
+ static final int LOSS_ONE_SINGLE = 5;
+ static final int LOSS_TWO = 6;
+
+ final Comparator<? super K> comparator;
+ long size;
+ Node<K, V> root;
+ Rank<K, V> rankList;
+ Node<K, V> fixPassive;
+ Node<K, V> fixFreeMultiple;
+ Node<K, V> fixFreeSingle;
+ Node<K, V> fixLossZero;
+ Node<K, V> fixLossOneMultiple;
+ Node<K, V> fixLossOneSingle;
+ Node<K, V> fixLossTwo;
+
+ /*
+ * Union-find pointer used both to mark this record permanently passive
+ * (other != this) once it loses a meld, and to let a node handle that was
+ * absorbed several melds ago find its current owner in amortized O(1) time via
+ * path compression. A record is active if and only if other == this.
+ */
+ HeapRecord<K, V> other;
+
+ HeapRecord(Comparator<? super K> comparator) {
+ this.comparator = comparator;
+ this.size = 0;
+ this.root = null;
+ this.rankList = null;
+ this.fixPassive = null;
+ this.fixFreeMultiple = null;
+ this.fixFreeSingle = null;
+ this.fixLossZero = null;
+ this.fixLossOneMultiple = null;
+ this.fixLossOneSingle = null;
+ this.fixLossTwo = null;
+ this.other = this;
+ }
+
+ boolean isActive() {
+ return other == this;
+ }
+
+ HeapRecord<K, V> find() {
+ if (other != this) {
+ HeapRecord<K, V> r = this;
+ while (r.other != r) {
+ r = r.other;
+ }
+ HeapRecord<K, V> cur = this;
+ while (cur.other != r) {
+ HeapRecord<K, V> next = cur.other;
+ cur.other = r;
+ cur = next;
+ }
+ return r;
+ }
+ return this;
+ }
+
+ /*
+ * Unified strict node ordering used everywhere the reference algorithm compares
+ * two nodes. This deliberately does not duplicate a comparator/Comparable code
+ * path pair as most other heaps in this library do: the fix-list bookkeeping
+ * ported here is large and intricate enough that duplicating it entirely was
+ * judged too risky, so a single helper is used at the cost of one extra
+ * indirection per comparison.
+ */
+ @SuppressWarnings("unchecked")
+ boolean less(Node<K, V> x, Node<K, V> y) {
+ if (x.forcedMinimum) {
+ return true;
+ }
+ if (y.forcedMinimum) {
+ return false;
+ }
+ int c;
+ if (comparator == null) {
+ c = ((Comparable<? super K>) x.key).compareTo(y.key);
+ } else {
+ c = comparator.compare(x.key, y.key);
+ }
+ return c != 0 ? c < 0 : x.seq < y.seq;
+ }
+
+ Node<K, V> link(Node<K, V> x, Node<K, V> y) {
+ if (less(x, y)) {
+ x.addChild(y);
+ return x;
+ } else {
+ y.addChild(x);
+ return y;
+ }
+ }
+
+ /*
+ * Allocation-free equivalent of the reference's generic round-robin
+ * apply_reductions, specialized to the only two reduction kinds it is ever
+ * called with (free node and root degree reductions). Repeatedly attempts the
+ * still-pending attempts of both kinds until either the budget is exhausted or
+ * a full round makes no progress at all.
+ */
+ void applyFreeRootReductions(int freeAttempts, int rootAttempts) {
+ while (freeAttempts > 0 || rootAttempts > 0) {
+ int f = freeAttempts;
+ int r = rootAttempts;
+ freeAttempts = 0;
+ rootAttempts = 0;
+ boolean progress = false;
+ for (int i = 0; i < f; i++) {
+ if (reduceFree()) {
+ progress = true;
+ } else {
+ freeAttempts++;
+ }
+ }
+ for (int i = 0; i < r; i++) {
+ if (reduceRoot()) {
+ progress = true;
+ } else {
+ rootAttempts++;
+ }
+ }
+ if (!progress) {
+ return;
+ }
+ }
+ }
+
+ void reducePassive() {
+ for (int i = 0; i < 3; i++) {
+ if (fixPassive == null) {
+ return;
+ }
+ fixPassive.passive2free(this);
+ }
+ }
+
+ boolean reduceFree() {
+ Node<K, V> x = fixFreeMultiple;
+ if (x == null) {
+ return false;
+ }
+ Node<K, V> y = x.fixNext;
+ if (less(y, x)) {
+ Node<K, V> t = x;
+ x = y;
+ y = t;
+ }
+
+ y.cut(this);
+ y.free2fixed();
+ x.addChild(y);
+ Node<K, V> z = x.leftChild.left;
+ if (z.passive()) {
+ z.cut(this);
+ root.addChild(z);
+ }
+ return true;
+ }
+
+ boolean reduceRoot() {
+ if (root == null || root.leftChild == null) {
+ return false;
+ }
+ Node<K, V> z = root.leftChild.left;
+ Node<K, V> y = z.left;
+ Node<K, V> x = y.left;
+ if (z == y || z == x || !x.passive()) {
+ return false;
+ }
+
+ x.cut(this);
+ y.cut(this);
+ z.cut(this);
+ x.passive2free(this);
+ y.passive2free(this);
+ z.passive2free(this);
+
+ Node<K, V> t;
+ if (less(y, x)) {
+ t = x;
+ x = y;
+ y = t;
+ }
+ if (less(z, y)) {
+ t = y;
+ y = z;
+ z = t;
+ if (less(y, x)) {
+ t = x;
+ x = y;
+ y = t;
+ }
+ }
+
+ y.free2fixed();
+ z.free2fixed();
+ root.addChild(x);
+ x.addChild(y);
+ y.addChild(z);
+ return true;
+ }
+
+ boolean reduceLoss() {
+ return reduceLossOne() || reduceLossTwo();
+ }
+
+ boolean reduceLossOne() {
+ Node<K, V> x = fixLossOneMultiple;
+ if (x == null) {
+ return false;
+ }
+ Node<K, V> y = x.fixNext;
+ if (less(y, x)) {
+ Node<K, V> t = x;
+ x = y;
+ y = t;
+ }
+
+ y.cut(this);
+ x.fixListRemove(this);
+ y.fixListRemove(this);
+ x.loss--;
+ y.loss--;
+ x.fixListAdd(this);
+ y.fixListAdd(this);
+ x.addChild(y);
+ Node<K, V> z = x.leftChild.left;
+ if (z.passive()) {
+ z.cut(this);
+ root.addChild(z);
+ }
+ return true;
+ }
+
+ boolean reduceLossTwo() {
+ Node<K, V> x = fixLossTwo;
+ if (x == null) {
+ return false;
+ }
+ x.fixed2free();
+ return true;
+ }
+
+ Rank<K, V> rankZero() {
+ Rank<K, V> r = rankList;
+ if (r == null) {
+ r = new Rank<K, V>(this, 0);
+ rankList = r;
+ }
+ r.increaseReferenceCount();
+ return r;
+ }
+
+ void deleteMin() {
+ Node<K, V> oldRoot = root;
+ if (size == 1) {
+ root = null;
+ oldRoot.retire(this);
+ return;
+ }
+
+ Node<K, V> newRoot = oldRoot.leftChild;
+ Node<K, V> cur = newRoot.right;
+ while (cur != oldRoot.leftChild) {
+ if (less(cur, newRoot)) {
+ newRoot = cur;
+ }
+ cur = cur.right;
+ }
+ if (newRoot.fixed()) {
+ newRoot.fixed2free();
+ }
+ newRoot.cut(this);
+ while (oldRoot.leftChild != null) {
+ Node<K, V> child = oldRoot.leftChild;
+ if (child.fixed()) {
+ child.fixed2free();
+ }
+ child.cut(this);
+ link(newRoot, child);
+ }
+ root = newRoot;
+ oldRoot.retire(this);
+
+ reducePassive();
+ while (reduceLoss()) {
+ // repeat until no loss reduction is possible
+ }
+ while (reduceFree() || reduceRoot()) {
+ // repeat until no free or root reduction can be applied
+ }
+ }
+
+ /*
+ * Structural half of a decrease-key, shared verbatim by both a genuine
+ * decreaseKey and a delete (the latter via Node.forcedMinimum, see delete()
+ * below). The node's key (or forcedMinimum flag) must already reflect its new,
+ * smaller value by the time this is called.
+ */
+ void decreaseKeyStructural(Node<K, V> node) {
+ Node<K, V> parent = node.parent;
+ if (parent == null || less(parent, node)) {
+ return;
+ }
+ if (node.fixed()) {
+ node.fixed2free();
+ }
+ node.cut(this);
+ root = link(node, root);
+ reduceLoss();
+ applyFreeRootReductions(6, 4);
+ }
+
+ /*
+ * Delete a node by temporarily treating it as though its key were minus
+ * infinity (without actually touching its real key, since K is generic and has
+ * no synthesizable sentinel value), moving it to the root via the same
+ * structural transformation used by decreaseKey, and then deleting the
+ * (now-minimum) root. At most one node per heap ever has forcedMinimum set at a
+ * time, since deleteMin() below fully retires exactly that node before
+ * returning.
+ */
+ void delete(Node<K, V> node) {
+ node.forcedMinimum = true;
+ decreaseKeyStructural(node);
+ deleteMin();
+ }
+
+ void retireFixList() {
+ fixPassive = null;
+ fixFreeMultiple = null;
+ fixFreeSingle = null;
+ fixLossZero = null;
+ fixLossOneMultiple = null;
+ fixLossOneSingle = null;
+ fixLossTwo = null;
+ }
+
+ Node<K, V> fixListHead() {
+ return fixListInsertionPoint(PASSIVE);
+ }
+
+ Node<K, V> fixListGroupHead(int group) {
+ switch (group) {
+ case PASSIVE:
+ return fixPassive;
+ case FREE_MULTIPLE:
+ return fixFreeMultiple;
+ case FREE_SINGLE:
+ return fixFreeSingle;
+ case LOSS_ZERO:
+ return fixLossZero;
+ case LOSS_ONE_MULTIPLE:
+ return fixLossOneMultiple;
+ case LOSS_ONE_SINGLE:
+ return fixLossOneSingle;
+ default:
+ return fixLossTwo;
+ }
+ }
+
+ Node<K, V> fixListInsertionPoint(int group) {
+ for (int i = 0; i < 7; i++) {
+ Node<K, V> point = fixListGroupHead((group + i) % 7);
+ if (point != null) {
+ return point;
+ }
+ }
+ return null;
+ }
+
+ void fixListInsert(int group, Node<K, V> node) {
+ Node<K, V> succ = fixListInsertionPoint(group);
+ node.fixListInsertBefore(succ);
+ switch (group) {
+ case FREE_MULTIPLE:
+ fixFreeMultiple = node;
+ break;
+ case FREE_SINGLE:
+ fixFreeSingle = node;
+ break;
+ case LOSS_ZERO:
+ fixLossZero = node;
+ break;
+ case LOSS_ONE_MULTIPLE:
+ fixLossOneMultiple = node;
+ break;
+ case LOSS_ONE_SINGLE:
+ fixLossOneSingle = node;
+ break;
+ case LOSS_TWO:
+ fixLossTwo = node;
+ break;
+ default:
+ break;
+ }
+ }
+
+ }
+
+ // --------------------------------------------------------------------
+ static class Node<K, V> implements AddressableHeap.Handle<K, V>, Serializable {
+
+ private final static long serialVersionUID = 1;
+
+ /*
+ * Global, monotonically increasing counter used only to break ties between
+ * equal keys with a strict order, exactly as the reference implementation
+ * breaks ties using node identity. Must be global rather than per-heap, since
+ * nodes originally created in different heaps can end up compared to each
+ * other after a meld.
+ */
+ private static long nextSeq = 0;
+
+ K key;
+ V value;
+
+ // tree structure
+ Node<K, V> parent;
+ Node<K, V> leftChild;
+ Node<K, V> left;
+ Node<K, V> right;
+
+ // active-node state
+ boolean free;
+ int loss;
+ Rank<K, V> rank;
+
+ // fix-list
+ Node<K, V> fixPrev;
+ Node<K, V> fixNext;
+
+ final long seq;
+
+ /*
+ * Used by delete() to emulate decreasing this node's key to minus infinity
+ * without needing a synthesizable sentinel K value; see HeapRecord.delete.
+ */
+ boolean forcedMinimum;
+
+ Node(HeapRecord<K, V> heap, K key, V value) {
+ heap.size++;
+ this.key = key;
+ this.value = value;
+ this.parent = null;
+ this.leftChild = null;
+ this.right = this;
+ this.left = this;
+ this.free = true;
+ this.loss = 0;
+ this.rank = heap.rankZero();
+ this.fixPrev = this;
+ this.fixNext = this;
+ this.seq = nextSeq++;
+ this.forcedMinimum = false;
+ fixListAdd(heap);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public K getKey() {
+ return key;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public V getValue() {
+ return value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setValue(V value) {
+ this.value = value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @ConstantTime
+ @SuppressWarnings("unchecked")
+ public void decreaseKey(K newKey) {
+ if (rank == null) {
+ throw new IllegalArgumentException("Invalid handle!");
+ }
+ HeapRecord<K, V> heap = rank.heap.find();
+
+ int c;
+ if (heap.comparator == null) {
+ c = ((Comparable<? super K>) newKey).compareTo(this.key);
+ } else {
+ c = heap.comparator.compare(newKey, this.key);
+ }
+ if (c > 0) {
+ throw new IllegalArgumentException("Keys can only be decreased!");
+ }
+ this.key = newKey;
+ heap.decreaseKeyStructural(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @LogarithmicTime
+ public void delete() {
+ if (rank == null) {
+ throw new IllegalArgumentException("Invalid handle!");
+ }
+ rank.heap.find().delete(this);
+ }
+
+ void retire(HeapRecord<K, V> heap) {
+ Rank<K, V> r = this.rank;
+ fixListRemove(heap);
+ heap.size--;
+ this.rank = null;
+ this.forcedMinimum = false;
+ r.decreaseReferenceCount();
+ }
+
+ HeapRecord<K, V> heap() {
+ return rank.heap;
+ }
+
+ boolean active() {
+ return heap().isActive();
+ }
+
+ boolean passive() {
+ return !active();
+ }
+
+ boolean free() {
+ return active() && free;
+ }
+
+ boolean fixed() {
+ return active() && !free;
+ }
+
+ void changeRank(Rank<K, V> newRank) {
+ HeapRecord<K, V> heap = heap();
+ newRank.increaseReferenceCount();
+ fixListRemove(heap);
+ rank.decreaseReferenceCount();
+ rank = newRank;
+ fixListAdd(heap);
+ }
+
+ void increaseRank() {
+ changeRank(rank.next());
+ }
+
+ void decreaseRank() {
+ changeRank(rank.prev());
+ }
+
+ void addChild(Node<K, V> child) {
+ child.parent = this;
+ if (this.leftChild == null) {
+ this.leftChild = child;
+ } else {
+ child.right = this.leftChild;
+ child.left = child.right.left;
+ child.right.left = child;
+ child.left.right = child;
+ if (!child.passive()) {
+ this.leftChild = child;
+ }
+ }
+ if (this.active() && child.fixed()) {
+ this.increaseRank();
+ }
+ }
+
+ void cut(HeapRecord<K, V> heap) {
+ Node<K, V> parent = this.parent;
+ Node<K, V> right = this.right;
+ Node<K, V> left = this.left;
+
+ this.parent = null;
+ if (parent.leftChild == this) {
+ if (right != this) {
+ parent.leftChild = right;
+ } else {
+ parent.leftChild = null;
+ }
+ }
+ if (right != this) {
+ left.right = right;
+ right.left = left;
+ this.right = this;
+ this.left = this;
+ }
+ if (this.fixed() && parent.active()) {
+ parent.decreaseRank();
+ if (parent.fixed()) {
+ parent.fixListRemove(heap);
+ parent.loss++;
+ parent.fixListAdd(heap);
+ }
+ }
+ }
+
+ void free2fixed() {
+ HeapRecord<K, V> heap = heap();
+ fixListRemove(heap);
+ this.free = false;
+ this.loss = 0;
+ fixListAdd(heap);
+ }
+
+ void fixed2free() {
+ Node<K, V> parent = this.parent;
+ HeapRecord<K, V> heap = heap();
+ fixListRemove(heap);
+ this.free = true;
+ this.loss = 0;
+ fixListAdd(heap);
+ parent.decreaseRank();
+ if (parent.fixed()) {
+ parent.fixListRemove(heap);
+ parent.loss++;
+ parent.fixListAdd(heap);
+ }
+ }
+
+ void passive2free(HeapRecord<K, V> heap) {
+ fixListRemove(heap);
+ rank.decreaseReferenceCount();
+ rank = heap.rankZero();
+ this.free = true;
+ this.loss = 0;
+ Node<K, V> parent = this.parent;
+ if (parent != null) {
+ cut(heap);
+ parent.addChild(this);
+ }
+ fixListAdd(heap);
+ }
+
+ void fixListUnlink() {
+ fixPrev.fixNext = fixNext;
+ fixNext.fixPrev = fixPrev;
+ fixPrev = this;
+ fixNext = this;
+ }
+
+ void fixListInsertBefore(Node<K, V> succ) {
+ if (succ != null) {
+ this.fixNext = succ;
+ this.fixPrev = succ.fixPrev;
+ this.fixNext.fixPrev = this;
+ this.fixPrev.fixNext = this;
+ }
+ }
+
+ void fixListInsertAfter(Node<K, V> prev) {
+ this.fixPrev = prev;
+ this.fixNext = prev.fixNext;
+ this.fixNext.fixPrev = this;
+ this.fixPrev.fixNext = this;
+ }
+
+ boolean fixListSameGroup(Node<K, V> other) {
+ return (free() && other.free() && this.rank == other.rank)
+ || (fixed() && other.fixed() && this.rank == other.rank && this.loss == 1 && other.loss == 1);
+ }
+
+ void fixListGroup(Node<K, V> head, GroupInfo<K, V> out) {
+ int count = 1;
+ Node<K, V> first = this;
+ Node<K, V> last = this;
+ while (count < 3 && first != head && fixListSameGroup(first.fixPrev)) {
+ first = first.fixPrev;
+ count++;
+ }
+ while (count < 3 && last.fixNext != head && fixListSameGroup(last.fixNext)) {
+ last = last.fixNext;
+ count++;
+ }
+ out.count = count;
+ out.first = first;
+ }
+
+ void fixListAdd(HeapRecord<K, V> heap) {
+ Node<K, V> head = heap.fixListHead();
+ if (free()) {
+ Node<K, V> first = rank.free;
+ if (first == null) {
+ rank.free = this;
+ heap.fixListInsert(HeapRecord.FREE_SINGLE, this);
+ } else {
+ GroupInfo<K, V> info = new GroupInfo<K, V>();
+ first.fixListGroup(head, info);
+ if (info.count >= 2) {
+ fixListInsertAfter(first);
+ } else {
+ if (heap.fixFreeSingle == first) {
+ if (first.fixNext != head && first.fixNext.free()) {
+ heap.fixFreeSingle = first.fixNext;
+ } else {
+ heap.fixFreeSingle = null;
+ }
+ }
+ first.fixListUnlink();
+ heap.fixListInsert(HeapRecord.FREE_MULTIPLE, this);
+ heap.fixListInsert(HeapRecord.FREE_MULTIPLE, first);
+ }
+ }
+ } else {
+ if (loss == 0) {
+ heap.fixListInsert(HeapRecord.LOSS_ZERO, this);
+ } else if (loss >= 2) {
+ heap.fixListInsert(HeapRecord.LOSS_TWO, this);
+ } else {
+ Node<K, V> first = rank.lossOne;
+ if (first == null) {
+ rank.lossOne = this;
+ heap.fixListInsert(HeapRecord.LOSS_ONE_SINGLE, this);
+ } else {
+ GroupInfo<K, V> info = new GroupInfo<K, V>();
+ first.fixListGroup(head, info);
+ if (info.count >= 2) {
+ fixListInsertAfter(first);
+ } else {
+ if (heap.fixLossOneSingle == first) {
+ if (first.fixNext != head && first.fixNext.fixed() && first.fixNext.loss == 1) {
+ heap.fixLossOneSingle = first.fixNext;
+ } else {
+ heap.fixLossOneSingle = null;
+ }
+ }
+ first.fixListUnlink();
+ heap.fixListInsert(HeapRecord.LOSS_ONE_MULTIPLE, this);
+ heap.fixListInsert(HeapRecord.LOSS_ONE_MULTIPLE, first);
+ }
+ }
+ }
+ }
+ }
+
+ void fixListRemove(HeapRecord<K, V> heap) {
+ Node<K, V> succ = fixNext;
+ Node<K, V> head = heap.fixListHead();
+
+ if (active() && rank.free == this) {
+ if (succ != head && succ.free() && succ.rank == rank) {
+ rank.free = succ;
+ } else {
+ rank.free = null;
+ }
+ }
+ if (active() && rank.lossOne == this) {
+ if (succ != head && succ.fixed() && succ.loss == 1 && succ.rank == rank) {
+ rank.lossOne = succ;
+ } else {
+ rank.lossOne = null;
+ }
+ }
+
+ if (heap.size == 1) {
+ heap.retireFixList();
+ return;
+ }
+
+ if (passive()) {
+ if (this == heap.fixPassive) {
+ if (succ != head && succ.passive()) {
+ heap.fixPassive = succ;
+ } else {
+ heap.fixPassive = null;
+ }
+ }
+ } else if (free()) {
+ GroupInfo<K, V> info = new GroupInfo<K, V>();
+ fixListGroup(head, info);
+ int count = info.count;
+ Node<K, V> first = info.first;
+ if (count == 1) {
+ if (heap.fixFreeSingle == this) {
+ if (succ != head && succ.free()) {
+ heap.fixFreeSingle = succ;
+ } else {
+ heap.fixFreeSingle = null;
+ }
+ }
+ } else if (count >= 3) {
+ if (heap.fixFreeMultiple == this) {
+ heap.fixFreeMultiple = succ;
+ }
+ } else {
+ Node<K, V> other = (first != this) ? first : succ;
+ if (heap.fixFreeMultiple == first) {
+ Node<K, V> f = first.fixNext.fixNext;
+ if (f != head && f.free() && f.fixNext != head && f.fixNext.free() && f.rank == f.fixNext.rank) {
+ heap.fixFreeMultiple = f;
+ } else {
+ heap.fixFreeMultiple = null;
+ }
+ }
+ other.fixListUnlink();
+ heap.fixListInsert(HeapRecord.FREE_SINGLE, other);
+ }
+ } else {
+ if (loss == 0) {
+ if (heap.fixLossZero == this) {
+ if (succ != head && succ.fixed() && succ.loss == 0) {
+ heap.fixLossZero = succ;
+ } else {
+ heap.fixLossZero = null;
+ }
+ }
+ } else if (loss >= 2) {
+ if (heap.fixLossTwo == this) {
+ if (succ != head && succ.fixed() && succ.loss >= 2) {
+ heap.fixLossTwo = succ;
+ } else {
+ heap.fixLossTwo = null;
+ }
+ }
+ } else {
+ GroupInfo<K, V> info = new GroupInfo<K, V>();
+ fixListGroup(head, info);
+ int count = info.count;
+ Node<K, V> first = info.first;
+ if (count == 1) {
+ if (heap.fixLossOneSingle == this) {
+ if (succ != head && succ.fixed() && succ.loss == 1) {
+ heap.fixLossOneSingle = succ;
+ } else {
+ heap.fixLossOneSingle = null;
+ }
+ }
+ } else if (count >= 3) {
+ if (heap.fixLossOneMultiple == this) {
+ heap.fixLossOneMultiple = succ;
+ }
+ } else {
+ Node<K, V> other = (first != this) ? first : succ;
+ if (heap.fixLossOneMultiple == first) {
+ Node<K, V> f = first.fixNext.fixNext;
+ if (f != head && f.fixed() && f.loss == 1 && f.fixNext != head && f.fixNext.fixed()
+ && f.fixNext.loss == 1 && f.rank == f.fixNext.rank) {
+ heap.fixLossOneMultiple = f;
+ } else {
+ heap.fixLossOneMultiple = null;
+ }
+ }
+ other.fixListUnlink();
+ heap.fixListInsert(HeapRecord.LOSS_ONE_SINGLE, other);
+ }
+ }
+ }
+ fixListUnlink();
+ }
+
+ }
+
+ // --------------------------------------------------------------------
+ static class Rank<K, V> implements Serializable {
+
+ private final static long serialVersionUID = 1;
+
+ int rank;
+ Rank<K, V> prev;
+ Rank<K, V> next;
+ HeapRecord<K, V> heap;
+ Node<K, V> free;
+ Node<K, V> lossOne;
+ int referenceCount;
+
+ Rank(HeapRecord<K, V> heap, int rank) {
+ this.next = null;
+ this.prev = null;
+ this.heap = heap;
+ this.rank = rank;
+ this.free = null;
+ this.lossOne = null;
+ this.referenceCount = 0;
+ }
+
+ void retire() {
+ if (heap.rankList == this) {
+ heap.rankList = this.next;
+ }
+ if (this.next != null) {
+ this.next.prev = this.prev;
+ }
+ if (this.prev != null) {
+ this.prev.next = this.next;
+ }
+ this.next = null;
+ this.prev = null;
+ this.heap = null;
+ this.free = null;
+ this.lossOne = null;
+ }
+
+ void increaseReferenceCount() {
+ referenceCount++;
+ }
+
+ void decreaseReferenceCount() {
+ referenceCount--;
+ if (referenceCount == 0) {
+ retire();
+ }
+ }
+
+ Rank<K, V> next() {
+ if (next == null || next.rank > rank + 1) {
+ new Rank<K, V>(heap, rank + 1).insertAfter(this);
+ }
+ return next;
+ }
+
+ Rank<K, V> prev() {
+ if (prev == null || prev.rank < rank - 1) {
+ new Rank<K, V>(heap, rank - 1).insertAfter(prev);
+ }
+ return prev;
+ }
+
+ void insertAfter(Rank<K, V> previousRank) {
+ this.prev = previousRank;
+ this.next = previousRank.next;
+ previousRank.next = this;
+ if (this.next != null) {
+ this.next.prev = this;
+ }
+ }
+
+ }
+
+ /*
+ * Tiny mutable holder replacing the (count, first) tuple returned by the
+ * reference's fix_list_group.
+ */
+ private static class GroupInfo<K, V> {
+ int count;
+ Node<K, V> first;
+ }
+
+}
=====================================
src/test/java/org/jheaps/tree/AbstractAddressableHeapTest.java
=====================================
@@ -19,6 +19,7 @@ package org.jheaps.tree;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
@@ -27,7 +28,10 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Comparator;
+import java.util.List;
import java.util.NoSuchElementException;
import java.util.Random;
@@ -772,4 +776,146 @@ public abstract class AbstractAddressableHeapTest {
assertEquals(comparator, h.comparator());
}
+ @Test(expected = NullPointerException.class)
+ public void testInsertNullKeySingleArg() {
+ AddressableHeap<Integer, Void> h = createHeap();
+ h.insert(null);
+ }
+
+ @Test
+ public void testInsertReturnsNullValue() {
+ AddressableHeap<Integer, Void> h = createHeap();
+ assertNull(h.insert(5).getValue());
+ }
+
+ @Test
+ public void testClearAndReuse() {
+ AddressableHeap<Integer, Void> h = createHeap();
+
+ for (int i = 0; i < 15; i++) {
+ h.insert(i);
+ }
+
+ h.clear();
+ assertEquals(0L, h.size());
+ assertTrue(h.isEmpty());
+
+ h.insert(780);
+ assertEquals(h.size(), 1);
+ assertEquals(Integer.valueOf(780), h.findMin().getKey());
+
+ h.insert(-389);
+ assertEquals(h.size(), 2);
+ assertEquals(Integer.valueOf(-389), h.findMin().getKey());
+
+ h.deleteMin();
+ assertEquals(h.size(), 1);
+ assertEquals(Integer.valueOf(780), h.findMin().getKey());
+
+ h.deleteMin();
+ assertEquals(h.size(), 0);
+ assertTrue(h.isEmpty());
+ }
+
+ @Test
+ public void testDuplicateKeys() {
+ AddressableHeap<Integer, Void> h = createHeap();
+
+ Integer[] keys = { 5, 3, 5, 1, 3, 3, 1, 5 };
+ for (Integer k : keys) {
+ h.insert(k);
+ }
+
+ List<Integer> drained = new ArrayList<>();
+ while (!h.isEmpty()) {
+ drained.add(h.deleteMin().getKey());
+ }
+
+ Integer[] expected = keys.clone();
+ Arrays.sort(expected);
+ assertEquals(Arrays.asList(expected), drained);
+ }
+
+ @Test
+ public void testSortWithDuplicatesRandomSeed1() {
+ AddressableHeap<Integer, Void> h = createHeap();
+
+ Random generator = new Random(1);
+
+ for (int i = 0; i < SIZE; i++) {
+ h.insert(generator.nextInt(1000));
+ }
+
+ Integer prev = null, cur;
+ int count = 0;
+ while (!h.isEmpty()) {
+ cur = h.deleteMin().getKey();
+ if (prev != null) {
+ assertTrue(prev.compareTo(cur) <= 0);
+ }
+ prev = cur;
+ count++;
+ }
+ assertEquals(SIZE, count);
+ }
+
+ @Test
+ public void testNegativeAndDuplicateKeys() {
+ AddressableHeap<Integer, Void> h = createHeap();
+
+ Random generator = new Random(3);
+
+ for (int i = 0; i < SIZE; i++) {
+ h.insert(generator.nextInt(2001) - 1000);
+ }
+
+ Integer prev = null, cur;
+ int count = 0;
+ while (!h.isEmpty()) {
+ cur = h.deleteMin().getKey();
+ if (prev != null) {
+ assertTrue(prev.compareTo(cur) <= 0);
+ }
+ prev = cur;
+ count++;
+ }
+ assertEquals(SIZE, count);
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testDecreaseKeyStressRandom() {
+ AddressableHeap<Integer, Void> h = createHeap();
+
+ AddressableHeap.Handle<Integer, Void> array[];
+ array = new AddressableHeap.Handle[SIZE];
+ int[] currentKey = new int[SIZE];
+ for (int i = 0; i < SIZE; i++) {
+ currentKey[i] = 2 * i;
+ array[i] = h.insert(currentKey[i]);
+ }
+
+ Random generator = new Random(1);
+ for (int i = 0; i < SIZE / 2; i++) {
+ int idx = generator.nextInt(SIZE);
+ if (currentKey[idx] > 0) {
+ int newKey = generator.nextInt(currentKey[idx]);
+ array[idx].decreaseKey(newKey);
+ currentKey[idx] = newKey;
+ }
+ }
+
+ Integer prev = null, cur;
+ int count = 0;
+ while (!h.isEmpty()) {
+ cur = h.deleteMin().getKey();
+ if (prev != null) {
+ assertTrue(prev.compareTo(cur) <= 0);
+ }
+ prev = cur;
+ count++;
+ }
+ assertEquals(SIZE, count);
+ }
+
}
=====================================
src/test/java/org/jheaps/tree/AbstractMergeableAddressableHeapTest.java
=====================================
@@ -464,4 +464,111 @@ public abstract class AbstractMergeableAddressableHeapTest {
assertTrue(h1.isEmpty());
}
+ @Test
+ public void testMeldEvenOddSizes() {
+ MergeableAddressableHeap<Integer, String> a = createHeap();
+ a.insert(10);
+ a.insert(11);
+ a.insert(12);
+ a.insert(13);
+
+ MergeableAddressableHeap<Integer, String> b = createHeap();
+ b.insert(14);
+ b.insert(15);
+ b.insert(16);
+
+ a.meld(b);
+
+ assertEquals(7, a.size());
+ assertTrue(b.isEmpty());
+ assertEquals(0, b.size());
+
+ assertEquals(Integer.valueOf(10), a.findMin().getKey());
+ }
+
+ @Test
+ public void testMeldOddOddSizes() {
+ MergeableAddressableHeap<Integer, String> a = createHeap();
+ a.insert(10);
+ a.insert(11);
+ a.insert(12);
+
+ MergeableAddressableHeap<Integer, String> b = createHeap();
+ b.insert(14);
+ b.insert(15);
+ b.insert(16);
+
+ a.meld(b);
+
+ assertEquals(6, a.size());
+ assertTrue(b.isEmpty());
+ assertEquals(0, b.size());
+
+ assertEquals(Integer.valueOf(10), a.findMin().getKey());
+ }
+
+ @Test
+ public void testMeldOddEvenSizes() {
+ MergeableAddressableHeap<Integer, String> a = createHeap();
+ a.insert(10);
+ a.insert(11);
+ a.insert(12);
+
+ MergeableAddressableHeap<Integer, String> b = createHeap();
+ b.insert(13);
+ b.insert(14);
+ b.insert(15);
+ b.insert(16);
+
+ a.meld(b);
+
+ assertEquals(7, a.size());
+ assertTrue(b.isEmpty());
+ assertEquals(0, b.size());
+
+ assertEquals(Integer.valueOf(10), a.findMin().getKey());
+ }
+
+ @Test
+ public void testMeldEmptyIntoEmpty() {
+ MergeableAddressableHeap<Integer, String> a = createHeap();
+ MergeableAddressableHeap<Integer, String> b = createHeap();
+
+ a.meld(b);
+
+ assertTrue(a.isEmpty());
+ assertEquals(0, a.size());
+ assertTrue(b.isEmpty());
+ assertEquals(0, b.size());
+ }
+
+ @Test
+ public void testMeldThenClearAndReuse() {
+ MergeableAddressableHeap<Integer, String> a = createHeap();
+ a.insert(10);
+ a.insert(11);
+
+ MergeableAddressableHeap<Integer, String> b = createHeap();
+ b.insert(12);
+ b.insert(13);
+
+ a.meld(b);
+ assertEquals(4, a.size());
+
+ a.clear();
+ assertEquals(0, a.size());
+ assertTrue(a.isEmpty());
+
+ a.insert(20);
+ assertEquals(1, a.size());
+ assertEquals(Integer.valueOf(20), a.findMin().getKey());
+
+ a.insert(5);
+ assertEquals(2, a.size());
+ assertEquals(Integer.valueOf(5), a.findMin().getKey());
+
+ a.deleteMin();
+ assertEquals(Integer.valueOf(20), a.findMin().getKey());
+ }
+
}
=====================================
src/test/java/org/jheaps/tree/BinaryTreeSoftHeapTest.java
=====================================
@@ -33,7 +33,6 @@ import java.util.Random;
import org.jheaps.Heap;
import org.jheaps.MergeableHeap;
-import org.jheaps.tree.BinaryTreeSoftHeap;
import org.jheaps.tree.BinaryTreeSoftHeap.RootListNode;
import org.jheaps.tree.BinaryTreeSoftHeap.SoftHandle;
import org.jheaps.tree.BinaryTreeSoftHeap.TreeNode;
@@ -85,7 +84,7 @@ public class BinaryTreeSoftHeapTest {
final int n = SIZE;
double epsilon = 1.0 / (n + 1);
- BinaryTreeSoftHeap<Integer> a = new BinaryTreeSoftHeap<Integer>(epsilon, comparator);
+ BinaryTreeSoftHeap<Integer> a = new BinaryTreeSoftHeap<>(epsilon, comparator);
for (int i = 0; i < n; i++) {
a.insert(i);
@@ -100,7 +99,7 @@ public class BinaryTreeSoftHeapTest {
@Test
public void testSort1RandomSeed1() {
- Heap<Integer> h = new BinaryTreeSoftHeap<Integer>(1.0 / (SIZE + 1));
+ Heap<Integer> h = new BinaryTreeSoftHeap<>(1.0 / (SIZE + 1));
Random generator = new Random(1);
@@ -120,7 +119,7 @@ public class BinaryTreeSoftHeapTest {
@Test
public void testSort1RandomSeed1WithComparator() {
- Heap<Integer> h = new BinaryTreeSoftHeap<Integer>(1.0 / (SIZE + 1), comparator);
+ Heap<Integer> h = new BinaryTreeSoftHeap<>(1.0 / (SIZE + 1), comparator);
Random generator = new Random(1);
@@ -140,14 +139,14 @@ public class BinaryTreeSoftHeapTest {
@Test
public void testComparator() {
- Heap<Long> h = new BinaryTreeSoftHeap<Long>(0.5);
+ Heap<Long> h = new BinaryTreeSoftHeap<>(0.5);
assertNull(h.comparator());
}
@Test
public void testFindMinDeleteMinSameObject() {
- Heap<Long> h = new BinaryTreeSoftHeap<Long>(0.5);
+ Heap<Long> h = new BinaryTreeSoftHeap<>(0.5);
Random generator = new Random(1);
=====================================
src/test/java/org/jheaps/tree/PurePairingHeapAddressableHeapTest.java
=====================================
@@ -0,0 +1,40 @@
+/*
+ * (C) Copyright 2014-2026, by Dimitrios Michail
+ *
+ * JHeaps Library
+ *
+ * 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
+ *
+ * http://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.
+ */
+package org.jheaps.tree;
+
+import java.util.Comparator;
+
+import org.jheaps.AddressableHeap;
+
+public class PurePairingHeapAddressableHeapTest extends AbstractAddressableHeapTest {
+
+ @Override
+ protected AddressableHeap<Integer, Void> createHeap() {
+ return new PurePairingHeap<Integer, Void>();
+ }
+
+ @Override
+ protected AddressableHeap<Integer, Void> createHeap(Comparator<Integer> comparator) {
+ return new PurePairingHeap<Integer, Void>(comparator);
+ }
+
+ @Override
+ protected AddressableHeap<Integer, String> createHeapWithStringValues() {
+ return new PurePairingHeap<Integer, String>();
+ }
+}
=====================================
src/test/java/org/jheaps/tree/PurePairingHeapMergeableAddressableHeapTest.java
=====================================
@@ -0,0 +1,36 @@
+/*
+ * (C) Copyright 2014-2026, by Dimitrios Michail
+ *
+ * JHeaps Library
+ *
+ * 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
+ *
+ * http://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.
+ */
+package org.jheaps.tree;
+
+import java.util.Comparator;
+
+import org.jheaps.MergeableAddressableHeap;
+import org.jheaps.tree.PurePairingHeap;
+
+public class PurePairingHeapMergeableAddressableHeapTest extends AbstractMergeableAddressableHeapTest {
+
+ protected MergeableAddressableHeap<Integer, String> createHeap() {
+ return new PurePairingHeap<Integer, String>();
+ }
+
+ @Override
+ protected MergeableAddressableHeap<Integer, String> createHeap(Comparator<Integer> comparator) {
+ return new PurePairingHeap<Integer, String>(comparator);
+ }
+
+}
=====================================
src/test/java/org/jheaps/tree/StrictFibonacciHeapAddressableHeapTest.java
=====================================
@@ -0,0 +1,40 @@
+/*
+ * (C) Copyright 2014-2026, by Dimitrios Michail
+ *
+ * JHeaps Library
+ *
+ * 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
+ *
+ * http://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.
+ */
+package org.jheaps.tree;
+
+import java.util.Comparator;
+
+import org.jheaps.AddressableHeap;
+
+public class StrictFibonacciHeapAddressableHeapTest extends AbstractAddressableHeapTest {
+
+ @Override
+ protected AddressableHeap<Integer, Void> createHeap() {
+ return new StrictFibonacciHeap<Integer, Void>();
+ }
+
+ @Override
+ protected AddressableHeap<Integer, Void> createHeap(Comparator<Integer> comparator) {
+ return new StrictFibonacciHeap<Integer, Void>(comparator);
+ }
+
+ @Override
+ protected AddressableHeap<Integer, String> createHeapWithStringValues() {
+ return new StrictFibonacciHeap<Integer, String>();
+ }
+}
=====================================
src/test/java/org/jheaps/tree/StrictFibonacciHeapMergeableAddressableHeapTest.java
=====================================
@@ -0,0 +1,36 @@
+/*
+ * (C) Copyright 2014-2026, by Dimitrios Michail
+ *
+ * JHeaps Library
+ *
+ * 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
+ *
+ * http://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.
+ */
+package org.jheaps.tree;
+
+import java.util.Comparator;
+
+import org.jheaps.MergeableAddressableHeap;
+
+public class StrictFibonacciHeapMergeableAddressableHeapTest extends AbstractMergeableAddressableHeapTest {
+
+ @Override
+ protected MergeableAddressableHeap<Integer, String> createHeap() {
+ return new StrictFibonacciHeap<Integer, String>();
+ }
+
+ @Override
+ protected MergeableAddressableHeap<Integer, String> createHeap(Comparator<Integer> comparator) {
+ return new StrictFibonacciHeap<Integer, String>(comparator);
+ }
+
+}
=====================================
src/test/java/org/jheaps/tree/StrictFibonacciHeapValidationTest.java
=====================================
@@ -0,0 +1,444 @@
+/*
+ * (C) Copyright 2014-2026, by Dimitrios Michail
+ *
+ * JHeaps Library
+ *
+ * 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
+ *
+ * http://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.
+ */
+package org.jheaps.tree;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+import org.jheaps.AddressableHeap;
+import org.jheaps.tree.StrictFibonacciHeap.HeapRecord;
+import org.jheaps.tree.StrictFibonacciHeap.Node;
+import org.jheaps.tree.StrictFibonacciHeap.Rank;
+import org.junit.Test;
+
+/**
+ * An extra, non-mandatory stress test for {@link StrictFibonacciHeap}, ported from the
+ * structural-invariant validator ({@code Heap.validate}) and random-operation driver
+ * ({@code test_random_operations}) of the paper's own reference implementation
+ * (https://github.com/gsbrodal/strict-fibonacci-heaps). Given how intricate the
+ * fix-list bookkeeping of this data structure is, this exercises a long random sequence
+ * of every operation and, after each one, walks the whole internal representation of
+ * every live heap re-checking invariants I1-I4 from the paper (node ranks/loss/degree
+ * bounds, fix-list partition consistency, rank-list consistency), rather than only
+ * checking externally observable sortedness like the shared {@code AbstractAddressableHeapTest}
+ * suite does.
+ */
+public class StrictFibonacciHeapValidationTest {
+
+ @Test
+ public void testRandomOperationsSmall() {
+ randomOperations(2000, 1, 50);
+ }
+
+ @Test
+ public void testRandomOperationsMedium() {
+ randomOperations(4000, 2, 300);
+ }
+
+ @Test
+ public void testRandomOperationsLarge() {
+ randomOperations(20000, 3, 5000);
+ }
+
+ @Test
+ public void testRandomOperationsLargeFewDistinctKeys() {
+ // stresses the tiebreak logic with many equal keys
+ randomOperations(20000, 4, 5);
+ }
+
+ // ------------------------------------------------------------------
+ // random operation driver
+ // ------------------------------------------------------------------
+
+ private static class TrackedHeap {
+ final StrictFibonacciHeap<Integer, Void> heap = new StrictFibonacciHeap<Integer, Void>();
+ final List<Integer> keys = new ArrayList<Integer>();
+ final List<AddressableHeap.Handle<Integer, Void>> handles = new ArrayList<AddressableHeap.Handle<Integer, Void>>();
+ }
+
+ private static TrackedHeap popRandom(List<TrackedHeap> list, Random random) {
+ int idx = random.nextInt(list.size());
+ TrackedHeap h = list.get(idx);
+ int last = list.size() - 1;
+ list.set(idx, list.get(last));
+ list.remove(last);
+ return h;
+ }
+
+ private void randomOperations(int opCount, long seed, int keyBound) {
+ Random random = new Random(seed);
+ List<TrackedHeap> heaps = new ArrayList<TrackedHeap>();
+
+ for (int iteration = 0; iteration < opCount; iteration++) {
+ double p = random.nextDouble();
+ if (heaps.isEmpty() || p < 0.05) {
+ heaps.add(new TrackedHeap());
+ } else if (p < 0.10) {
+ if (heaps.size() >= 2) {
+ TrackedHeap h1 = popRandom(heaps, random);
+ TrackedHeap h2 = popRandom(heaps, random);
+ h1.heap.meld(h2.heap);
+ h1.keys.addAll(h2.keys);
+ h1.handles.addAll(h2.handles);
+ validate(h1.heap);
+ heaps.add(h1);
+ }
+ } else if (p < 0.5) {
+ TrackedHeap h = heaps.get(random.nextInt(heaps.size()));
+ if (!h.handles.isEmpty()) {
+ int idx = random.nextInt(h.handles.size());
+ AddressableHeap.Handle<Integer, Void> handle = h.handles.get(idx);
+ int oldKey = handle.getKey();
+ int newKey = oldKey - 1 - random.nextInt(25);
+ handle.decreaseKey(newKey);
+ h.keys.remove(Integer.valueOf(oldKey));
+ h.keys.add(newKey);
+ validate(h.heap);
+ }
+ } else if (p < 0.8) {
+ TrackedHeap h = heaps.get(random.nextInt(heaps.size()));
+ int key = random.nextInt(keyBound);
+ AddressableHeap.Handle<Integer, Void> handle = h.heap.insert(key);
+ h.keys.add(key);
+ h.handles.add(handle);
+ validate(h.heap);
+ } else {
+ TrackedHeap h = heaps.get(random.nextInt(heaps.size()));
+ if (!h.keys.isEmpty()) {
+ int min = Collections.min(h.keys);
+ assertEquals(Integer.valueOf(min), h.heap.findMin().getKey());
+ AddressableHeap.Handle<Integer, Void> deleted = h.heap.deleteMin();
+ h.keys.remove(Integer.valueOf(deleted.getKey()));
+ h.handles.remove(deleted);
+ validate(h.heap);
+ }
+ }
+
+ for (TrackedHeap h : heaps) {
+ assertEquals(h.keys.size(), h.heap.size());
+ List<Integer> actual = new ArrayList<Integer>();
+ for (Node<Integer, Void> n : collectAll(h.heap.rec.root)) {
+ actual.add(n.key);
+ }
+ List<Integer> expected = new ArrayList<Integer>(h.keys);
+ Collections.sort(expected);
+ Collections.sort(actual);
+ assertEquals(expected, actual);
+ }
+ }
+ }
+
+ // ------------------------------------------------------------------
+ // structural invariant validation, ported from the reference's Heap.validate()
+ // ------------------------------------------------------------------
+
+ private static <K, V> List<Node<K, V>> children(Node<K, V> node) {
+ List<Node<K, V>> result = new ArrayList<Node<K, V>>();
+ Node<K, V> child = node.leftChild;
+ if (child != null) {
+ result.add(child);
+ Node<K, V> cur = child.right;
+ while (cur != child) {
+ result.add(cur);
+ cur = cur.right;
+ }
+ }
+ return result;
+ }
+
+ private static <K, V> List<Node<K, V>> collectAll(Node<K, V> node) {
+ List<Node<K, V>> out = new ArrayList<Node<K, V>>();
+ if (node != null) {
+ collectAll(node, out);
+ }
+ return out;
+ }
+
+ private static <K, V> void collectAll(Node<K, V> node, List<Node<K, V>> out) {
+ out.add(node);
+ for (Node<K, V> child : children(node)) {
+ collectAll(child, out);
+ }
+ }
+
+ private static <K, V> void validate(StrictFibonacciHeap<K, V> outer) {
+ HeapRecord<K, V> heap = outer.rec;
+ assertTrue(heap.isActive());
+ validateRankList(heap);
+ validateFixList(heap);
+ if (heap.size == 0) {
+ assertNull(heap.root);
+ return;
+ }
+ assertNotNull(heap.root);
+ List<Node<K, V>> all = collectAll(heap.root);
+ assertEquals(heap.size, (long) all.size());
+
+ int passive = 0;
+ int free = 0;
+ long lossSum = 0;
+ for (Node<K, V> n : all) {
+ if (n.passive()) {
+ passive++;
+ }
+ if (n.free()) {
+ free++;
+ }
+ if (n.fixed()) {
+ lossSum += n.loss;
+ }
+ }
+ double r = 1.25 * (Math.log(all.size()) / Math.log(2)) + 6;
+ double delta = 2.5 * (Math.log(3.0 * all.size() - passive) / Math.log(2)) + 14;
+ // Invariant I2
+ assertTrue(free <= r + 1);
+ // Invariant I3
+ assertTrue(lossSum <= r + 1);
+
+ validateTree(heap.root, null, heap, r, delta);
+ }
+
+ private static <K, V> void validateTree(Node<K, V> node, Node<K, V> parent, HeapRecord<K, V> heap, double r,
+ double delta) {
+ assertSame(parent, node.parent);
+ if (parent != null) {
+ assertTrue(heap.less(parent, node));
+ }
+ assertSame(node, node.right.left);
+ assertSame(node, node.left.right);
+ if (node.passive()) {
+ assertEquals(0, node.heap().size);
+ }
+ if (node.active()) {
+ assertSame(heap, node.heap());
+ }
+ // Invariant I1: fixed nodes have an active parent
+ if (node.fixed()) {
+ assertNotNull(parent);
+ assertTrue(parent.active());
+ }
+
+ List<Node<K, V>> kids = children(node);
+ int fixedCount = 0;
+ boolean allowPassive = true;
+ for (int i = kids.size() - 1; i >= 0; i--) {
+ Node<K, V> child = kids.get(i);
+ if (child.active()) {
+ if (child.fixed()) {
+ fixedCount++;
+ // I1: the i'th rightmost fixed child has rank + loss >= i - 1
+ assertTrue(child.rank.rank + child.loss >= fixedCount - 1);
+ }
+ allowPassive = false;
+ } else {
+ assertTrue(child.passive());
+ assertTrue(allowPassive);
+ }
+ }
+ if (node.active()) {
+ assertEquals(fixedCount, node.rank.rank);
+ }
+ // Invariant I4
+ int degree = kids.size();
+ if (node.active()) {
+ assertTrue(degree <= delta);
+ } else {
+ assertTrue(degree <= delta - 1);
+ }
+ if (node.active()) {
+ assertTrue(node.rank.rank <= r);
+ }
+ for (Node<K, V> child : kids) {
+ validateTree(child, node, heap, r, delta);
+ }
+ }
+
+ private static <K, V> void validateRankList(HeapRecord<K, V> heap) {
+ Rank<K, V> rank = heap.rankList;
+ if (heap.size == 0) {
+ assertNull(rank);
+ return;
+ }
+ assertNotNull(rank);
+ assertNull(rank.prev);
+ while (rank != null) {
+ assertTrue(rank.referenceCount > 0);
+ assertSame(heap, rank.heap);
+ Node<K, V> free = rank.free;
+ if (free != null) {
+ assertTrue(free.free());
+ assertSame(rank, free.rank);
+ }
+ Node<K, V> lossOne = rank.lossOne;
+ if (lossOne != null) {
+ assertTrue(lossOne.fixed());
+ assertEquals(1, lossOne.loss);
+ assertSame(rank, lossOne.rank);
+ }
+ if (rank.next != null) {
+ assertSame(rank, rank.next.prev);
+ assertTrue(rank.next.rank > rank.rank);
+ }
+ rank = rank.next;
+ }
+ }
+
+ private static <K, V> void validateFixList(HeapRecord<K, V> heap) {
+ if (heap.size == 0) {
+ assertNull(heap.fixPassive);
+ assertNull(heap.fixFreeMultiple);
+ assertNull(heap.fixFreeSingle);
+ assertNull(heap.fixLossZero);
+ assertNull(heap.fixLossOneMultiple);
+ assertNull(heap.fixLossOneSingle);
+ assertNull(heap.fixLossTwo);
+ return;
+ }
+
+ Node<K, V> head = heap.fixListHead();
+ assertNotNull(head);
+
+ // verify the cyclic fix-list links
+ Node<K, V> node = head;
+ assertSame(node, node.fixNext.fixPrev);
+ node = node.fixNext;
+ while (node != head) {
+ assertSame(node, node.fixNext.fixPrev);
+ node = node.fixNext;
+ }
+
+ // verify links from the heap record to the fix-list and its grouping
+ node = head;
+ if (heap.fixPassive != null) {
+ assertSame(heap.fixPassive, node);
+ assertTrue(node.passive());
+ node = node.fixNext;
+ while (node != head && node.passive()) {
+ node = node.fixNext;
+ }
+ }
+
+ Set<Integer> freeRanks = new HashSet<Integer>();
+ if (heap.fixFreeMultiple != null) {
+ assertSame(heap.fixFreeMultiple, node);
+ assertNotSame(node, node.fixNext);
+ assertNotSame(head, node.fixNext);
+ assertTrue(node.free());
+ assertTrue(node.fixNext.free());
+ assertSame(node.rank, node.fixNext.rank);
+ assertSame(node, node.rank.free);
+ freeRanks.add(node.rank.rank);
+ node = node.fixNext;
+ while (node != head && node.free() && node.rank == node.fixPrev.rank) {
+ node = node.fixNext;
+ }
+ while (node != head && node.free() && node.fixNext != head && node.fixNext.free()
+ && node.rank == node.fixNext.rank) {
+ assertTrue(freeRanks.add(node.rank.rank));
+ node = node.fixNext;
+ while (node != head && node.free() && node.rank == node.fixPrev.rank) {
+ node = node.fixNext;
+ }
+ }
+ }
+ if (heap.fixFreeSingle != null) {
+ assertSame(heap.fixFreeSingle, node);
+ assertTrue(node.free());
+ assertFalse(freeRanks.contains(node.rank.rank));
+ assertTrue(heap.fixFreeMultiple != null || node.rank.free == node);
+ freeRanks.add(node.rank.rank);
+ node = node.fixNext;
+ while (node != head && node.free()) {
+ assertFalse(freeRanks.contains(node.rank.rank));
+ freeRanks.add(node.rank.rank);
+ node = node.fixNext;
+ }
+ }
+ if (heap.fixLossZero != null) {
+ assertSame(heap.fixLossZero, node);
+ assertTrue(node.fixed());
+ assertEquals(0, node.loss);
+ node = node.fixNext;
+ while (node != head && node.fixed() && node.loss == 0) {
+ node = node.fixNext;
+ }
+ }
+ Set<Integer> lossOneRanks = new HashSet<Integer>();
+ if (heap.fixLossOneMultiple != null) {
+ assertSame(heap.fixLossOneMultiple, node);
+ assertNotSame(node, node.fixNext);
+ assertNotSame(head, node.fixNext);
+ assertTrue(node.fixed());
+ assertTrue(node.fixNext.fixed());
+ assertEquals(1, node.loss);
+ assertEquals(1, node.fixNext.loss);
+ assertSame(node.rank, node.fixNext.rank);
+ assertSame(node, node.rank.lossOne);
+ lossOneRanks.add(node.rank.rank);
+ node = node.fixNext;
+ while (node != head && node.fixed() && node.loss == 1 && node.rank == node.fixPrev.rank) {
+ node = node.fixNext;
+ }
+ while (node != head && node.fixed() && node.loss == 1 && node.fixNext != head && node.fixNext.fixed()
+ && node.fixNext.loss == 1 && node.rank == node.fixNext.rank) {
+ assertTrue(lossOneRanks.add(node.rank.rank));
+ node = node.fixNext;
+ while (node != head && node.fixed() && node.loss == 1 && node.rank == node.fixPrev.rank) {
+ node = node.fixNext;
+ }
+ }
+ }
+ if (heap.fixLossOneSingle != null) {
+ assertSame(heap.fixLossOneSingle, node);
+ assertTrue(node.fixed());
+ assertEquals(1, node.loss);
+ assertFalse(lossOneRanks.contains(node.rank.rank));
+ assertTrue(heap.fixLossOneMultiple != null || node.rank.lossOne == node);
+ lossOneRanks.add(node.rank.rank);
+ node = node.fixNext;
+ while (node != head && node.fixed() && node.loss == 1) {
+ assertFalse(lossOneRanks.contains(node.rank.rank));
+ lossOneRanks.add(node.rank.rank);
+ node = node.fixNext;
+ }
+ }
+ if (heap.fixLossTwo != null) {
+ assertSame(heap.fixLossTwo, node);
+ assertTrue(node.fixed());
+ assertTrue(node.loss >= 2);
+ node = node.fixNext;
+ while (node != head && node.fixed() && node.loss >= 2) {
+ node = node.fixNext;
+ }
+ }
+ assertSame(head, node);
+ }
+
+}
View it on GitLab: https://salsa.debian.org/java-team/jheaps/-/commit/bf308e468ca82a0b6d037b39bb1242cd405a0046
--
View it on GitLab: https://salsa.debian.org/java-team/jheaps/-/commit/bf308e468ca82a0b6d037b39bb1242cd405a0046
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20260908/02910eca/attachment.htm>
More information about the pkg-java-commits
mailing list