Algorithmic heap sort
@author:Jingdai
@date:2020.10.29
Brushing LeetCode 215 questions, using heap sorting, now summarize the knowledge of heap sorting.
Basic knowledge
Complete binary tree
Before talking about the heap, we must first talk about the complete binary tree. Pay attention to the difference between a full binary tree, each layer of a full binary tree must be full, and the last layer of a full binary tree can be dissatisfied (other layers must also be full), and the leaf nodes of the last layer must all be on the left. Row left to right. The following figure is an example of a complete binary tree and an incomplete binary tree.
heap
Heap is a special kind of complete binary tree, divided into big root heap and small root heap. For the large root pile, the value of each node except the root node is less than or equal to the value of its parent node; the small root pile is just the opposite.
Ideas
After understanding the heap, let's take a look at how to use the heap to sort. Generally, the big root heap is used for sorting.
Look at a few properties, when using an array
array
to represent a complete binary tree, the root of the tree isarray[0]
, for an array subscripti
node, its parent node array subscript(i-1)/2
, its left child array subscript2*i+1
, The subscript of its right child array is2*i+2
.The heap sorting can be divided into 2 steps, constructing the initial big root heap and maintaining the big root heap.
First of all, we look at how to maintain a large root heap. For a certain node
i
, when its left subtree is a large root heap and its right subtree is a large root heap , the process of building a heap for this node is called maintaining the large root heap. As shown in the figure below, the node with subscript 1 (brown) is maintained for the big root pile (its left subtree and right subtree are both big root piles), first find the left subtree root, right subtree root and its own largest Value, and then exchange with your own value.However, as shown in the figure, it is found that the node with subscript 3 is not a big root heap again, and the third node needs to be recursively maintained to maintain the big root heap.
Here, the node whose subscript is 4 does not need to be changed, because it has not changed, so it still maintains the nature of its own big root pile. Just pay attention here, that is, when maintaining a large root heap for a node, the left and right subtrees of the node must be the large root heap . Look at the code snippet below to maintain the heap.
public static void heapify(int[] array, int index, int lastElementIndex){ int leftChild = 2 * index + 1; int rightChild = 2 * index + 2; int maxValueIndex = index; if (leftChild <= lastElementIndex && array[leftChild] > array[maxValueIndex]) { maxValueIndex = leftChild; } if (rightChild <= lastElementIndex && array[rightChild] > array[maxValueIndex]) { maxValueIndex = rightChild; } if (maxValueIndex != index) { // swap int temp = array[index]; array[index] = array[maxValueIndex]; array[maxValueIndex] = temp; // recurse heapify(array, maxValueIndex, lastElementIndex); } // else // is already a heap, just return }
Here's
heapify
the code can be optimized, the code is now called recursively needs, but we all know that non-recursive way better than recursive way, so there can be directly replaced by the current node after the exchangemaxValueIndex
, then the same operation before continuing, Until the current node subscript and the maximum node subscript are equal, it means that the maximum heap has been maintained, then exit the loop and change the recursion to a loop. Look at the optimized code below.public static void heapify(int[] array, int index, int lastElementIndex){ int leftChild; int rightChild; int maxValueIndex; while (true) { leftChild = 2 * index + 1; rightChild = 2 * index + 2; maxValueIndex = index; if (leftChild <= lastElementIndex && array[leftChild] > array[maxValueIndex]) { maxValueIndex = leftChild; } if (rightChild <= lastElementIndex && array[rightChild] > array[maxValueIndex]) { maxValueIndex = rightChild; } if (maxValueIndex != index) { // swap int temp = array[index]; array[index] = array[maxValueIndex]; array[maxValueIndex] = temp; index = maxValueIndex; } else { break; } } }
Then we look at the construction of the initial big root heap. If you don’t think about it carefully, you may think of maintaining the big root heap directly from the last node to the root node, so that when each node is traversed, its left and right subtrees have been maintained, and it must be a big root heap. , Can meet the first step to maintain the large root pile conditions. This is not wrong, but it can be optimized, because you will find that when traversing and maintaining the big root heap from back to front, the nodes starting later are all leaf nodes, and the leaf nodes must be big root heaps and do not need to be maintained, so you can directly start from the last non- Start with leaf nodes, which simplifies a lot.
Then the question comes again, what is the index of the last non-leaf node? There is a theorem. For a complete binary tree represented by an array, the subscript of the last non-leaf node is
(lastIndex-1)/2
, in fact, the parent node of the last leaf is the last non-leaf node , so we can directly maintain the big root heap from this node. , An initial large root heap is constructed, see the code for building a large root heap below.public static void buildHeap(int[] array) { int lastElementIndex = array.length - 1; for (int i = (lastElementIndex - 1) / 2; i >= 0; i--) { heapify(array, i, lastElementIndex); } }
After that, the heap sorting is done. After the big root heap is built for the first time, the maximum value of the big root heap is the root of the tree, and the root node and the last node can be swapped, which means that the largest number has been found. What's the next step? Rebuild the big root pile for the first n-1 numbers and find the second smallest number? Of course not, this complexity then becomes
O(n^2)
, and too much trouble orO(n^2)
, is it no one used. When the root node is exchanged, you will actually find that the left and right subtrees of the root node are still big root heaps, so you can directly maintain the big root heap on the root node instead of rebuilding the heap. After the maintenance is completed, you will contact the penultimate The two numbers are exchanged, then maintained, and then exchanged... until the final sorting is completed.Look at the sorted code snippet below.
public static void heapSort(int[] array) { buildHeap(array); for (int i = array.length - 1; i >= 1; i--) { // swap int temp = array[i]; array[i] = array[0]; array[0] = temp; heapify(array, 0, i-1); } }
First built in the time complexity of the heap
O(n)
, and stack each time the maintenance of time complexityO(lgn)
, it requires maintenancen-1
times, so this last time complexityO(nlgn)
.The complete code is as follows.
Code
public static void main(String[] args){ int[] array = { 4, 5, 9, 2, 1, 4, 1, 3, 5, 6, 7}; heapSort(array); System.out.println(Arrays.toString(array)); } public static void heapSort(int[] array) { buildHeap(array); for (int i = array.length - 1; i >= 1; i--) { // swap int temp = array[i]; array[i] = array[0]; array[0] = temp; heapify(array, 0, i-1); } } public static void buildHeap(int[] array) { int lastElementIndex = array.length - 1; for (int i = (lastElementIndex - 1) / 2; i >= 0; i--) { heapify(array, i, lastElementIndex); } } public static void heapify(int[] array, int index, int lastElementIndex){ int leftChild; int rightChild; int maxValueIndex; while (true) { leftChild = 2 * index + 1; rightChild = 2 * index + 2; maxValueIndex = index; if (leftChild <= lastElementIndex && array[leftChild] > array[maxValueIndex]) { maxValueIndex = leftChild; } if (rightChild <= lastElementIndex && array[rightChild] > array[maxValueIndex]) { maxValueIndex = rightChild; } if (maxValueIndex != index) { // swap int temp = array[index]; array[index] = array[maxValueIndex]; array[maxValueIndex] = temp; index = maxValueIndex; } else { break; } } }