Home Manual Reference Source

src/PairingHeap.js

import merge from './merge.js';
import mergepairs from './mergepairs.js';
import decreasekey from './decreasekey.js';
import Node from './Node.js';

export default class PairingHeap {
	constructor(compare) {
		this.compare = compare; // Comparison function
		this.min = null; // Root node, must have .prev = .next = null at all times
	}

	/**
	 * Find-min: simply return the top element of the heap.
	 */
	head() {
		if (this.min === null) return undefined;
		return this.min.value;
	}

	headreference() {
		return this.min;
	}

	/**
	 * Delete-min: remove the root and merge its subtrees. Various strategies
	 * are employed.
	 */
	pop() {
		const min = this.popreference();
		return min === null ? undefined : min.value;
	}

	/**
	 */
	popreference() {
		if (this.min === null) return null;
		const min = this.min;
		this.min = mergepairs(this.compare, min.children); // Min.children.next = null
		return min;
	}

	/**
	 * Insert: create a new heap for the inserted element and merge into the
	 * original heap.
	 */
	push(value) {
		const node = new Node(value);
		return this.pushreference(node);
	}

	/**
	 * /!\ ref.next = ref.prev = null which means all references that are
	 * external to the tree must reset .next and .prev and one must not call
	 * PairingHeap#pushreference with an internal reference from this tree or
	 * another, except the root of another tree.
	 */
	pushreference(ref) {
		this.min = this.min === null ? ref : merge(this.compare, this.min, ref); // This.min != null != ref
		return ref;
	}

	/**
	 * Supposes the same comparison function is used in both trees.
	 * We can call pushreference since other.min.next = other.min.prev = null.
	 */
	merge(other) {
		const ref = other.min;
		if (ref !== null) this.pushreference(ref);
	}

	/**
	 * @param {Node} ref Non-null internal node object.
	 * @param {Object} value The new value for ref.
	 */
	update(ref, value) {
		const d = this.compare(value, ref.value);

		if (d < 0) this.decreasekey(ref, value);
		else if (d > 0) this.increasekey(ref, value);
		else ref.value = value;
	}

	/**
	 * Decrease-key
	 *
	 * @param {Node} ref Non-null internal node object.
	 * @param {Object} value The new value for ref.
	 */
	decreasekey(ref, value) {
		if (ref === this.min) ref.value = value;
		else {
			// This.min != null, ref != null
			this.min = decreasekey(this.compare, this.min, ref, value);
		}
	}

	/**
	 * Increase-key: remove the item at the key to be increased, replace
	 * the key with a larger key, then push the result back into the heap.
	 *
	 * @param {Node} ref Non-null internal node object.
	 * @param {Object} value The new value for ref.
	 *
	 */
	increasekey(ref, value) {
		this.delete(ref);

		ref.value = value;

		this.pushreference(ref);
	}

	/**
	 * Ref must be internal
	 * ref.prev and ref.next get reset to null
	 */
	delete(ref) {
		if (ref === this.min) {
			this.popreference();
			return;
		}

		const successor = mergepairs(this.compare, ref.children); // Ref.children.next = null

		// ref has no children
		if (successor === null) {
			//  _       _       _
			// | | --> | | --> | |
			// |_| <-- |_| <-- |_|
			//  P       R       N

			// detach ref and link ref.prev to ref.next
			//
			//  _               _
			// | | ----------> | |
			// |_| <    _   -> |_|
			//  P   \  | | /  / N
			//       - |_| <-/
			//          R
			//
			ref.prev.next = ref.next; // Must be != null because ref != min

			if (ref.next !== null) {
				//
				//  _               _
				// | | ----------> | |
				// |_| <---------- |_|
				//  P       _       N
				//  ^      | | -----^
				//  |----- |_|
				//          R
				//
				ref.next.prev = ref.prev;
				//
				//  _               _
				// | | ----------> | |
				// |_| <---------- |_|
				//  P       _       N
				//  ^      | |
				//  |----- |_|
				//          R
				//
				ref.next = null;
			}

			//
			//  _               _
			// | | ----------> | |
			// |_| <---------- |_|
			//  P       _       N
			//         | |
			//         |_|
			//          R
			//
			ref.prev = null;

			return;
		}

		successor.prev = ref.prev; // Must be != null because ref != min
		successor.prev.next = successor;
		ref.prev = null;

		if (ref.next !== null) {
			successor.next = ref.next; // Might be null
			successor.next.prev = successor;
			ref.next = null;
		}
	}
}