背景知识
什么是堆
堆就是数据结构中的完全二叉树,是一种非线性数据结构。可以把堆看作一个数组,也可以被看作一个完全二叉树,通俗来讲堆其实就是利用完全二叉树的结构来维护的一维数组。
堆的类型
按照堆的特点可以分为大顶堆和小顶堆。如下图所示。
大顶堆(max heap)
跟节点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆,又称最大堆(大顶堆)。
大顶堆要求根节点的关键字既大于或等于左子树的关键字值,又大于或等于右子树的关键字值。如上图所示。
小顶堆(min heap)
跟节点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者,称为小根堆,又称最小堆(小顶堆)。
小顶堆要求根节点的关键字既小于或等于左子树的关键字值,又小于或等于右子树的关键字值。如上图所示。
数据结构描述
我们采用数组对堆中的结点按层进行编号,参考上图数据,将这种逻辑结构映射到数组中就是下面这个样子:
该数组从逻辑上讲就是一个堆结构,我们用简单的公式来描述一下堆的定义就是:
大顶堆: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个元素的次小值。
4、如此反复执行,便能得到一个有序序列了。
图解算法
假设我们有一个数列 {4, 6, 8, 5, 9}。如下图所示。
步骤一、构造初始堆。
将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。
1、给定无序序列结构如下图所示。下面我们需要将无序堆调整为大顶堆。
2、此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。调整过程如下图所示。
3、找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。调整过程如下图所示。
4、这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
此时,我们就将一个无需序列构造成了一个大顶堆。
步骤二、将堆顶元素与末尾元素进行交换
将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。
1、将堆顶元素9和末尾元素4进行交换。交换过程如下图所示。
2、重新调整结构,使其继续满足堆定义。过程如下图所示。
3、再将堆顶元素8与末尾元素5进行交换,得到第二大元素8。过程如下图所示。
4、后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序。如下图所示。
动画展示
感谢五分钟学算法提供的GIF图片。
算法性能
时间复杂度
O(nlogn)。
空间复杂度
O(1)。
稳定性
不稳定。
代码实现
C和C++
void max_heapify(int arr[], int start, int end) {
// 建立父结点和子节点
int dad = start;
int son = dad * 2 + 1;
while (son <= end) { // 若子节点在范围內才做比较
if (son + 1 <= end && arr[son] < arr[son + 1]) // 先比较两个子节点大小,选择最大的
son++;
if (arr[dad] > arr[son]) // 如果父结点大于子节点代表调整完毕,跳出函数
return;
else { // 否则交换父子内容再继续子节点和孙结点比较
swap(arr[dad], arr[son]);
dad = son;
son = dad * 2 + 1;
}
}
}
void heap_sort(int arr[], int len) {
// 初始化,i從最後一個父節點開始調整
for (int i = len / 2 - 1; i >= 0; i--)
max_heapify(arr, i, len - 1);
// 先將第一個元素和已经排好的元素前一位做交換,再從新調整(刚调整的元素之前的元素),直到排序完毕
for (int i = len - 1; i > 0; i--) {
swap(arr[0], arr[i]);
max_heapify(arr, 0, i - 1);
}
}
Java
public class HeapSort implements IArraySort {
@Override
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
int len = arr.length;
buildMaxHeap(arr, len);
for (int i = len - 1; i > 0; i--) {
swap(arr, 0, i);
len--;
heapify(arr, 0, len);
}
return arr;
}
private void buildMaxHeap(int[] arr, int len) {
for (int i = (int) Math.floor(len / 2); i >= 0; i--) {
heapify(arr, i, len);
}
}
private void heapify(int[] arr, int i, int len) {
int left = 2 * i + 1;
int right = 2 * i + 2;
int largest = i;
if (left < len && arr[left] > arr[largest]) {
largest = left;
}
if (right < len && arr[right] > arr[largest]) {
largest = right;
}
if (largest != i) {
swap(arr, i, largest);
heapify(arr, largest, len);
}
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
Python
def buildMaxHeap(arr):
import math
for i in range(math.floor(len(arr)/2),-1,-1):
heapify(arr,i)
def heapify(arr, i):
left = 2*i+1
right = 2*i+2
largest = i
if left < arrLen and arr[left] > arr[largest]:
largest = left
if right < arrLen and arr[right] > arr[largest]:
largest = right
if largest != i:
swap(arr, i, largest)
heapify(arr, largest)
def swap(arr, i, j):
arr[i], arr[j] = arr[j], arr[i]
def heapSort(arr):
global arrLen
arrLen = len(arr)
buildMaxHeap(arr)
for i in range(len(arr)-1,0,-1):
swap(arr,0,i)
arrLen -=1
heapify(arr, 0)
return arr