<think>
我们被要求提供堆排序的清晰解释和Python实现代码。堆排序是一种基于二叉堆(Binary Heap)数据结构的比较排序算法。它分为两个主要步骤:建堆(将无序数组构建成堆)和排序(不断移除堆顶元素并调整堆)。
### 堆排序原理
1. **堆的性质**:二叉堆是一个完全二叉树,其每个节点的值总是大于等于(最大堆)或小于等于(最小堆)其子节点的值。
2. **建堆**:从最后一个非叶子节点开始(即索引为$n//2-1$的节点),自底向上调整,使得整个数组满足堆的性质。
3. **排序**:
- 将堆顶元素(当前最大值或最小值)与堆的最后一个元素交换。
- 缩小堆的范围(排除最后一个元素),然后调整堆顶元素,使剩余部分重新满足堆的性质。
- 重复上述过程,直到堆中只剩下一个元素。
堆排序的时间复杂度为$O(n\log n)$,且是原地排序(空间复杂度为$O(1)$)。
### Python实现堆排序(从小到大排序)
下面我们实现一个使用最大堆的堆排序,以实现从小到大的排序:
```python
def heapify(arr, n, i):
"""调整以i为根的子树,使其满足最大堆性质"""
largest = i # 初始化最大值为根节点
left = 2 * i + 1
right = 2 * i + 2
# 如果左子节点存在且大于根节点
if left < n and arr[left] > arr[largest]:
largest = left
# 如果右子节点存在且大于当前最大值
if right < n and arr[right] > arr[largest]:
largest = right
# 如果最大值不是根节点,则交换并继续调整
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
def heap_sort(arr):
n = len(arr)
# 构建最大堆(从最后一个非叶子节点开始)
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)
# 一个一个取出堆顶元素(最大值)
for i in range(n - 1, 0, -1):
# 将当前堆顶(最大值)与堆的最后一个元素交换
arr[i], arr[0] = arr[0], arr[i]
# 调整堆(堆的大小减少1,排除最后一个元素)
heapify(arr, i, 0)
return arr
# 测试
if __name__ == "__main__":
arr = [12, 11, 13, 5, 6, 7]
print("排序前:", arr)
heap_sort(arr)
print("排序后:", arr)
```
### 代码说明:
- `heapify(arr, n, i)`:用于调整以`i`为根的子树,使其满足最大堆性质。参数`n`表示堆的大小(因为排序过程中堆的大小会变)。
- 建堆过程:从最后一个非叶子节点(索引为`n//2-1`)开始,向前遍历到根节点(索引0),对每个节点调用`heapify`。
- 排序过程:每次将堆顶元素(最大值)与当前堆的最后一个元素交换,然后堆的大小减一,再调整堆顶元素。
### 堆排序特性
- **时间复杂度**:建堆过程$O(n)$,每次调整$O(\log n)$,共$n-1$次调整,总时间复杂度为$O(n\log n)$。
- **空间复杂度**:$O(1)$(原地排序)。
- **不稳定性**:堆排序是不稳定的排序算法(因为交换过程中可能改变相同元素的相对位置)[^2]。
如果希望使用最小堆实现从小到大排序,则需要调整堆的性质(将最大堆改为最小堆),但通常我们使用最大堆进行堆排序,因为这样可以直接在数组上操作而不需要额外空间[^2]。
§§相关问题§§
1. 堆排序与快速排序在性能上有何差异?
2. 堆排序为什么是不稳定的排序算法?
3. 如何用最小堆实现从小到大排序?
4. 堆排序在哪些实际场景中应用?
</think>
### 堆排序原理及Python实现
堆排序是一种基于**二叉堆数据结构**的高效排序算法,时间复杂度为$O(n\log n)$。其核心思想是将待排序序列构建成堆,再利用堆的特性进行排序[^2]。
#### 算法步骤:
1. **建堆**:将无序数组构建成二叉堆(最大堆或最小堆)
- 最大堆:每个父节点 ≥ 子节点(用于升序排序)
- 最小堆:每个父节点 ≤ 子节点(用于降序排序)
2. **交换堆顶元素**:堆顶元素(最大值/最小值)与末尾元素交换
3. **调整堆**:缩小堆范围,重新调整剩余元素为有效堆
4. **重复**:重复步骤2-3直到堆大小为1
#### 时间复杂度分析:
| 步骤 | 时间复杂度 |
|------------|------------|
| 建堆 | $O(n)$ |
| 调整堆 | $O(\log n)$ |
| 总体 | $O(n\log n)$ |
#### Python实现(升序排序)
```python
def heapify(arr, n, i):
"""调整以i为根节点的子树为最大堆"""
largest = i # 初始化最大值为根节点
left = 2 * i + 1
right = 2 * i + 2
# 比较左子节点
if left < n and arr[left] > arr[largest]:
largest = left
# 比较右子节点
if right < n and arr[right] > arr[largest]:
largest = right
# 若最大值不在根节点,交换并递归调整
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
def heap_sort(arr):
n = len(arr)
# 构建最大堆(从最后一个非叶子节点开始)
for i in range(n//2 - 1, -1, -1):
heapify(arr, n, i)
# 逐个提取元素排序
for i in range(n-1, 0, -1):
arr[i], arr[0] = arr[0], arr[i] # 交换堆顶和末尾元素
heapify(arr, i, 0) # 调整剩余元素的堆
return arr
# 测试示例
if __name__ == "__main__":
data = [12, 11, 13, 5, 6, 7]
print("排序前:", data)
print("排序后:", heap_sort(data.copy()))
```
#### 执行过程示例:
```
初始数组: [12, 11, 13, 5, 6, 7]
建堆后: [13, 11, 12, 5, 6, 7] # 最大堆
第1轮: 交换13和7 → 调整堆 → [12, 11, 7, 5, 6, 13]
第2轮: 交换12和6 → 调整堆 → [11, 6, 7, 5, 12, 13]
...
最终结果: [5, 6, 7, 11, 12, 13]
```
#### 算法特性:
- **原地排序**:空间复杂度$O(1)$[^3]
- **不稳定排序**:相同值元素可能改变相对顺序
- **适用场景**:大数据量排序、优先级队列实现
- **优势**:最坏情况仍保持$O(n\log n)$时间复杂度