堆排序
最近学习了堆排序算法。堆排序是一种选择排序,是不稳定的排序,其最坏、平均、最优时间复杂度都为O(nlogn)。堆排序逻辑上是利用完全二叉树对数进行移动,实际是对数组进行操作。
堆排序算法的基本思路:
- 根据升序或降序将数组逻辑上转换成大顶堆或小顶堆;
- 将根节点的数和最后的数交换位置,“沉”在数组的最后,然后继续调整成大顶堆/小顶堆;
- 反复执行步骤2,直至结束。
看了上述步骤之后可能会有这两个问题:1.什么是大顶堆和小顶堆?2.为什么对数组操作,会跟二叉树有关系?
- 首先来解释大顶堆和小顶堆的问题:
大顶堆:对一颗二叉树而言,如果每个节点的值都大于或等于其左右子节点的值,则称为大顶堆;
小顶堆:对一颗二叉树而言,如果每个节点的值都小于或等于其左右子节点的值,则称为小顶堆;
注意:没有要求节点的左右子节点值的大小关系 - 为什么对数组操作,会跟二叉树有关系?
堆排序实际上是在对数组进行操作,但是其逻辑思想是利用完全二叉树来移动数据。首先将一个无序数组按顺序排列成一棵完全二叉树,例如数组arr:{8,6,7,4,3,2,5,1,9},排列成一棵完全二叉树:
- 然后从最后的非叶子节点开始,将这棵完全二叉树调整成大顶堆;
- 再将根节点和最后的节点互换位置,将最大的数放到了数组的最后:
- 将9放到数组末端之后,将不再参与下一轮排序。重新将剩余的数调整为大顶堆。依次类推,直至结束。
其代码实现如下:
package com.tree.heapSort;
import java.util.Arrays;
/*
* 堆排序
* 将数组从小到大排序为例:
* 说明:逻辑上是将数组按顺序排列成一个完全二叉树进行操作,实际上是对数组的操作
* 1.构建大顶堆
* 2.将最开始的节点和末尾节点互换位置,将最大元素“沉”到最后,也就是放在数组的最末尾,然后继续反复调整+交换
*/
public class HeapSortDemo2 {
public static void main(String[] args) {
// 生成一个无序的数组
int[] arr = new int[10];
int num = 0;
int count = 0;
boolean flag = false;
while (count < arr.length) {
num = (int) (Math.random() * 100);
for (int i = 0; i < arr.length; i++) {
if (arr[i] == num) {
flag = true;
break;
}
}
if (!flag) {
arr[count++] = num;
} else {
flag = false;
}
}
System.out.println("原数组为:" + Arrays.toString(arr));
// 堆排序
heapSort(arr);
}
/**
* 堆排序
*
* @param arr
*/
public static void heapSort(int[] arr) {
// 1.将数组转换成大顶堆
// 从最后的非叶子节点开始自下向上调整
for (int i = arr.length / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
System.out.println("数组转换成大顶堆后:" + Arrays.toString(arr));
// 2.将顶端的元素与末尾元素置换位置,然后再继续调整成大顶堆
int temp = 0;
int count = 0;
for (int j = arr.length - 1; j > 0; j--) {
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
// 交换后较小的元素又被放在了根节点,需要重新调整成大顶堆
adjustHeap(arr, 0, j);
System.out.println("第" + (++count) + "轮堆排序后:" + Arrays.toString(arr));
}
System.out.println("堆排序最终结果:" + Arrays.toString(arr));
}
/**
* 将数组调整为大顶堆
*
* @param arr 带调整的数组
* @param i 每次需要调整子树的父节点下标
* @param length 每次需要调整的元素个数,递减
*/
public static void adjustHeap(int[] arr, int i, int length) {
// 将待调整的元素临时保存
int temp = arr[i];
// 开始调整
// 从下标i开始,比较其左右子节点的值是否大于arr[i],如果大于,就和arr[i]互换位置,构建局部大顶堆
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
// k表示下标i元素的左子节点,k+1表示其右子节点,首先判将k移动到较大值的位置
if (k + 1 < length && arr[k] < arr[k + 1]) {// 说明右子节点大于左子节点
k++;// 移动到右子节点
}
// 判断下标k的值是否大于待调整元素,如果大于,就arr[k]=arr[i]
if (arr[k] > temp) {
arr[i] = arr[k];
// 赋值完毕后,要将i移动到k的位置,下一轮比较该位置和其左右子节点的大小
i = k;
} else {
break;
}
}
// 循环结束时,说明已经将最初下标为i的父节点的树调整为大顶堆,并且此时i的位置已经移动
// 需要将temp填在arr[i]的位置
arr[i] = temp;
}
}
如有错误之处,还望指出,定会及时改正。