当前位置: 首页 > news >正文

堆排序原理与实现详解

我们来详细、系统地解释一下**堆排序**。

堆排序是一种基于**二叉堆**数据结构的比较类排序算法。它以其原址排序(只需要常数级别的额外空间)和O(n log n)的时间复杂度而闻名。

为了理解堆排序,我们需要分步拆解几个核心概念。

1. 什么是“堆”?

这里的“堆”不是指内存管理中的堆,而是一种特殊的**完全二叉树**。它必须满足以下性质之一:

*   **大顶堆**:每个节点的值都**大于或等于**其左右子节点的值。
*   推论:堆顶(根节点)是整个堆中的**最大元素**。
*   **小顶堆**:每个节点的值都**小于或等于**其左右子节点的值。
*   推论:堆顶(根节点)是整个堆中的**最小元素**。

在堆排序中,我们通常使用**大顶堆**。

**重要特性**:堆通常用**数组**来存储。对于一个给定下标 `i` 的节点:
*   其父节点下标:`(i-1) / 2`
*   其左子节点下标:`2*i + 1`
*   其右子节点下标:`2*i + 2`

例如,下面是一个大顶堆及其数组表示:
```
[90]
/  \
[80]   [70]
/  \   /
[40] [30][20]
```
数组:`[90, 80, 70, 40, 30, 20]`

---

 2. 堆排序的核心思想

堆排序的巧妙之处在于利用了大顶堆(或小顶堆)的堆顶是最大(或最小)值这一特性。其基本思想可以概括为两个步骤:

1.  **建堆**:将一个无序的数组构建成一个大顶堆。
2.  **排序**:不断地将堆顶元素(当前最大值)与堆的末尾元素交换,然后缩小堆的范围,并对新的堆顶元素进行“下沉”操作,以重新维持堆的性质。重复此过程,直到堆中只剩下一个元素。

---

### 3. 堆排序的详细步骤

我们以数组 `[4, 10, 3, 5, 1]` 的升序排序为例(使用大顶堆)。

# 步骤一:构建大顶堆

目标是重新排列数组元素,使其满足大顶堆的性质。

1.  **从最后一个非叶子节点开始**。最后一个非叶子节点的下标是 `n/2 - 1`(n是数组长度)。这里 `n=5`,所以从下标 `5/2 - 1 = 1` 开始(即元素 `10`)。
2.  **进行“下沉”操作**:
*   **“下沉”**:对于一个节点,如果它比它的子节点小,就将它与较大的那个子节点交换,并继续向下比较,直到它大于等于它的所有子节点,或者成为叶子节点。
3.  **从下到上,从右到左地对所有非叶子节点执行“下沉”**
*   处理下标1(元素10):它的子节点是下标3(5)和4(1)。10比它们都大,无需下沉。堆状态:`[4, 10, 3, 5, 1]`
*   处理下标0(元素4):它的子节点是下标1(10)和2(3)。4 < 10,所以与10交换。交换后,下标1变成了4。现在4的子节点是下标3(5)和4(1)。4 < 5,所以与5交换。交换后,下标3变成了4,它已经是叶子节点,停止。
*   现在数组变成了:`[10, 5, 3, 4, 1]`。检查一下,这已经是一个大顶堆了。
```
[10]
/  \
[5]   [3]
/ \
[4] [1]
```

步骤二:排序

现在,堆顶元素(下标0)是最大值。

1.  **第一次交换与下沉**:
*   将堆顶元素(10)与当前堆的最后一个元素(1)交换。交换后,数组为 `[1, 5, 3, 4, 10]`。此时,`10` 已经位于其最终的正确位置。我们将堆的大小减1(现在有效的堆范围是前4个元素 `[1, 5, 3, 4]`)。
*   对新的堆顶元素 `1` 进行**下沉**操作,以重新构建大顶堆。
*   1的子节点是5和3。5更大,所以1和5交换。数组变为 `[5, 1, 3, 4, 10]`。
*   现在1(在下标1)的子节点是4(在下标3)。1 < 4,所以交换。数组变为 `[5, 4, 3, 1, 10]`。现在又形成了一个有效的大顶堆(范围在前4个元素)。
2.  **重复此过程**:
*   **第二次**:将堆顶(5)与当前堆的最后一个元素(1)交换。数组变为 `[1, 4, 3, 5, 10]`。`5` 和 `10` 都在最终位置。堆大小减1(有效堆是 `[1, 4, 3]`)。
*   对 `1` 进行下沉:1的子节点是4和3,与4交换。数组变为 `[4, 1, 3, 5, 10]`。
*   **第三次**:将堆顶(4)与当前堆的最后一个元素(3)交换。数组变为 `[3, 1, 4, 5, 10]`。`4`, `5`, `10` 在最终位置。堆大小减1(有效堆是 `[3, 1]`)。
*   对 `3` 进行下沉:3的子节点是1,无需交换。
*   **第四次**:将堆顶(3)与当前堆的最后一个元素(1)交换(也就是自己和自己交换)。数组变为 `[1, 3, 4, 5, 10]`。排序完成。

最终,我们得到了升序排列的数组:`[1, 3, 4, 5, 10]`。

---

4. 算法复杂度分析

*   **时间复杂度**:**O(n log n)**
*   **建堆过程**:看似是O(n log n),但通过精细分析,其平均时间复杂度可以达到**O(n)**。
*   **排序过程**:需要进行n-1次循环,每次循环中主要操作是堆顶的“下沉”,下沉操作的时间复杂度与树高有关,即O(log n)。所以排序过程的时间复杂度是**O(n log n)**。
*   综合起来,堆排序的**最好、最坏、平均**时间复杂度都是 **O(n log n)**。这是一个非常稳定的性能。
*   **空间复杂度**:**O(1)**
*   因为堆排序是**原址排序**,所有操作都在原数组上进行,只使用了常数级别的临时变量。

---

5. 优缺点

**优点**:
*   时间复杂度稳定在O(n log n),效率高。
*   空间复杂度低,是原址排序。

**缺点**:
*   算法不稳定(相等的元素在排序后相对位置可能会改变)。
*   在数据量较小的情况下,其常数因子可能使得性能不如快速排序或归并排序。
*   由于内存访问模式比较跳跃(不像快速排序那样局部顺序访问),对CPU缓存不友好。

---

总结

堆排序是一种高效且节省内存的排序算法。它的核心在于:
1.  **将待排序序列构造成一个大顶堆**。
2.  **不断取出堆顶的最大值,并将其放到序列末尾,然后调整剩余部分使其保持堆结构**。

理解堆排序的关键在于理解**堆的数据结构**和**下沉操作**。

例子构建数字序列 **825479** 的大顶堆。

## 1. 将数组视为完全二叉树

首先,我们将数组 `[8, 2, 5, 4, 7, 9]` 看作一个完全二叉树:

```
8
/ \
2   5
/ \   /
4   7 9
```

数组索引对应关系:
- 索引0: 8
- 索引1: 2  
- 索引2: 5
- 索引3: 4
- 索引4: 7
- 索引5: 9

## 2. 构建大顶堆的过程

**大顶堆性质**:每个节点的值都大于或等于其子节点的值。

我们从最后一个非叶子节点开始向前调整(最后一个非叶子节点下标 = n/2 - 1 = 6/2 - 1 = 2)。

### 步骤1:调整索引2(值为5)
节点5的子节点是9(索引5),5 < 9,需要交换:

```
交换前:
5
/
9

交换后:
9
/
5
```

数组变为:`[8, 2, 9, 4, 7, 5]`

树结构:
```
8
/ \
2   9
/ \   /
4   7 5
```

### 步骤2:调整索引1(值为2)
节点2的子节点是4和7,最大值是7,2 < 7,需要交换:

```
交换前:
2
/ \
4   7

交换后:
7
/ \
4   2
```

数组变为:`[8, 7, 9, 4, 2, 5]`

树结构:
```
8
/ \
7   9
/ \   /
4   2 5
```

### 步骤3:调整索引0(值为8)
节点8的子节点是7和9,最大值是9,8 < 9,需要交换:

```
交换前:
8
/ \
7   9

交换后:
9
/ \
7   8
```

数组变为:`[9, 7, 8, 4, 2, 5]`

树结构:
```
9
/ \
7   8
/ \   /
4   2 5
```

### 步骤4:检查交换后的影响
交换后,索引2的值变为8,需要检查是否需要继续调整:
节点8的子节点是5,8 > 5,不需要调整。

## 3. 最终的大顶堆

**最终数组**:`[9, 7, 8, 4, 2, 5]`

**树形结构**:
```
9
/ \
7   8
/ \   /
4   2 5
```

**验证大顶堆性质**:
- 节点9 ≥ 子节点7和8 ✓
- 节点7 ≥ 子节点4和2 ✓  
- 节点8 ≥ 子节点5 ✓
- 叶子节点4、2、5没有子节点 ✓

原始数组 `[8, 2, 5, 4, 7, 9]` 经过堆化后,得到的大顶堆为 `[9, 7, 8, 4, 2, 5]`。

这个堆满足大顶堆的所有条件,堆顶元素9是整个堆中的最大值。如果要进行堆排序,接下来就可以开始不断取出堆顶元素进行排序了。

http://www.dtcms.com/a/453578.html

相关文章:

  • 网页设计与网站建设过程wordpress淘宝客主题破解版
  • 不关网站备案wordpress安装完成后
  • 分割回文串(dfs)
  • 第二十二章:记忆封存,时光回溯——Memento的备忘录艺术
  • Spring Framework源码解析——ApplicationContextAware
  • 30个做设计的网站wordpress远程图片下载
  • 建网站权威机构西安专业网站建设服务
  • Express+Vue表格数据分页联调:模拟数据与真实接口的无缝切换
  • Qt 多线程与并发编程详解
  • 第五个实验——动态nat地址转换操作
  • 排查 TCP 连接中 TIME_WAIT 状态异常
  • 《C++ 实际应用系列》第二部分:内存管理与性能优化实战
  • 登建设厅锁子的是哪个网站祥云平台网站管理系统
  • 浙江省建设厅网站在哪里手机网站制作招聘
  • nat server 概念及题目
  • 试看30秒做受小视频网站深圳外贸网站制作
  • 网站营销推广怎么做网络营销推广网站建设关于公司怎么写
  • 【AI】专访 Braintrust CEO Ankur Goyal:为什么 AI 评测是产品 prototype 走向生产的唯一桥梁?
  • 大模型文生图和语音转换的调用以及向量和向量数据库RedisStack.
  • 做代练去什么网站安全合肥网站seo整站优化
  • 网站案例展示怎么做桂电做网站的毕设容易过嘛
  • QT-常用控件(一)
  • 网站开发选asp还是hph网站域名解析步骤
  • AI行业应用深度解析:从理论到实践的跨越
  • DeepMind 和罗光记团队 推出“帧链”概念:视频模型或将实现全面视觉理解
  • 外贸圈阿里巴巴微信seo什么意思
  • 【专业词汇】元认知
  • 有什么网站开发软件网页棋牌开发
  • Flutter与Dart结合AI开发实战
  • Easyx使用(数据可视化)