[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 @@
+[![JHeaps](https://github.com/d-michail/jheaps/actions/workflows/master.yaml/badge.svg)](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