Binary heap operations

This article discusses common binary heap operations:

 **Update** | Update the value of the node, and then rearrange the nodes so that they satisfy the heap properties.
 **Get top** | Get the top value of the heap, i.e. the minimum value (in a min-heap) or the maximum value (in a max-heap).
 **Pop top** | Remove the element from the top. This is done by first moving the tail of the heap to the top, and then *percolating down* if necessary.
 **Heapify** | Transform an unsorted array into a heap.

## Get top (min / max)

This is the simplest operation of a binary heap, which does not involve any structural changes to the heap. To get the minimum node in a min-heap, or the maximum node in the max-heap, we just need to return the root node.

If viewed from the array perspective, it means just to return the index 0.

Hence the time complexity is **O(1)**.

## Updating a node

**Updating a node** is divided into two steps:

1. Update the value of the node.
2. Percolate up / down the updated node to make the heap satisfy heap property again.

### Percolate

**Percolating up / down** means to swap a node with its parent / children to make the heap satisfy min-heap or max-heap property.

Here are the rules of percolating actions for a ***min-heap***. Percolating actions on a *max-heap* follow the opposite rules.

Min-heap
* Percolate up
	+ If a node's value < its parent's value, swap this node with its parent.
* Percolate down
	+ If one child's value < the node's value < other child's value, swap the node with the smaller children.
	+ Or, if the node's value > both children's value, swap the node with the smaller children.

Since a complete binary tree with *n* nodes have the height of

⌊

log

2

⁡

n

⌋

{\displaystyle \lfloor\log\_{2}{n}\rfloor}

, the time complexity for percolating up / down is O(log *n*).

Hence, **updating a node**, **inserting a node**, and **deleting a node** all have time complexity **O(log *n*)**.

### Example: update in a min-heap

Original min-heap
Change 1 to 10, and percolate the nodes to make it satisfy the min-heap properties.

```
           1
        /     \
       3       5
      / \     / \
     4   6   7   8

```

Step 1
Change 1 to 10.

```
           10
        /     \
       3       5
      / \     / \
     4   6   7   8

```

Step 2
Percolate down.

```
           3
        /     \
       10      5
      / \     / \
     4   6   7   8

           ↓

           3
        /     \
       4       5
      / \     / \
     10  6   7   8

```

The resulting min-heap

```
           3
        /     \
       4       5
      / \     / \
     10  6   7   8

```

## Inserting a node

**Inserting a node** takes two steps:

1. Append the node at the end.
2. Percolate up the inserted node if necessary.

The time complexity for inserting a node is **O(log *n*)**, i.e. O(*height of tree*).

### Example: insert into a min-heap

Original min-heap
Insert 2 into this min-heap.

```
           1
        /     \
       3       5
      / \     / \
     4   6   7   8

```

Step 1
Put 2 at the end.

```
           1
        /     \
       3       5
      / \     / \
     4   6   7   8
    /
   2

```

Step 2
Percolate up.

```
           1
        /     \
       3       5
      / \     / \
     2   6   7   8
    /
   4
           ↓

           1
        /     \
       2       5
      / \     / \
     3   6   7   8
    /
   4

```

The resulting min-heap

```
           1
        /     \
       2       5
      / \     / \
     3   6   7   8
    /
   4

```

## Deleting the root node

**Deleting the root node** takes three steps:

1. Update the root node with the tail node's value.
2. Delete the tail node (i.e. the original root node).
3. Percolate down the new root node if necessary.

### Example: delete the root

Original min-heap
Delete the root (1) from this min-heap.

```
           1
        /     \
       3       5
      / \     / \
     4   6   7   8

```

Step 1
Remove 1, and put the tail's value (i.e. 8) at the root.

```
           8
        /     \
       3       5
      / \     /
     4   6   7

```

Step 2
Percolate down.

```
           3
        /     \
       8       5
      / \     /
     4   6   7

           ↓

           3
        /     \
       4       5
      / \     /
     8   6   7

```

