Algorithm heap sorting (explained in recursive and non-recursive ways)

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.

Insert picture description here

  • 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 arrayto represent a complete binary tree, the root of the tree is array[0], for an array subscript inode, its parent node array subscript (i-1)/2, its left child array subscript 2*i+1, The subscript of its right child array is 2*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.

Insert picture description here

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.

Insert picture description here

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 heapifythe 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 exchange maxValueIndex, 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 or O(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 complexity O(lgn), it requires maintenance n-1times, so this last time complexity O(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;
        }
    }
}

Guess you like

Origin blog.csdn.net/qq_41512783/article/details/109355341