堆排序(Heap Sort)是一种基于二叉堆数据结构的比较排序算法,它是一种选择排序,可分为最大堆排序和最小堆排序,以下主要介绍最大堆排序。
堆排序的基本原理
-
二叉堆的定义:
- 最大堆:对于每个节点
i
(除根节点外),都满足A[parent(i)] >= A[i]
,即父节点的值大于或等于其子节点的值。 - 最小堆:对于每个节点
i
(除根节点外),都满足A[parent(i)] <= A[i]
,即父节点的值小于或等于其子节点的值。 - 对于数组
A
表示的堆,根节点为A[0]
,对于索引为i
的节点,其左子节点索引为2 * i + 1
,右子节点索引为2 * i + 2
,父节点索引为(i - 1) / 2
。
- 最大堆:对于每个节点
-
堆排序的步骤:
- 首先将数组构建成一个最大堆(或最小堆)。
- 交换堆顶元素(最大值或最小值)和堆的最后一个元素。
- 缩小堆的范围,对堆顶元素进行下沉操作(sift down),使其满足堆的性质。
- 重复上述步骤,直到堆的大小为 1。
C++代码实现
#include <iostream>
#include <vector>
// 辅助函数:交换两个元素
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
// 下沉操作,将元素放到合适位置,维护最大堆的性质
void siftDown(std::vector<int>& arr, int start, int end) {
int root = start;
while (2 * root + 1 <= end) {
int child = 2 * root + 1;
int swap_index = root;
// 比较左子节点
if (arr[swap_index] < arr[child]) {
swap_index = child;
}
// 比较右子节点,如果存在且比当前最大的子节点大
if (child + 1 <= end && arr[swap_index] < arr[child + 1]) {
swap_index = child + 1;
}
// 如果需要交换
if (swap_index!= root) {
swap(arr[root], arr[swap_index]);
root = swap_index;
} else {
return;
}
}
}
// 堆排序函数
void heapSort(std::vector<int>& arr) {
int n = arr.size();
// 构建最大堆
for (int i = n / 2 - 1; i >= 0; i--) {
siftDown(arr, i, n - 1);
}
// 逐个将堆顶元素移到末尾,并对堆顶元素进行下沉操作
for (int i = n - 1; i > 0; i--) {
swap(arr[0], arr[i]);
siftDown(arr, 0, i - 1);
}
}
// 打印数组元素
void printArray(const std::vector<int>& arr) {
for (int val : arr) {
std::cout << val << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> arr = {12, 11, 13, 5, 6};
std::cout << "Original array: ";
printArray(arr);
heapSort(arr);
std::cout << "Sorted array: ";
printArray(arr);
return 0;
}
代码解释
-
swap
函数:- 该函数用于交换两个元素的值,接受两个引用参数,使用一个临时变量来交换元素的值。
-
siftDown
函数:- 该函数用于将堆中
start
位置的元素下沉到合适的位置,以保持最大堆的性质。 start
为要下沉元素的位置,end
为堆的最后一个元素的位置。- 首先将
root
设为start
位置。 - 计算
child
为root
的左子节点(2 * root + 1
)。 - 比较
arr[swap_index]
与arr[child]
的大小,若arr[child]
大,更新swap_index
为child
。 - 再比较
arr[swap_index]
与arr[child + 1]
(若存在)的大小,若arr[child + 1]
大,更新swap_index
。 - 若
swap_index
不等于root
,交换arr[root]
与arr[swap_index]
的值,并更新root
为swap_index
。
- 该函数用于将堆中
-
heapSort
函数:- 首先构建最大堆,从最后一个非叶子节点开始,依次对每个节点进行下沉操作,调用
siftDown
函数。 - 构建好最大堆后,将堆顶元素(最大值)与堆的最后一个元素交换。
- 对堆顶元素进行下沉操作,保持最大堆的性质。
- 重复上述步骤,直到堆中只剩下一个元素。
- 首先构建最大堆,从最后一个非叶子节点开始,依次对每个节点进行下沉操作,调用
-
printArray
函数:- 该函数用于打印数组元素,遍历数组并输出元素值。
堆排序的时间复杂度
- 堆排序的平均时间复杂度为
O
(
n
l
o
g
n
)
O(n log n)
O(nlogn),因为构建堆的时间复杂度为
O
(
n
)
O(n)
O(n),而交换堆顶元素和下沉操作的时间复杂度为
O
(
l
o
g
n
)
O(log n)
O(logn),需要执行
n
次。 - 空间复杂度为 O ( 1 ) O(1) O(1),因为堆排序是原地排序,除了输入数组外,不需要额外的存储空间。
堆排序是一种高效的排序算法,尤其适用于处理大量数据的情况,它在性能上较为稳定,并且不需要额外的存储空间。