The resulting min-heap

```
           3
        /     \
       4       5
      / \     /
     8   6   7

```

## Heapify

**Heapify** is reordering an unsorted array, so that it follows either the min-heap or max-heap property.

This is the most important method to deal with arbitrary unsorted arrays.

### Approach 1: Percolate up

To achieve in-place heapifying for the input array, we use one trick: treat the left part as *processed*, and right part as *unprocessed*. From left to right, after each time we process an index, all nodes up to that index is considered "processed", while the remaining nodes are considered "unprocessed". Repeat until all nodes are processed.

For each index *i* the processing steps (*percolating up*) are as follows:

1. Find the parent index of *i*: `parent = (i-1) / 2`.
2. If `array[i] < array[parent]`, swap the two indices.
3. Set `i = parent`.
4. Repeat the steps above until *i* = 0. This means that the percolating process is complete for indices 0 through *i* in the original array.

Then after all elements are processed, the original array is heapified in place.

#### Example: heapify an unsorted array (percolate up)

**Input**
Input array: [6, 2, 8, 9, 1, 4, 3, 5, 7]
Binary tree:
```
             6
          /     \
         2       8
        / \     / \
       9   1   4   3
      / \
     5   7

```

Procedure
Each item number represents the index to be processed.
2. Start with index 0 (value = 6). No change.

```
             6
          /     \
         2       8
        / \     / \
       9   1   4   3
      / \
     5   7

```

5. Percolate up index 1 (value = 2).
```
             2
          /     \
         6       8
        / \     / \
       9   1   4   3
      / \
     5   7

```
6. Percolate up index 2 (value = 8). No change.
```
             2
          /     \
         6       8
        / \     / \
       9   1   4   3
      / \
     5   7

```
7. Percolate up index 3 (value = 9). No change.
```
             2
          /     \
         6       8
        / \     / \
       9   1   4   3
      / \
     5   7

```
8. Percolate up index 4 (value = 1). 1 moves up to the top (height change = 2).
```
             1
          /     \
         2       8
        / \     / \
       9   6   4   3
      / \
     5   7

```
9. Percolate up index 5 (value = 4). 4 moves up by one level.
```
             1
          /     \
         2       4
        / \     / \
       9   6   8   3
      / \
     5   7

```
10. Percolate up index 6 (value = 3). 3 moves up by one level.
```
             1
          /     \
         2       3
        / \     / \
       9   6   8   4
      / \
     5   7

```
11. Percolate up index 7 (value = 5). 5 moves up by one level.
```
             1
          /     \
         2       3
        / \     / \
       5   6   8   4
      / \
     9   7

```
12. Percolate up index 8 (value = 7). no change.
```
             1
          /     \
         2       3
        / \     / \
       5   6   8   4
      / \
     9   7

```

Final output
Output array: [1, 2, 3, 5, 6, 8, 4, 9, 7]

```
             1
          /     \
         2       3
        / \     / \
       5   6   8   4
      / \
     9   7

```

#### Code implementation

##### Java

```
public class Solution {
 // swap two indices
 // assumption: both indices are valid for the array
 private static int[] swap(int[] array, int i1, int i2) {
 int temp = array[i1];
 array[i1] = array[i2];
 array[i2] = temp;
 return array;
 }

 private static int[] percolateUp(int[] array, int index) {
 while (index > 0) {
 int parent = (index - 1) / 2;
 if (array[index] < array[parent]) {
 swap(array, index, parent);
 }
 index = parent;
 }
 return array;
 }

 public int[] heapify(int[] array) {
 // corner cases
 if ((array == null) || (array.length <= 1)) {
 return array;
 }

 // treat left part as processed & right part as unprocessed
 // process each node
 // percolate up if necessary
 for (int i = 0; i < array.length; ++i) {
 percolateUp(array, i);
 }
 return array;
 }
}

```

#### Time complexity analysis

