选择排序(Selection Sort)是入门级的排序算法之一,它的核心思想简单易懂,实现成本低,非常适合编程新手理解排序的基本逻辑。本文将从原理、C 语言实现、性能分析到优化思路,全方位讲解选择排序。
一、选择排序的核心原理
选择排序的核心可以概括为 **"找最值,放位置"**,它将数组分为 "已排序区间" 和 "未排序区间":
-
初始时,已排序区间为空,未排序区间为整个数组;
-
遍历未排序区间,找到其中的最小值(或最大值);
-
将找到的最值与未排序区间的第一个元素交换位置,此时该元素归入已排序区间;
-
重复步骤 2-3,直到未排序区间为空
直观示例
以数组
[5, 2, 9, 1, 5, 6]为例,选择排序的执行过程: -
第 1 轮:未排序区间
[5,2,9,1,5,6],最小值是 1,与第一个元素 5 交换 →[1, 2, 9, 5, 5, 6] -
第 2 轮:未排序区间
[2,9,5,5,6],最小值是 2,无需交换 →[1, 2, 9, 5, 5, 6] -
第 3 轮:未排序区间
[9,5,5,6],最小值是 5,与 9 交换 →[1, 2, 5, 9, 5, 6] -
第 4 轮:未排序区间
[9,5,6],最小值是 5,与 9 交换 →[1, 2, 5, 5, 9, 6] -
第 5 轮:未排序区间
[9,6],最小值是 6,与 9 交换 →[1, 2, 5, 5, 6, 9] -
第 6 轮:未排序区间仅剩一个元素,排序完成。二、C 语言实现选择排序(升序)
二、C 语言实现选择排序(升序)
cs
#include <stdio.h>
// 交换两个整数的值
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 选择排序(升序)
void selectionSort(int arr[], int n) {
// i 表示已排序区间的末尾(未排序区间的起始)
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) { // 优化:避免自身交换
swap(&arr[i], &arr[minIndex]);
}
}
}
// 打印数组
void printArray(int arr[], int n) {
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// 主函数测试
int main() {
int arr[] = {5, 2, 9, 1, 5, 6};
int n = sizeof(arr) / sizeof(arr[0]);
printf("排序前的数组:");
printArray(arr, n);
selectionSort(arr, n);
printf("排序后的数组:");
printArray(arr, n);
return 0;
}
三、选择排序的性能分析
1. 时间复杂度
- 最好情况:数组已有序,时间复杂度仍为 O(n2)(必须遍历找最值,无法提前终止);
- 最坏情况:数组逆序,时间复杂度 O(n2);
- 平均情况:O(n2)。
选择排序的时间复杂度始终是 O(n2),这是因为它的内层循环必须完整遍历未排序区间,无法像冒泡排序那样提前终止。
2. 空间复杂度
选择排序是原地排序算法 (In-place Sort),仅使用了常数级的临时变量(如 temp、minIndex),空间复杂度为 O(1)。
3. 稳定性
选择排序是不稳定排序 。例如数组 [2, 3, 2, 1],第一轮找到最小值 1,与第一个 2 交换后,原数组中两个 2 的相对位置被改变(变为 [1, 3, 2, 2])
四、选择排序的优化:双向选择排序
基础选择排序每轮只找最小值,优化思路是每轮同时找最小值和最大值,减少循环次数,这就是 "双向选择排序"(也叫 "鸡尾酒排序" 简化版)
cs
// 双向选择排序(同时找最小和最大值)
void bidirectionalSelectionSort(int arr[], int n) {
int left = 0; // 未排序区间左边界
int right = n - 1; // 未排序区间右边界
while (left < right) {
int minIndex = left;
int maxIndex = right;
// 遍历未排序区间,同时找最小和最大值下标
for (int i = left; i <= right; i++) {
if (arr[i] < arr[minIndex]) {
minIndex = i;
}
if (arr[i] > arr[maxIndex]) {
maxIndex = i;
}
}
// 交换最小值到左边界
if (minIndex != left) {
swap(&arr[left], &arr[minIndex]);
}
// 注意:如果最大值在左边界(刚被交换走),需要更新maxIndex
if (maxIndex == left) {
maxIndex = minIndex;
}
// 交换最大值到右边界
if (maxIndex != right) {
swap(&arr[right], &arr[maxIndex]);
}
// 缩小未排序区间
left++;
right--;
}
}
优化说明
双向选择排序每轮处理两个元素(最小值放左、最大值放右),循环次数减少约一半,但时间复杂度仍为 O(n2)(仅减少常数项,不改变阶数),适合数据量较大的场景。
五、选择排序的适用场景
选择排序的优势是交换次数少(最多 n−1 次交换),因此适合:
- 数据量小的场景(如嵌入式系统、单片机);
- 交换成本远高于比较成本的场景(如磁盘数据排序);
- 编程新手理解排序逻辑的入门场景。
总结
- 选择排序的核心是遍历找最值,交换到指定位置,将数组分为已排序 / 未排序区间;
- 基础选择排序的 C 语言实现简单,时间复杂度固定为 O(n2),空间复杂度 O(1),是不稳定排序;
- 双向选择排序可减少循环次数,但未改变时间复杂度阶数,适合对交换次数敏感的场景。
选择排序虽然效率不高,但它的逻辑简单、代码易实现,是理解排序算法 "分区间、找规律" 思想的绝佳入门案例。掌握选择排序后,再学习冒泡、插入排序等算法,会更容易理解不同排序思路的差异