【排序算法】堆排序
为了实现堆排序,我们先把把这个需要排序的数组看做成一个顺序结构的完全二叉树,如图
为了实现数据的升序排序,我们还要借助“大顶堆”(降序需要小顶堆),而一个大顶堆,需要满足两点:
(1)这个树必须是完全二叉树。
(2)某个节点的值必须大于其两个子节点的值。
我们先手动的把这个二叉树转化为大顶堆。
如图:
这个就是这个二叉树大顶堆的一种,需要注意的是,大顶堆只要求某个节点的值必须大于其两个子节点的值,左右节点的大小是任意的。
我们先把怎么得到这个大顶堆的过程放一下。
对于这个大顶堆,我们需要做怎么样的处理才能使其排序呢?
毫无疑问,这个大顶堆的根节点肯定是最大了,它的值是arr[0],我们现在需要将其与最后一个节点是arr[arr.length-1]进行交换,这样最大的值就在最后了。
此时还有一个问题,交换后的这个二叉树是大顶堆吗,如图,肯定不是的,那么我们需要再次对其进行转化,直到成为大顶堆。然后一直这样进行操作。最后就是排序完成了的数组了。
现在我们回到如何得到大顶堆这个问题里面。
我们可以先实现第一个函数,让其可以对任意节点进行进行左右子节点的判断,判断这两个子节点是否小于自己。
这里需要顺序结构的二叉树的一个知识,也就是第n个节点的左节点的值为:arr[2*n+1],右节点的值为:arr[2*n+2]。
提到交换,我们肯定需要一个交换两个变量的值的方法,为了方便,我们封装一个这样的方法:
代码如下(由于Java中没有像C++的那种&的引用类型或者指针,只能通过数组完成函数内变量的值交换,当然这里也刚好只需要交换数组里面的两个值):
private static void swap(int[] arr, int i, int j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
下面就是如何进行左右节点的判断和交换的代码了:
private static void maxHeap(int[] arr, int len, int k) {
int leftNode = 2 * k + 1;
int rightNode = 2 * k + 2;
int max = k;
if (leftNode < len && arr[max] < arr[leftNode]) {
max = leftNode;
}
if (rightNode < len && arr[max] < arr[rightNode]) {
max = rightNode;
}
if (k != max) {
swap(arr, k, max);
maxHeap(arr, len, max);
}
}
对于交换后为什么还有递归调用一次maxHeap()方法,也就是刚才所说的需要再次检验交换后的节点是否还是不是符合大顶堆。这样我们就可以开始写我们的堆排序代码了。
此外还有一个问题,那就是我们需要从哪里开始转化大顶堆,不难思考从二叉树的下到上转化是比较轻松的,进过找寻规律,可以得出,arr[(arr.length-1)/2]就是二叉树最下面左边的一个节点,通过比较它的两个子节点即可,那我们就从这里开始,代码如下:
public static void sort(int[] arr) {
int begin = ((arr.length - 1) >> 1);
for (int i = begin; i >= 0; i--) {
maxHeap(arr, arr.length, i);
}
for (int i = arr.length - 1; i > 0; i--) {
swap(arr, i, 0);
maxHeap(arr, i, 0);
}
}
完整代码:
import java.util.Arrays;
public class HeapSort {
public static void main(String[] args) {
int[] arr = new int[]{5, 4, 7, 3, 2, 6, 1};
sort(arr);
System.out.println(Arrays.toString(arr));
}
public static void sort(int[] arr) {
int begin = ((arr.length - 1) >> 1);
for (int i = begin; i >= 0; i--) {
maxHeap(arr, arr.length, i);
}
for (int i = arr.length - 1; i > 0; i--) {
swap(arr, i, 0);
maxHeap(arr, i, 0);
}
}
private static void maxHeap(int[] arr, int len, int k) {
int leftNode = 2 * k + 1;
int rightNode = 2 * k + 2;
int max = k;
if (leftNode < len && arr[max] < arr[leftNode]) {
max = leftNode;
}
if (rightNode < len && arr[max] < arr[rightNode]) {
max = rightNode;
}
if (k != max) {
swap(arr, k, max);
maxHeap(arr, len, max);
}
}
private static void swap(int[] arr, int i, int j) {
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
最后通过生成1000次长度为10000的随机数数组,利用刚才写的堆排序与Java自带的排序进行对比检验:
代码:
public static int[] generateRandomArray() {
Random r = new Random();
int[] arr = new int[10000];
for (int i = 0; i < arr.length; i++) {
arr[i] = r.nextInt();
}
return arr;
}
public static void check() {
int cnt = 0;
boolean flag = true;
long before = System.currentTimeMillis();
while (cnt != 1000) {
int[] arr = generateRandomArray();
int[] toCheckArray = new int[arr.length];
System.arraycopy(arr, 0, toCheckArray, 0, arr.length);
Arrays.sort(arr);
HeapSort.sort(toCheckArray);
for (int i = 0; i < arr.length; i++) {
if (arr[i] != toCheckArray[i]) {
flag = false;
break;
}
}
cnt++;
}
System.out.println(flag ? "正确" : "错误");
long after = System.currentTimeMillis();
System.out.println("用时:" + (after - before) + "ms");
}
结果为: