考点频率 :★★★★★(数据结构必考,选择题常考堆的性质与建堆过程,简单选择排序常考时间复杂度)
难度 :⭐⭐⭐⭐(简单选择排序简单,堆排序需要重点理解堆的调整过程)
建议:重点掌握简单选择排序的时间复杂度与稳定性;重点掌握堆的定义(大根堆/小根堆)、建堆过程与堆排序流程
1️⃣ 什么是选择类排序?
选择类排序 的核心思想是:每一轮从待排序序列中选出最小(或最大)的元素,放到已排序序列的末尾。
打个比方:你有一堆大小不一的石头,要从小到大排列。你每次都从剩下的石头中找出最小的那颗,放到已经排好的石头堆后面。重复这个过程,直到所有石头都排完。
两类最经典的选择排序:
| 算法 | 核心策略 | 一句话概括 |
|---|---|---|
| 简单选择排序 | 每轮扫描剩余序列,选最小元素放到前面 | "每轮找最小的,放到最前面" |
| 堆排序 | 利用堆(完全二叉树)快速找到最大/最小元素 | "用堆结构加速选择" |
2️⃣ 简单选择排序
2.1 核心思想
每一轮从待排序序列 中选出最小的元素,放到已排序序列的末尾。
c
void SelectionSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int minIdx = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIdx]) {
minIdx = j;
}
}
if (minIdx != i) {
Swap(&arr[i], &arr[minIdx]);
}
}
}
2.2 执行过程示例
对数组 [5, 3, 8, 1, 4] 进行简单选择排序:
| 轮次 | 未排序部分 | 最小元素 | 交换后结果 |
|---|---|---|---|
| 初始 | [5, 3, 8, 1, 4] |
--- | --- |
| 第1轮 | [5, 3, 8, 1, 4] |
1(下标3) | [1, 3, 8, 5, 4] |
| 第2轮 | [3, 8, 5, 4] |
3(下标1) | [1, 3, 8, 5, 4] |
| 第3轮 | [8, 5, 4] |
4(下标4) | [1, 3, 4, 5, 8] |
| 第4轮 | [5, 8] |
5(下标3) | [1, 3, 4, 5, 8] |
2.3 复杂度与特点
| 情况 | 时间复杂度 | 说明 |
|---|---|---|
| 最好情况 | O(n2)O(n^2)O(n2) | 即使有序,仍需扫描找最小值 |
| 最坏情况 | O(n2)O(n^2)O(n2) | 与最好情况相同 |
| 平均情况 | O(n2)O(n^2)O(n2) | 与最好情况相同 |
| 空间复杂度 | O(1)O(1)O(1) | 原地排序 |
稳定性 :❌ 不稳定 (交换操作可能改变相等元素的相对顺序,如
[2, 2, 1]第一轮会将第一个2与1交换,两个2的相对顺序被破坏)
关键特点 :简单选择排序的比较次数与初始序列无关 ,始终为 n(n−1)/2n(n-1)/2n(n−1)/2 次。
3️⃣ 堆排序(Heap Sort)
3.1 什么是堆?
堆(Heap) 是一种特殊的完全二叉树,满足以下条件:
| 类型 | 条件 | 用途 |
|---|---|---|
| 大根堆(Max Heap) | 任意节点的值 ≥ 其子节点的值 | 升序排序(每次取堆顶最大值) |
| 小根堆(Min Heap) | 任意节点的值 ≤ 其子节点的值 | 降序排序(每次取堆顶最小值) |
堆的存储 :用数组 存储完全二叉树,下标从 111 开始(或从 000 开始,软考中下标从1开始更常见)。
数组下标从 111 开始时:节点 iii 的左子节点为 2i2i2i,右子节点为 2i+12i+12i+1,父节点为 ⌊i/2⌋\lfloor i/2 \rfloor⌊i/2⌋。
3.2 堆排序的核心思想
堆排序利用堆结构快速找到最大(或最小)元素,分为两步:
- 建堆:将无序序列构建成一个大根堆(或小根堆)
- 排序:反复取出堆顶元素(最大值),放到数组末尾,然后对剩余元素重新调整成堆
打个比方:你有一堆大小不一的石头,想从大到小排列。你先花点功夫把所有石头按"父大子小"的规则堆成一个"金字塔"(建堆)。然后每次从塔顶拿走最大的石头(堆顶),把剩下的石头重新调整成金字塔(堆调整)。这样每次都能拿到剩余石头中最大的,不用每次都扫描所有石头。
3.3 建堆过程(核心考点)
建堆的步骤:
- 将待排序序列看作一棵完全二叉树
- 从最后一个非叶子节点开始,依次向前调整,使每个子树都满足堆的性质
最后一个非叶子节点的位置 :n/2n/2n/2(下标从 111 开始)
建堆示例 :将序列 [4, 10, 3, 5, 1, 2, 8] 建成大根堆
初始完全二叉树:
4
/ \
10 3
/ \ / \
5 1 2 8
节点编号(从1开始):1:4, 2:10, 3:3, 4:5, 5:1, 6:2, 7:8
n=7,最后一个非叶子节点 = 7/2 = 3(节点3,值为3)
调整过程:
| 步骤 | 调整节点 | 操作 | 结果 |
|---|---|---|---|
| 1 | 节点3(值3) | 子节点为6(2)、7(8),8最大且>3,交换3和8 | 节点3变为8,节点7变为3 |
| 2 | 节点2(值10) | 子节点为4(5)、5(1),10最大,无需交换 | 不变 |
| 3 | 节点1(值4) | 子节点为2(10)、3(8),10最大且>4,交换1和2 | 节点1变为10,节点2变为4 |
| 4 | 节点2(值4) | 子节点为4(5)、5(1),5最大且>4,交换2和4 | 节点2变为5,节点4变为4 |
最终大根堆:
10
/ \
5 8
/ \ / \
4 1 2 3
数组表示 :[10, 5, 8, 4, 1, 2, 3]
3.4 堆排序过程
建堆完成后,排序过程如下:
- 将堆顶(最大值)与堆的最后一个元素交换
- 堆的大小减1
- 对新的堆顶进行向下调整(重新满足堆的性质)
- 重复步骤1-3,直到堆中只剩1个元素
示例(接上面的建堆结果):
| 步骤 | 堆顶 | 交换到末尾 | 调整后的堆 |
|---|---|---|---|
| 初始 | 10 | --- | [10, 5, 8, 4, 1, 2, 3] |
| 第1次 | 10 与 3 交换 | [3, 5, 8, 4, 1, 2, 10] |
[8, 5, 3, 4, 1, 2, 10] |
| 第2次 | 8 与 2 交换 | [2, 5, 3, 4, 1, 8, 10] |
[5, 4, 3, 2, 1, 8, 10] |
| 第3次 | 5 与 1 交换 | [1, 4, 3, 2, 5, 8, 10] |
[4, 2, 3, 1, 5, 8, 10] |
| ... | ... | ... | ... |
最终得到升序序列:[1, 2, 3, 4, 5, 8, 10]
3.5 复杂度与特点
| 情况 | 时间复杂度 | 说明 |
|---|---|---|
| 最好情况 | O(nlogn)O(n \log n)O(nlogn) | 建堆 O(n)O(n)O(n),排序 O(nlogn)O(n \log n)O(nlogn) |
| 最坏情况 | O(nlogn)O(n \log n)O(nlogn) | 同最好 |
| 平均情况 | O(nlogn)O(n \log n)O(nlogn) | 同最好 |
空间复杂度 :O(1)O(1)O(1)(原地排序,仅需几个临时变量)
稳定性 :❌ 不稳定(堆顶与末尾元素交换时可能改变相等元素的相对顺序)
4️⃣ 简单选择 vs 堆排序(完整对比表,重点!)
| 对比项 | 简单选择排序 | 堆排序 |
|---|---|---|
| 核心策略 | 每轮扫描剩余序列找最小 | 利用堆结构快速找最大/最小 |
| 最好时间复杂度 | O(n2)O(n^2)O(n2) | O(nlogn)O(n \log n)O(nlogn) |
| 最坏时间复杂度 | O(n2)O(n^2)O(n2) | O(nlogn)O(n \log n)O(nlogn) |
| 平均时间复杂度 | O(n2)O(n^2)O(n2) | O(nlogn)O(n \log n)O(nlogn) |
| 空间复杂度 | O(1)O(1)O(1) | O(1)O(1)O(1) |
| 稳定性 | ❌ 不稳定 | ❌ 不稳定 |
| 比较次数与初始状态是否相关 | 无关(始终为 n(n−1)/2n(n-1)/2n(n−1)/2) | 相关(建堆调整次数与初始序列有关) |
| 适用场景 | 数据量小、对稳定性无要求 | 数据量大、追求高效排序 |
5️⃣ 经典例题
例题1(堆的定义):以下哪个序列可以构成一个大根堆?
A. [10, 7, 8, 5, 6, 4, 3]
B. [10, 7, 8, 5, 6, 9, 3]
C. [10, 7, 8, 5, 6, 4, 9]
D. [10, 7, 8, 5, 6, 4, 11]
解析:大根堆要求每个节点的值 ≥ 其子节点。检查各选项:
- A:10≥7,8;7≥5,6;8≥4,3 → ✅ 全部满足
- B:节点3(值8)的子节点为4和9,8≥9?❌ 不满足
- C:节点2(值7)的子节点为4和9,7≥9?❌ 不满足
- D:节点1(值10)的子节点为2和11,10≥11?❌ 不满足
选 A。
例题2 (堆排序过程):用堆排序对序列 [3, 6, 5, 8, 2, 1, 7] 进行升序排序,建堆后(大根堆)的数组为( )。
解析:
- n=7,最后一个非叶子节点为 7/2 = 3(节点3,值5)
- 调整节点3:5的子节点为6(1)、7(7),7最大且>5 → 交换5和7
- 调整节点2:6的子节点为4(8)、5(2),8最大且>6 → 交换6和8
- 调整节点1:3的子节点为2(8)、3(7),8最大且>3 → 交换3和8
- 继续向下调整节点2:值3,子节点为4(6)、5(2),6>3 → 交换3和6
最终建堆结果:[8, 6, 7, 3, 2, 1, 5]
答案 :[8, 6, 7, 3, 2, 1, 5]
6️⃣ 记忆口诀
选择排序分两种,简单堆排要分清。
简单选择每轮扫,O(n2)O(n^2)O(n2) 不变样。
堆排先建大根堆,堆顶最大末尾归。
向下调整保堆性,O(nlogn)O(n \log n)O(nlogn) 效率高。
7️⃣ 小测验(评论区对答案)
序列
[1, 5, 4, 8, 2, 6, 3]要建成大根堆,需要从哪个节点开始调整?A. 节点 1
B. 节点 2
C. 节点 3
D. 节点 4
🔔 本专栏日更,点击头像 → 专栏《软考中级高频考点》订阅,第一时间接收新内容
#软考中级 #软件设计师 #选择排序 #堆排序 #排序算法 #数据结构 #软考备考