一、堆排序原理
堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
(1)堆中某个结点的值总是不大于或不小于其父结点的值。
(2)堆总是一棵完全二叉树。
将根结点最大的堆称为最大堆或大根堆,根结点最小的堆称为最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
堆是非线性数据结构,相当于一维数组,有两个直接后继(即左右孩子节点)。
堆的定义如下:n个元素的序列{k1,k2,ki,…,kn},当且仅当满足下关系时,称之为堆。
若序列{k1,k2,…,kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值)。
大顶堆和小顶堆示例:
对堆中的结点,按层进行编号,将这种逻辑结构映射到数组中,就是下面这个样子:
从逻辑上讲,这个数组就是一个堆结构。我们用简单的公式来描述一下堆的定义就是:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
了解这些定义后,我们来看看堆排序的算法思想和基本步骤:
第1步:将待排序序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。
第2步:将其与末尾元素进行交换,此时,末尾元素就是最大值。
第3步:将剩余n-1个元素重新构造成一个大顶堆,将堆顶的根节点与这n-1个元素序列的末尾元素进行交换。
第4步:如此反复执行,便能得到一个有序序列。
二、堆排序Java实现
2.1 堆排序实现方式1
从根节点开始构建大顶堆,然后将堆顶元素与末尾元素进行交换。将剩余n-1个元素重新构造成一个大顶堆,将堆顶的根节点与这n-1个元素序列的末尾元素进行交换。如此反复执行,便能得到一个有序序列。
package com.mvp.test.sortway;
import java.util.Arrays;
public class HeapSortTest {
public void sort(int[] array) {
if (array == null || array.length <= 1) {
return;
}
// 从根节点开始,构建大顶堆
System.out.println("Before heapInsert " + Arrays.toString(array));
for (int i = 0; i < array.length; i++) {
heapInsert(array, i);
}
System.out.println("After heapInsert " + Arrays.toString(array));
// 利用大顶堆的性质,升序排列
int heapSize = array.length;
swap(array, 0, --heapSize); // 将 索引0处的最大值与索引length-1处的值 进行交换,使得索引heapSize-1处的值为最大值
while (heapSize > 0) {
System.out.println("Before heapify " + Arrays.toString(array));
heapify(array, 0, heapSize); // 将索引0到索引heapSize-1的数组 调整为大顶堆
System.out.println("After heapify " + Arrays.toString(array));
swap(array, 0, --heapSize); // 将 索引0处的最大值与索引heapSize-1处的值 进行交换,使得索引heapSize-1处的值为最大值
}
}
// 构建大顶堆
public void heapInsert(int[] array, int nodeIndex) {
// index > 0 的条件可以省略,原因为:如果index是0,左值和右值都等于a[0],无法进入循环。
int parentIndex = (nodeIndex - 1) / 2;
while (array[parentIndex] < array[nodeIndex]) { // 如果父节点的值小于子节点的值,则交换父节点和子节点的值。
swap(array, nodeIndex, parentIndex);
nodeIndex = parentIndex;
parentIndex = (nodeIndex - 1) / 2;
}
}
// 调整位置,保持索引范围[nodeIndex,heapSize-1]内的元素序列为大顶堆
public void heapify(int[] array, int nodeIndex, int heapSize) {
int leftChildIndex = nodeIndex * 2 + 1;
while (leftChildIndex < heapSize) {
int rightChildIndex = leftChildIndex + 1;
int maxChildIndex = ((rightChildIndex < heapSize) && (array[leftChildIndex] < array[rightChildIndex])) ? rightChildIndex : leftChildIndex;
int maxIndex = array[maxChildIndex] > array[nodeIndex] ? maxChildIndex : nodeIndex;
if (maxIndex == nodeIndex) {
break;
}
swap(array, nodeIndex, maxIndex);
nodeIndex = maxIndex;
leftChildIndex = nodeIndex * 2 + 1;
}
}
public void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
int[] array = {3, 5, 6, 2, 9, 0};
HeapSortTest heapSortTest = new HeapSortTest();
heapSortTest.sort(array);
System.out.println("sorted=" + Arrays.toString(array));
}
}
运行结果如下所示:
Before heapInsert [3, 5, 6, 2, 9, 0]
After heapInsert [9, 6, 5, 2, 3, 0]
Before heapify [0, 6, 5, 2, 3, 9]
After heapify [6, 3, 5, 2, 0, 9]
Before heapify [0, 3, 5, 2, 6, 9]
After heapify [5, 3, 0, 2, 6, 9]
Before heapify [2, 3, 0, 5, 6, 9]
After heapify [3, 2, 0, 5, 6, 9]
Before heapify [0, 2, 3, 5, 6, 9]
After heapify [2, 0, 3, 5, 6, 9]
Before heapify [0, 2, 3, 5, 6, 9]
After heapify [0, 2, 3, 5, 6, 9]
sorted=[0, 2, 3, 5, 6, 9]
2.2 堆排序实现方式2
从倒数第1个非叶子结点开始,从下至上、从右至左构建大顶堆,然后将堆顶元素与末尾元素进行交换。将剩余n-1个元素重新构造成一个大顶堆,将堆顶的根节点与这n-1个元素序列的末尾元素进行交换。如此反复执行,便能得到一个有序序列。
package com.mvp.test.sortway;
import java.util.Arrays;
public class HeapSortAdjustTest {
public static void sort(int[] array) {
if ((array == null) || (array.length <= 1)) {
return;
}
// 1.构建大顶堆
for (int i = array.length / 2 - 1; i >= 0; i--) {
// 从倒数第1个非叶子结点 从下至上、从右至左 调整结构
adjustHeap(array, i, array.length);
}
// 2.调整结构,交换堆顶元素与末尾元素
for (int j = array.length - 1; j > 0; j--) {
swap(array, 0, j); // 将堆顶元素与末尾元素进行交换
adjustHeap(array, 0, j); // 将剩余j-1个元素调整为大顶堆
}
}
/**
* 调整大顶堆
*
* @param array 数组
* @param nodeIndex 下标
* @param length 序列长度
*/
public static void adjustHeap(int[] array, int nodeIndex, int length) {
int temp = array[nodeIndex]; // 先取出当前元素
int rightChildIndex;
int maxChildIndex;
for (int leftChildIndex = nodeIndex * 2 + 1; leftChildIndex < length; ) { // 从nodeIndex结点的左子结点开始,也就是2*nodeIndex+1处开始
rightChildIndex = leftChildIndex + 1;
maxChildIndex = leftChildIndex;
if ((rightChildIndex < length) && (array[leftChildIndex] < array[rightChildIndex])) { // 如果左子结点小于右子结点,k指向右子结点
maxChildIndex = rightChildIndex;
}
if (array[maxChildIndex] > temp) { // 如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
array[nodeIndex] = array[maxChildIndex];
nodeIndex = maxChildIndex;
leftChildIndex = nodeIndex * 2 + 1;
} else {
break;
}
}
array[nodeIndex] = temp; // 将temp值放到最终的位置
}
/**
* 交换元素
*
* @param array 数组
* @param a 下标值a
* @param b 下标值b
*/
public static void swap(int[] array, int a, int b) {
int temp = array[a];
array[a] = array[b];
array[b] = temp;
}
public static void main(String[] args) {
int[] array = {3, 5, 6, 2, 9, 0, 7, 11, 5, 12, 3, 0, 1};
System.out.println("排序前:" + Arrays.toString(array));
sort(array);
System.out.println("排序前:" + Arrays.toString(array));
}
}
运行结果如下所示:
排序前:[3, 5, 6, 2, 9, 0, 7, 11, 5, 12, 3, 0, 1]
排序前:[0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 9, 11, 12]
参考文档: