47
loading...
This website collects cookies to deliver better user experience
heapify
and heapSort
. If you are unfamiliar with the heap structure, and the bubbleUp
and trickleDown
manipulations it requires, please first read my previous post1) We create a new empty heap, and then iterate through the array, inserting each element of the array into this heap using the insert
and bubbleUp
functions described in the previous entry. The drawback to this approach, is that not only are we creating an entirely new structure to hold each value, O(n) space, we also need to iterate through each item in the array, O(n), and insert each one, O(log n), which creates an overall time complexity of O(n log n). It works, but we can do better.
bubbleUp()
or trickleDown()
methods.2) Our first option would be to start at the first item in the array (root of the tree), and iterate through each element and invoking bubbleUp
on each one. This would compare each node against its ancestors, and move it further up the tree as necessary. By the time we finish with the last element in the array, each element will have been placed accordingly and we will have our heap.
3) The other option, is to start at the bottom-most node on the tree that has children and iterate up to the root, calling trickleDown
on each one. Like the previous option, this would leave each element correctly place once we finish with the root node.
log n
where n is the number of nodes. In this case, that means 4 tiers. Using the approach in option 2, we could find the worst case total number of swaps by looking at the distance from a node's tier to the root.n/2 * log n
for any given tier, which simplifies to O(n log n) like option 1, but without the extra needed space.trickleDown
on each node, the "swap count" would look very different for our 16 node tree:bubbleUp
. Mathematically, this process comes out to O(n) time and is supported by this proof provided by Jeremy West. With this process, we can turn any array into a heap without extra space, and in constant time.trickleDown
for each node from there to the root. We can find this node by using Math.floor((n - 2)/2)
. Unlike in the previous blog, we want the trickleDown
action to begin at the specified node, and not always at the root, so I have refactored trickleDown
to accept an optional parameters compared to the implementation in my previous post. See the full MaxHeap class gist below for the trickleDown
implementation and the rest of the MaxHeap class implementation.class MaxHeap {
constructor(arr = []){
this.values = this._heapify(arr)
}
_heapify(arr){
if (this.size > 0) return // Optional: Prevent overriding existing heap values
this.size = arr.length
/**
* To prevent mutating current array, copy arr with
* this.values = [...arr]
*/
this.values = arr
const nodeCount = this.size - 1
// Finds the last node of the tree that has children
let cIdx = Math.floor((nodeCount - 2)/2)
/** For each node up through the root,
* call trickleDown
*/
for (let i = cIdx; i >= 0; i--){
this._trickleDown(i)
}
return this.values
}
// See gist for rest of class implementation
}
arr = [17,2,36,100,7,1,19,25,3]
we could model the heapify
action as such:.pop
, nor do we always want to move the extract value to the last index of the array each time. Instead we can use an index pointer to determine where to place the max value, and where to stop the trickleDown
static heapSort(arr){
const heap = new MaxHeap(arr)
for (let i = arr.length - 1; i > 0; i--){
// Place max at pointer position by swapping with root
heap._swap(0,i)
// Begin trickle at root, end before placed value
heap._trickleDown(0, i)
}
return heap.values
}