8.3 选择排序:简单选择与堆排序
简单选择排序,堆的定义、建堆与筛选(下坠)过程,堆排序的性能,以及堆的插入与删除。
选择排序的基本思想是:每一趟(如第 趟)在后面 ()个待排序元素中选取关键字最小的元素,作为有序子序列的第 个元素,直到第 趟做完,待排序元素只剩下 1 个,就不用再选了。
简单选择排序
假设排序表为 L[1..n],第 趟排序即从 L[i..n] 中选择关键字最小的元素与 L(i) 交换,每一趟排序可以确定一个元素的最终位置,这样经过 趟排序就可使得整个排序表有序。
动画演示简单选择排序
比较移动 / 交换当前最小已就位
C 代码
1void SelectSort(int A[], int n) {2 for (int i = 0; i < n - 1; i++) { // 共 n-1 趟3 int min = i; // 记录最小元素的位置4 for (int j = i + 1; j < n; j++) // 在 A[i..n-1] 中选最小5 if (A[j] < A[min])6 min = j; // 更新最小元素位置7 if (min != i)8 swap(A[i], A[min]); // 与第 i 个位置交换9 }10}
1/44简单选择排序:每一趟在待排序元素中选取最小的,放到有序区的末尾
比较0移动0
性能分析:
- 空间效率:仅使用常数个辅助单元,故空间效率为 。
- 时间效率:从上述伪码中不难看出,在简单选择排序过程中,元素移动的操作次数很少,不会超过 次,最好的情况是移动 0 次,此时对应的表已经有序;但元素间比较的次数与序列的初始状态无关,始终是 次,因此时间复杂度始终是 。
- 稳定性:在第 趟找到最小元素后,和第 个元素交换,可能会导致第 个元素与其含有相同关键字元素的相对位置发生改变。例如,表 ,经过一趟排序后 ,最终排序序列也是 ,显然,2 与 的相对次序已发生变化。因此,简单选择排序是一种不稳定的排序算法。
堆排序
大根堆:87 45 78 32 17 53 9
小根堆:9 17 53 45 32 87 78
堆排序的思路很简单:首先将存放在 L[1..n] 中的 个元素建成初始堆,由于堆本身的特点(以大顶堆为例),堆顶元素就是最大值。输出堆顶元素后,通常将堆底元素送入堆顶,此时根结点已不满足大顶堆的性质,堆被破坏,将堆顶元素向下调整使其继续保持大顶堆的性质,再输出堆顶元素。如此重复,直到堆中仅剩一个元素为止。
可见,堆排序需要解决两个问题:① 如何将无序序列构造成初始堆?② 输出堆顶元素后,如何将剩余元素调整成新的堆?
建堆
个结点的完全二叉树,最后一个结点是第 个结点的孩子。对第 个结点为根的子树筛选(对于大根堆,若根结点的关键字小于左右孩子中关键字较大者,则交换),使该子树成为堆。之后向前依次对各结点()为根的子树进行筛选,看该结点值是否大于其左右子结点的值,若不大于,则将左右子结点中的较大值与之交换,交换后可能会破坏下一级的堆,于是继续采用上述方法构造下一级的堆,直到以该结点为根的子树构成堆为止(“小元素不断下坠”)。反复利用上述调整堆的方法建堆,直到根结点。
动画演示堆排序(大根堆)
暂存 A[0]比较上移 / 交换已排好序
C 代码
1void HeadAdjust(int A[], int k, int len) {2 A[0] = A[k]; // A[0] 暂存子树的根结点3 for (int i = 2*k; i <= len; i *= 2) { // 沿较大的子结点向下筛选4 if (i < len && A[i] < A[i+1])5 i++; // 取较大的子结点的下标6 if (A[0] >= A[i]) break; // 筛选结束7 else {8 A[k] = A[i]; // 将 A[i] 调整到双亲结点上9 k = i; // 继续向下筛选10 }11 }12 A[k] = A[0]; // 被筛选结点放入最终位置13}14void BuildMaxHeap(int A[], int len) {15 for (int i = len/2; i > 0; i--) // 从最后一个分支结点开始16 HeadAdjust(A, i, len);17}18void HeapSort(int A[], int len) {19 BuildMaxHeap(A, len);20 for (int i = len; i > 1; i--) { // n-1 趟交换和建堆21 swap(A[i], A[1]); // 堆顶元素与堆底元素交换22 HeadAdjust(A, 1, i - 1); // 把剩余 i-1 个元素整理成堆23 }24}
1/67初始序列按层序看作一棵完全二叉树。先建立大根堆:从最后一个分支结点 ⌊n/2⌋ 开始,依次向前调整
len8
堆的插入与删除
- 插入:对于大(小)根堆,新元素放到表尾,与父结点对比,若新元素比父结点更大(小),则将二者互换。新元素就这样一路”上升”,直到无法继续上升为止。每上升一层只需对比 1 次。
- 删除:被删除的元素用堆底元素替代,然后让该元素不断”下坠”,直到无法下坠为止。
即时练习
即时练习5 题
点击选项即可作答
- 1已知序列 25, 13, 10, 12, 9 是大根堆,在序列尾部插入新元素 18,将其再调整为大根堆,调整过程中元素之间进行的比较次数是( )。
- 2向具有 n 个结点的堆中插入一个新元素的时间复杂度为( ),删除一个元素的时间复杂度为( )。
- 3构建 n 个记录的初始堆,其时间复杂度为( )。
- 4下列 4 种排序方法中,排序过程中的比较次数与序列初始状态无关的是( )。
- 5将序列 {6, 1, 5, 9, 8, 4, 7} 建成大根堆时,正确的序列变化过程是( )。
学完了吗?
标记完成后,侧栏会显示进度。进度仅保存在本浏览器中。