优先队列可以用于以O(NlogN)时间复杂度的排序,基于该思想的算法叫做堆排序(HeapSort)。
二叉堆概念忘记的请看:优先队列的实现与应用
思路与优化
基本思路:
建立N个元素的二叉堆,这个阶段花费O(N)时间,然后执行N次deleteMin操作,最小的元素先出堆(将其记录到第二个数组,因此需要O(N)的空间复杂度),最后得到N个元素的排序。每次deleteMin花费O(logN)时间,因此总时间复杂度为O(NlogN)。
优化思路:
基本思路的主要问题是空间复杂度,它使用了一个额外数组,存储需求增大了一倍。一个优化的方式是,在每次deleteMin后,堆缩小1。因此位于堆中最后的单元可以用来存放刚刚删去的元素。
更细致的思路分析见:堆排序算法 (个人认为写得不错)
Java实现
package algorithmAndDataStructure;
// 该堆排序以小顶堆为例
public class HeapSort {
public static void heapSort(int[] arr) {
if (arr == null || arr.length == 1) return;
// 建堆
buildHeap(arr);
int len = arr.length;
while (len > 1) {
// 把堆顶和最后一个元素交换
swap(arr, 0, len - 1);
// 交换完后,从逻辑上去掉最后一个元素
len--;
// 调整顺序,使其满足小(大)顶堆的性质
heapfy(arr, 0, len);
}
}
// 保持小顶堆
private static void heapfy(int[] arr, int i, int len) {
while (true) {
// 最小值的位置
int minPosition = i;
int leftChild = 2 * i + 1;// 左子结点在数组中的索引
int rightChild = 2 * i + 2;// 右子结点在数组中的索引
// 若左子结点的值大于最大值,更新最大值
if (leftChild < len && arr[leftChild] < arr[minPosition]) {
minPosition = leftChild;
}
// 若右子结点的值大于最大值,更新最大值
if (rightChild < len && arr[rightChild] < arr[minPosition]) {
minPosition = rightChild;
}
if (minPosition == i) {
break; // 表示已建成小顶堆
} else {
swap(arr, i, minPosition);
i = minPosition;
}
}
}
// 交换数值的函数
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 建堆方法
public static void buildHeap(int[] arr) {
// 此处i的值为最后一个非叶子结点的索引位置
for (int i = arr.length / 2 - 1; i >= 0; i--) {
heapfy(arr, i, arr.length);
}
}
// 打印数组
public static void print(int[] arr) {
if (arr == null) return;
for (int i : arr) {
System.out.print(i + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = {3, 7, 11, 0, 8, 5, 18, 9, 1, 2};
System.out.print("排序前: ");
print(arr);
heapSort(arr);
System.out.print("排序后: ");
print(arr);
}
}