近日,笔者忙里偷闲,学习了下堆排序(Heap-Sort)。堆排序算法,就时间复杂度而言,堆排序跟合并排序(Merge-Sort)算法是一样的,都是O(n * log(n));就排序方式而言,堆排序跟插入排序(Insertion-Sort)一样,都具有空间原址性。
这里首先介绍下堆的概念。堆,或者称为二叉堆,可以看作是一棵近似的完全二叉树。在这棵树中,除了最底层以外,该树是完全充满的,而且是从左至右填充的(这里笔者手画了几个栗子)。
在二叉堆中,通常使用两个属性:length表示数组长度,heap_size表述还有多少个元素存储在二叉堆中。二叉堆一般具有两种形式:最大堆和最小堆。在最大堆中,除了根结点以外的所有结点i均满足
A[parent(i)] >= A[i]
即某个结点的值,至多与父结点的值相等,最大堆的最大元素存储在根结点上。而最小堆则满足
A[parent(i)] <= A[i]
即某个结点的值,至少都与父结点的值相等,其最小元素存储在根结点上。
在堆排序算法中,一般考虑实现最大堆。考虑随机数组排序成升序数组的栗子,堆排序算法大致流程如下:
- 首先将随机数组建成一个最大堆。
- 从数组最后一位开始向前迭代,直到第二个元素。迭代过程中,将当前最大堆的最大值(数组首)依次从数组最后一位开始向前放置。同时,heap_size自减一,然后调用
maxHeapify
维持当前堆成为一个最大堆。
其中maxHeapify
过程,接受一个数组arr和一个下标i。在调用maxHeapify
时,通常假设根结点为left(i)和right(i)的二叉树是最大堆,但这时的arr[i]有可能小于其子节点,maxHeapify
通过让arr[i]的值在最大堆中“逐渐下降”,从而使得以下标i为根结点的子树满足最大堆的性质。这里笔者以《算法导论》上的栗子,maxHeapify(arr, 2)的执行过程如下。
代码如下:
package org.vimist.pro.Algorithm.Sort;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Random;
/**
* A demo to illustrate how {@code Heap-Sort} works.
*
* @author Mr.K
*/
public class HeapSort {
// length of the array
private static int length = 12;
// size of binary heap
private static int heapSize = length;
// array to be sorted
private static int[] arr = new int[length];
// random number generator
private static Random random = new Random();
public static void main(String[] args) {
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(3 * length);
}
System.out.println("待排序数组: " + Arrays.toString(arr) + "\n");
new HeapSort().heapSort(arr);
System.out.println("\n排序后数组: " + Arrays.toString(arr));
}
/**
* Accepts an array, constructs a <em>Maximum-Heap</em> by that array and
* starts by using that <em>Maximum-Heap</em>.
* <ul>
* <li>In the <em>Maximum-Heap</em>, the largest number is placed at
* position 0. So the first element makes exchange with the last index
* in the range of [0, Heap-Size], from the start at the end of the
* array to the end at second element of the array.</li>
* <li>When exchange has been made, the binary heap in the range of
* [0, Heap-Size] will not be a <em>Maximum-Heap</em>, hence process,
* which aims to maintain the condition of <em>Maximum-Heap</em>, will
* be invoked.</li>
* </ul>
* Generally, {@code HeapSort} may cost O(n * log(n)) with respect to time,
* in some worst cases.
*
* @param arr specified array to be sorted
*/
public void heapSort(@NotNull int[] arr) {
buildMaxHeap(arr);
System.out.println("最大二叉堆: " + Arrays.toString(arr) + "\n");
for (int i = arr.length - 1; i > 0; i--) {
exchange(arr, 0, i);
heapSize--;
maxHeapify(arr, 0);
System.out.println("第" + String.format("%2d", length - i) + "步: " + Arrays.toString(arr));
}
}
/**
* Accepts an array and constructs a <em>Maximum-Heap</em> by the specified
* array. The complexity of time for the method is almost O(n), which means
* this operation can be finished in linear cost of time.
*
* @param arr specified array to construct a maximum-heap
*/
public void buildMaxHeap(@NotNull int[] arr) {
for (int i = length / 2; i >= 0; i--) {
maxHeapify(arr, i);
}
}
/**
* Accepts an array, almost a maximum heap, and an index, which is going
* to be used to ensure that the whole binary heap is a <em>Maximum-Heap
* </em>. The complexity of time for the process is O(h) is the height of
* the binary heap is h.
*
* @param arr specified array, representing the binary heap
* @param index index of node to be re-sorted
*/
public void maxHeapify(@NotNull int[] arr, @NotNull int index) {
int left = left(index), right = right(index), largest;
if (left < heapSize && arr[left] > arr[index]) {
largest = left;
} else {
largest = index;
}
if (right < heapSize && arr[right] > arr[largest]) {
largest = right;
}
if (largest != index) {
exchange(arr, index, largest);
maxHeapify(arr, largest);
}
}
/**
* Accepts an array and two numbers, which are indexes of two element, and
* exchanges these two element.
*
* @param arr specified array
* @param i one of the indexes
* @param j the other one of the indexes
*/
public void exchange(@NotNull int[] arr, @NotNull int i, @NotNull int j) {
int temp = arr[i] ^ arr[j];
arr[i] = temp ^ arr[i];
arr[j] = temp ^ arr[j];
}
/**
* Gets an index and returns the left child node of current node, marked by
* ths specified index.
*
* @param index index of current node
* @return index of the left child node of current node
*/
public int left(@NotNull int index) {
return (index + 1) * 2 - 1;
}
/**
* Gets an index and returns the right child node of current node, marked by
* ths specified index.
*
* @param index index of current node
* @return index of the right child node of current node
*/
public int right(@NotNull int index) {
return (index + 1) * 2;
}
}
运行结果如下:
待排序数组: [23, 15, 8, 12, 26, 25, 28, 6, 6, 7, 26, 20]
最大二叉堆: [28, 26, 25, 12, 26, 23, 8, 6, 6, 7, 15, 20]
第 1步: [26, 26, 25, 12, 20, 23, 8, 6, 6, 7, 15, 28]
第 2步: [26, 20, 25, 12, 15, 23, 8, 6, 6, 7, 26, 28]
第 3步: [25, 20, 23, 12, 15, 7, 8, 6, 6, 26, 26, 28]
第 4步: [23, 20, 8, 12, 15, 7, 6, 6, 25, 26, 26, 28]
第 5步: [20, 15, 8, 12, 6, 7, 6, 23, 25, 26, 26, 28]
第 6步: [15, 12, 8, 6, 6, 7, 20, 23, 25, 26, 26, 28]
第 7步: [12, 7, 8, 6, 6, 15, 20, 23, 25, 26, 26, 28]
第 8步: [8, 7, 6, 6, 12, 15, 20, 23, 25, 26, 26, 28]
第 9步: [7, 6, 6, 8, 12, 15, 20, 23, 25, 26, 26, 28]
第10步: [6, 6, 7, 8, 12, 15, 20, 23, 25, 26, 26, 28]
第11步: [6, 6, 7, 8, 12, 15, 20, 23, 25, 26, 26, 28]
排序后数组: [6, 6, 7, 8, 12, 15, 20, 23, 25, 26, 26, 28]
来源:https://blog.csdn.net/qq_14873461/article/details/102753849