1. 问题描述:
对于一个乱序的整型数组,使用小顶堆算法对数组进行排序,输出排序之后的数组
2. 小顶堆算法
堆是一种经过排序的完全二叉树,其中任一非叶子节点的数据值均不大于(或不小于)其左子节点和右子节点的值。
最大堆和最小堆是二叉堆的两种形式
最大堆:根结点的键值是所有堆结点键值中最大者
最小堆:根结点的键值是所有堆结点键值中最小者
我们可以把数组想象成一颗二叉树,比如当数组元素为6个的时候,arr = [9,7,8,4,3,2],那么我们可以将其想象成树的形式为
先根据数组下标逐层画出每一层的元素,元素从左到右从上到下,我们可以根据一个公式来计算出非叶子节点的左子树或者右子树的下标
int left = 2 * n + 1;
int right = 2 * n + 2;
n为父节点的下标,left为左节点的下标,right为右节点的下标
小顶堆算法从数组下标为n / 2 - 1的元素开始堆化,堆化的目的是为了维持每一个非叶子节点都是小于等于它的叶子节点的,最终可以使得堆顶中的元素是最小的,方便以后交换元素
像上面的6个元素的数组我们应该从下标为2即元素值为8的元素开始往上进行堆化,使每一个非叶子节点都维持一个小顶堆
在堆化的过程中我们根据非叶子节点与子节点进行比较,如果非叶子节点大于了子节点那么我们进行元素的交换,比如上面的例子,8这个元素堆化得到的下面的二叉树
这样我们就保证了小范围的小顶堆,但是我们在往上堆化的过程中,我们在上一层可能在堆化的过程中有可能交换元素之后破坏了下面已经维持好的小顶堆,所以这个时候又要进行调整下层的小顶堆,这个时候我们采用的递归调用来调整被破坏的小顶堆,从当前节点与其左孩子或者是右孩子交换的那个孩子节点的下标开始往下调整使得这个位置下面的元素也是满足小顶堆的(往下堆化),这样在n / 2 - 1的位置开始到下标为0的位置都执行堆化的操作最终堆顶的元素肯定是最小的,然后我们将这个元素与数组的最后一个元素进行交换,这样最后一个元素就是最小的元素,对于[0:len(arr) - 2]范围的元素也是执行相同的操作这样倒数第二个元素也可以确定直到下标为0的元素那么排序就完成了,最终我们可以得到从大到小排序的数组
3. 具体的代码如下:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int arr[] = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = sc.nextInt();
}
sort(arr);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
sc.close();
}
private static void sort(int[] arr) {
heap(arr);
for (int i = arr.length - 1; i >= 0; i--) {
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
makeMaxHeap(arr, 0, i);
}
}
private static void makeMaxHeap(int arr[], int i, int n) {
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left >= n) {
return;
}
int min = left;
if (right >= n) {
min = left;
} else {
if (arr[left] > arr[right]) {
min = right;
}
}
if (arr[i] < arr[min]) {
return;
}
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
makeMaxHeap(arr, min, n);
}
private static void heap(int[] arr) {
int n = arr.length;
for (int i = arr.length / 2 - 1; i >= 0; i--) {
makeMaxHeap(arr, i, n);
}
}
}