'Dynamic heap implementation with insert O(log n)
I'm trying to implement a Min Heap in Java, but between studying the complexity and the actual implementation, I realized that it is not clear to me how O (log n) insertion into the heap can be if a dynamic array (such as ArrayList) is used as the base structure.
I understand properly that insert operation in a Heap is O(log n) because we add new item at the end of the array, and we swap each item with his parent, up to a maximum of log n swaps.
But this theorically will works only if you have a base structure big enough to can insert new element: if we need to grow array, operation will take O(n). I know that add element in ArrayList has amortized O(1), but i want to know if is possible to implement the heap by accomplishing with real O(log n) insert operation.
I can use linkedList to accomplish O(1) in insert, but this means that other heap operations (like delete) will no longer have O(log n) complexity.
I also can use a fixed array, but this doesn't allow to insert in heap many elements as you want.
Is there another way? Thanks to all
Solution 1:[1]
You can implement a heap with nodes that are both part of a doubly linked list, and part of a threaded binary tree.
The heap maintains a reference to the root (head) and tail node.
So we can imagine a node having 5 pointers: prev
, next
, left
, right
, parent
. However, we don't need to store right
as -- when it exists -- it is equal to left.next
.
Then the basic operations are as follows:
Extract min:
- Remember the value of the root
- Get the last node (
tail
), copy its value to the root, and remove the node. - Perform the sift down operation to restore the heap property.
- Return the value from the first step
Add
- Create a node with the given value and insert it as last node (and last child), so it becomes
tail
. - Perform the bubble up operation to restore the heap property.
- Create a node with the given value and insert it as last node (and last child), so it becomes
Here is an implementation in Java:
public class Node {
int value;
Node prev = null;
Node next = null;
Node parent = null;
Node left = null;
public Node(int val) { value = val; }
}
class MinHeap {
Node root = null;
Node tail = null;
void insert(int value) {
Node node = new Node(value);
if (root == null) {
root = node;
tail = node;
} else {
// Insert node into tree
Node parent = tail.parent != null ? tail.parent : root;
if (parent.left != null && parent.left.next != null) {
parent = parent.next;
}
if (parent.left == null) {
parent.left = node;
}
node.parent = parent;
// Insert node into doubly linked list
node.prev = tail;
tail.next = node;
tail = node;
// Restore heap property
bubbleUp(node);
}
}
Integer extract() {
if (root == null) return null;
int value = root.value;
Node node = tail;
if (node == root) { // Heap has only one node
root = null;
tail = null;
return value;
}
// Remove node from tree
if (node.parent.left == node) {
node.parent.left = null;
}
// Remove node from doubly linked list
tail = node.prev;
tail.next = null;
// Restore heap property
root.value = node.value;
bubbleDown(root);
return value;
}
void bubbleUp(Node node) {
int value = node.value;
while (node.parent != null && node.parent.value > value) {
node.value = node.parent.value;
node = node.parent;
}
node.value = value;
}
void bubbleDown(Node node) {
int value = node.value;
while (node.left != null) {
Node child = node.left;
if (child.next != null && child.next.value < child.value) {
child = child.next; // Right child
}
if (child.value >= value) break;
node.value = child.value;
node = child;
}
node.value = value;
}
}
public static void main(final String[] args) {
// Demo
MinHeap heap = new MinHeap();
heap.insert(9);
heap.insert(5);
heap.insert(3);
heap.insert(6);
heap.insert(7);
heap.insert(2);
heap.insert(8);
heap.insert(1);
heap.insert(4);
while (heap.root != null) {
System.out.print(heap.extract() + " ");
}
System.out.println();
}
Here is the same as a runnable JavaScript snippet:
class Node {
constructor(value) {
this.value = value;
this.prev = null;
this.next = null;
this.parent = null;
this.left = null;
}
}
class MinHeap {
constructor() {
this.root = null;
this.tail = null;
}
insert(value) {
let node = new Node(value);
if (!this.root) {
this.root = node;
this.tail = node;
} else {
// Insert node into tree
let parent = this.tail.parent || this.root;
if (parent.left?.next) {
parent = parent.next;
}
if (!parent.left) {
parent.left = node;
}
node.parent = parent;
// Insert node into doubly linked list
node.prev = this.tail;
this.tail.next = node;
this.tail = node;
// Restore heap property
this.bubbleUp(node);
}
}
extract() {
if (!this.root) return;
let value = this.root.value;
let node = this.tail;
if (node == this.root) { // Heap has only one node
this.root = null;
this.tail = null;
return value;
}
// Remove node from tree
if (node.parent.left === node) {
node.parent.left = null;
}
// Remove node from doubly linked list
this.tail = node.prev;
this.tail.next = null;
// Restore heap property
this.root.value = node.value;
this.bubbleDown(this.root);
return value;
}
bubbleUp(node) {
let value = node.value;
while (node.parent && node.parent.value > value) {
node.value = node.parent.value;
node = node.parent;
}
node.value = value;
}
bubbleDown(node) {
let value = node.value;
while (node.left) {
let child = node.left;
if (child.next && child.next.value < child.value) {
child = child.next; // Right child
}
if (child.value >= value) break;
node.value = child.value;
node = child;
}
node.value = value;
}
}
// Demo
let heap = new MinHeap;
heap.insert(9);
heap.insert(5);
heap.insert(3);
heap.insert(6);
heap.insert(7);
heap.insert(2);
heap.insert(8);
heap.insert(1);
heap.insert(4);
while (heap.root) {
console.log(heap.extract());
}
As the overhead of this heap is considerable, it is in practice not as fast as a dynamic-array based heap.
Solution 2:[2]
As @trincot answered before, a way to go is to build a linked struct with nodes having left
, right
, parent
pointers to other nodes. This implementation will have an insertion of O(logn) time (because the heap is an almost complete binary tree). In practical terms, though, it would be rather inefficient as it would require to maintain many (at least 3) pointers per node (aka, a memory overhead of at least 3 * 8 = 24 bytes per item).
Having said that, one can omit the prev
, next
pointers (lowering the memory overhead per item) because we can easily compute them (in O(logn) time also).
For example, next
would be (prev
is similar):
Node next(Node t)
{
if (t == null)
return null;
if (t.parent == null)
return t.left; // climb down one level (see @trincot's comment)
// if left child, next is my right sibling
if (t == t.parent.left)
return t.parent.right;
// so, i am right child of my parent
Node p = next(t.parent);
if (p != null) {
return p.left;
} else {
return null; // no next exists
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 |