Note that in heapifying an array, as we process each node, we only keep *percolating up* necessary nodes. So for each node the time complexity for percolating up is O(*tree of height*), where the tree of height are as follows:

Total time complexity =

T
(
n

)
1

=
1
⋅

2
1

2
⋅

2
2

3
⋅

2
3

⋯
⌊

log

2

⁡
(
n
1
)
⌋
⋅

2

⌊

log

2

⁡
(
n
1
)
⌋

{\displaystyle T(n)\_1 = 1 \cdot 2^1 + 2 \cdot 2^2 + 3 \cdot 2^3 + \dots + \lfloor \log\_{2}(n+1) \rfloor \cdot 2^{\lfloor \log\_{2}(n+1) \rfloor}}

2
⋅
T
(
n
# )
1
⋅

2
2

2
⋅

2
3

3
⋅

2
4

⋯
⌊

log

2

⁡
(
n
1
)
⌋
⋅

2

⌊

log

2

⁡
(
n
1
)
⌋
1

{\displaystyle 2 \cdot T(n) = 1 \cdot 2^2 + 2 \cdot 2^3 + 3 \cdot 2^4 + \dots + \lfloor \log\_{2}(n+1) \rfloor \cdot 2^{\lfloor \log\_{2}(n+1) \rfloor + 1}}

So

2
⋅
T
(
n
)
−
T
(
n
# )
−

2
1

−

2
2

−

2
3

−
…
−

2

⌊

log

2

⁡
(
n
1
)
⌋

⌊

log

2

⁡
(
n
1
)
⌋
⋅
(
n
1
)
⋅
2

{\displaystyle 2 \cdot T(n) - T(n) = -2^1 - 2^2 - 2^3 - \ldots - 2^{\lfloor \log\_{2}(n+1) \rfloor} + \lfloor \log\_{2}(n+1) \rfloor \cdot (n+1) \cdot 2}

=
⌊

log

2

⁡
(
n
1
)
⌋
⋅
(
n
1
)
⋅
2
−
2
(
1
2

2
2

2
3

⋯

2

⌊

log

2

⁡
(
n
1
)
⌋

)

{\displaystyle = \lfloor \log\_{2}(n+1) \rfloor \cdot (n+1) \cdot 2 - 2(1 + 2 + 2^2 + 2^3 + \dots + 2^{\lfloor \log\_{2}(n+1) \rfloor})}

=
2
(
n
1
)
⌊

log

2

⁡
(
n
1
)
⌋
−
2
⋅

1
−

2

⌊

log

2

⁡
(
n
1
)
⌋

1
−
2

{\displaystyle = 2(n+1) \lfloor \log\_{2}(n+1) \rfloor - 2 \cdot \frac{1 - 2^{\lfloor \log\_{2}(n+1) \rfloor}}{1-2}}

=
2
(
n
1
)
⌊

log

2

⁡
(
n
1
)
⌋
2
(
1
−

2

⌊

log

2

⁡
(
n
1
)
⌋

)

{\displaystyle = 2(n+1) \lfloor \log\_{2}(n+1) \rfloor + 2(1 - 2^{\lfloor \log\_{2}(n+1) \rfloor})}

=
O
(
n
log
⁡
n
)

{\displaystyle = O(n \log n)}

.

*But this is not the optimal approach.* So here is another approach.

### Approach 2: Percolate down

Instead by trying to insert each element at the end and percolate up, we can do the following: for each node that is not a leaf node (i.e. each node that has at least one child), we percolate down that node if necessary. Repeatedly perform the percolate-down action *from the deepest level to the root*.

#### Example: Reversing a max-heap to a min-heap (percolate down)

Input
Input array: [9, 8, 7, 6, 5, 4, 3, 2, 1]
Binary tree representation:
```
           9
        /     \
       8       7
      / \     / \
     6   5   4   3
    / \
   2   1

```

Procedure
The number before each step indicates the index to be processed.
In this example, we start from index 3, which is the last index that is not a leaf node.
3. Process index 3 (value = 6).
Since 1 < 2 < 6, we need to percolate down 6.
```
           9
        /     \
       8       7
      / \     / \
     1   5   4   3
    / \
   2   6

```
4. Process index 2 (value = 7).
Since 3 < 4 < 7, we need to percolate down 7.
```
           9
        /     \
       8       3
      / \     / \
     1   5   4   7
    / \
   2   6

```
5. Process index 1 (value = 8).
Since 1 < 5 < 8, we need to percolate down 8.
```
           9
        /     \
       1       3
      / \     / \
     8   5   4   7
    / \
   2   6
           ↓

           9
        /     \
       1       3
      / \     / \
     2   5   4   7
    / \
   8   6

```
6. Process index 0. (value = 9).
Since 1 < 3 < 9, we need to percolate down 9.
```
           1
        /     \
       9       3
      / \     / \
     8   5   4   7
    / \
   2   6
           ↓

           1
        /     \
       2       3
      / \     / \
     9   5   4   7
    / \
   8   6
           ↓

           1
        /     \
       2       3
      / \     / \
     6   5   4   7
    / \
   8   9

```

Final output
Output array: [1, 2, 3, 6, 5, 4, 7, 8, 9]
Binary tree representation:
```
           1
        /     \
       2       3
      / \     / \
     6   5   4   7
    / \
   8   9

```

#### Code implementation

##### Java

```
public class Solution {
 private static boolean isValidIndex(int[] array, int i) {
 return (i >= 0) && (i < array.length);
 }

 private static int[] swap(int[] array, int i1, int i2) {
 if (!isValidIndex(array, i1) || !isValidIndex(array, i2)) {
 return array;
 }

 int temp = array[i1];
 array[i1] = array[i2];
 array[i2] = temp;
 return array;
 }

 private static int[] percolateDown(int[] array, int index) {
 int leftChildIndex = index \* 2 + 1;
 int rightChildIndex = index \* 2 + 2;

 while (isValidIndex(array, leftChildIndex)) {
 // case 1: both children are valid
 if (isValidIndex(array, rightChildIndex)) {
 int childIndexToSwap = array[leftChildIndex] < array[rightChildIndex] ?
 leftChildIndex : rightChildIndex;
 if (array[index] > array[childIndexToSwap]) {
 swap(array, index, childIndexToSwap);
 index = childIndexToSwap;
 leftChildIndex = index \* 2 + 1;
 rightChildIndex = index \* 2 + 2;
 }
 else {
 // if no more swap is possible
 break;
 }
 }
 // case 2: only left child is valid
 else {
 if (array[index] > array[leftChildIndex]) {
 swap(array, index, leftChildIndex);
 index = leftChildIndex;
 leftChildIndex = index \* 2 + 1;
 rightChildIndex = index \* 2 + 2;
 }
 else {
 // if no more swap is possible
 break;
 }
 }
 }

 return array;
 }

 public int[] heapify(int[] array) {
 // percolate down approach
 for (int i = array.length - 1; i >= 0; --i) {
 percolateDown(array, i);
 }
 return array;
 }
}

```

#### Time complexity analysis

We first note that a binary heap with *n* nodes has the height

⌊

log

2

⁡
(
n
1
)
⌋

{\displaystyle \lfloor\log\_{2}(n+1)\rfloor}

.

Then we need to find the correspondence between each node index *k* and the level of that node:

So for a binary heap with *n* total nodes, for each index *k*, the number of levels to percolate down, in the worst case, is

d
(
k
# )
⌊

log

2

⁡
n
⌋
−
⌊

log

2

⁡
(
k
1
)
⌋
≤

log

2

⁡

n

k
1

1

{\displaystyle d(k) = \lfloor\log\_{2}n\rfloor - \lfloor\log\_{2}(k+1)\rfloor \le \log\_{2}\frac{n}{k+1} + 1}

In terms of the maximum number of levels to percolate down (*d(k)*), we have

Total time complexity to process *n* nodes =

T
(
n

)
2

=

{\displaystyle T(n)\_2 =}

(
⌊

log

2

⁡
n
⌋
−
0
)
⋅

2
0

(
⌊

log

2

⁡
n
⌋
−
1
)
⋅

2
1

(
⌊

log

2

⁡
n
⌋
−
2
)
⋅

2
2

⋯
0
⋅

2

⌊

log

2

⁡
n
⌋

{\displaystyle (\lfloor \log\_{2}n \rfloor - 0) \cdot 2^0 + (\lfloor \log\_{2}n \rfloor - 1) \cdot 2^1 + (\lfloor \log\_{2}n \rfloor - 2) \cdot 2^2 + \dots + 0 \cdot 2^{\lfloor \log\_{2}n \rfloor}}

=
⌊

log

2

⁡
n
⌋
⋅
(

2
0

2
1

2
2

⋯

2

⌊

log

2

⁡
n
⌋

)
−
(
0
⋅

2
0

1
⋅

2
1

2
⋅

2
2

⋯

{\displaystyle = \lfloor \log\_{2}n \rfloor \cdot (2^0 + 2^1 + 2^2 + \dots + 2^{\lfloor \log\_{2}n \rfloor}) - (0 \cdot 2^0 + 1 \cdot 2^1 + 2 \cdot 2^2 + \cdots}

⌊

log

2

⁡
n
⌋
⋅

2

⌊

log

2

⁡
n
⌋

)

{\displaystyle + \lfloor \log\_{2}n \rfloor \cdot 2^{\lfloor \log\_{2}n \rfloor})}

=
⌊

log

2

⁡
n
⌋
⋅

2
(
1
−

2

⌊

log

2

⁡
n
⌋

)

1
−
2

−
2
n
⌊

log

2

⁡
n
⌋
−
2
(
1
−

2

⌊

log

2

⁡
n
⌋

)

{\displaystyle = \lfloor \log\_{2}n \rfloor \cdot \frac{2(1-2^{\lfloor \log\_{2}n \rfloor})}{1-2} - 2n \lfloor \log\_{2}n \rfloor - 2(1 - 2^{\lfloor \log\_{2}n \rfloor})}

=
⌊

log

2

⁡
n
⌋
⋅
2
(

2

⌊

log

2

⁡
n
⌋

−
1
)
−
2
n
⌊

log

2

⁡
n
⌋
−
2
(
1
−

2

⌊

log

2

⁡
n
⌋

)

{\displaystyle = \lfloor \log\_{2}n \rfloor \cdot 2(2^{\lfloor \log\_{2}n \rfloor} - 1)- 2n \lfloor \log\_{2}n \rfloor - 2(1 - 2^{\lfloor \log\_{2}n \rfloor})}

 (from what we had for

T
(
n

)
1

{\displaystyle T(n)\_1}

)

To simplify the approximation, let *n* be some power of 2. Then

T
(
n

)
2

=

log

2

⁡
n
⋅
2
(
n
−
1
)
−
2
n

log

2

⁡
n
−
2
2
n

{\displaystyle T(n)\_2 = \log\_{2}n \cdot 2(n-1) - 2n \log\_{2}n - 2 + 2n}

=
2
n

log

2

⁡
n
−
2

log

2

⁡
n
−
2
n

log

2

⁡
n
−
2
2
n

{\displaystyle = 2n \log\_{2}n - 2 \log\_{2}n - 2n \log\_{2}n - 2 + 2n}

=
−
2

log

2

⁡
n
2
n
−
# 2
O
(
n
)

{\displaystyle = -2 \log\_{2}n + 2n - 2 = O(n)}

.

Hence the **percolate-down approach** gives the total time complexity of **O(n)**.

## External links

* Binary Heaps (Archive)
* Building Heap from Array (Archive)