8.4.选择排序
1.简单选择排序
核心思想:
-
从待排序数组中选择一个最小(或者最大)的元素,将选出的元素和待排序数组中的第一个元素进行交换。
-
重复操作1,n - 1次,数组排序完成。
实现代码(C语言):
void selectionSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
if (minIndex != i) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
算法特性:
-
时间复杂度为\(O(n^{2})\),空间复杂度为\(O(1)\)。
-
选择排序是一种不稳定的排序算法,属于原地排序算法,一般适用于小型数据,因为算法时间复杂度较高,且实现简单,容易理解。
-
简单选择排序适用于顺序存储和链式存储的线性表,以及关键字较少的情况。
常见问题:
-
选择排序的优化思路有哪些?
-
在一次遍历中,同时寻找最小元素和最大元素,将他们分别放到数组的两端,这样每次遍历考研确定两个元素的位置,从而减少遍历次数。
-
可以在特殊情况下,结合其他排序算法或者利用特殊情况的特性来减少比较和交换的次数。
-
2.堆排序
核心思想:
-
将待排序数组构建成一个小根堆(大根堆)。
-
建立好初始堆之后,堆顶元素是堆中最大(小)的元素,将堆顶元素与未排序数组的最后一个元素交换位置,此时数组中最大(小)的元素放在了数组的末尾。
-
交换完堆顶元素和未排序数组最后一个元素之后,堆的可能被破坏,此时需要重新调整堆,但是其他部分仍然满足堆的性质,所以只需要将堆顶元素和其子节点进行比较并交换即可。
-
重复2和3,n - 1次,数组排序完成。
注:以下代码的数组元素都是从下标为0的位置开始存储的。
实现代码(C语言):
- 递归实现堆排序的代码
// 堆化函数,将以 root 为根的子树调整为大顶堆
void heapify(int arr[], int n, int root) {
int largest = root;
int left = 2 * root + 1;
int right = 2 * root + 2;
// 若左子节点比根节点大,则更新 largest
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
// 若右子节点比当前 largest 大,则更新 largest
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
// 若 largest 不是根节点,则交换并继续堆化
if (largest != root) {
int temp = arr[root];
arr[root] = arr[largest];
arr[largest] = temp;
heapify(arr, n, largest);
}
}
// 堆排序函数
void heapSort(int arr[], int n) {
// 构建初始大顶堆
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// 一个个从堆中取出元素
for (int i = n - 1; i > 0; i--) {
// 交换堆顶(最大值)和当前最后一个元素
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// 重新调整堆
heapify(arr, i, 0);
}
}
- 非递归实现堆排序
#include <stdio.h>
// 非递归堆化函数,将以 root 为根的子树调整为大顶堆
void heapify(int arr[], int n, int root) {
int largest;
int temp;
while (1) {
largest = root;
int left = 2 * root + 1;
int right = 2 * root + 2;
// 若左子节点比根节点大,则更新 largest
if (left < n && arr[left] > arr[largest]) {
largest = left;
}
// 若右子节点比当前 largest 大,则更新 largest
if (right < n && arr[right] > arr[largest]) {
largest = right;
}
// 若 largest 不是根节点,则交换并继续循环
if (largest != root) {
temp = arr[root];
arr[root] = arr[largest];
arr[largest] = temp;
root = largest;
} else {
break;
}
}
}
// 堆排序函数
void heapSort(int arr[], int n) {
// 构建初始大顶堆
for (int i = n / 2 - 1; i >= 0; i--) {
heapify(arr, n, i);
}
// 一个个从堆中取出元素
for (int i = n - 1; i > 0; i--) {
// 交换堆顶(最大值)和当前最后一个元素
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// 重新调整堆
heapify(arr, i, 0);
}
}
算法特性:
-
时间复杂度为\(O(n log_{2}n)\),无论输入的数据初始排序状态如何,堆排序都需要先构建堆,时间复杂度为\(O(n)\),然后进行n - 1次排序,所以综合时间复杂度为\(O(n log_{2}n)\)。空间复杂度为\(O(1)\)。
-
由于堆排序的时间复杂度始终不变,性能稳定,不受数据分布的影响,所以使得它在处理大规模数据的时候表现出色。堆排序的一种原地排序算法,且不稳定。
-
排序过程中元素交换次数过多,某些情况下可能不如其他排序元素高效。
-
适合大规模数据排序,对内存要求较高的场景和不需要稳定排序的场景。
-
堆排序的代码实现相对复杂,相对难以理解。
-
堆排序只适用于顺序存储的线性表。
常见问题:
-
什么是小根堆和大根堆,如何在堆排序中应用?
- 堆是一种完全二叉树,大根堆(小根堆)的每个分支节点的值都要大于(小于)其左右节点的值。在堆排序中,若要进行升序排序,通常使用大根堆,若要进行降序排序,通常使用小根堆。
- 堆是一种完全二叉树,大根堆(小根堆)的每个分支节点的值都要大于(小于)其左右节点的值。在堆排序中,若要进行升序排序,通常使用大根堆,若要进行降序排序,通常使用小根堆。

浙公网安备 33010602011771号