经典排序算法深析:选择排序的原理与实现

选择排序是一种简单直观的比较排序算法,它的工作原理是不断地从未排序的元素中选择最小的元素,并将其放到已排序序列的末尾。

1. 算法原理

1.1 基本思想

选择排序的核心思想是”选择”:

  1. 在未排序序列中找到最小元素
  2. 将最小元素与未排序序列的第一个元素交换
  3. 重复上述过程,直到所有元素排序完成

1.2 算法步骤

1
2
3
4
5
6
1. 初始时,整个数组都是未排序状态
2. 从位置0开始,在整个数组中找到最小元素
3. 将最小元素与位置0的元素交换
4. 从位置1开始,在剩余数组中找到最小元素
5. 将最小元素与位置1的元素交换
6. 重复步骤4-5,直到排序完成

1.3 图解演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
原始数组: [64, 25, 12, 22, 11]

第1轮: 找到最小值11,与位置0交换
[11, 25, 12, 22, 64]

第2轮: 在[25, 12, 22, 64]中找到最小值12,与位置1交换
[11, 12, 25, 22, 64]

第3轮: 在[25, 22, 64]中找到最小值22,与位置2交换
[11, 12, 22, 25, 64]

第4轮: 在[25, 64]中找到最小值25,与位置3交换
[11, 12, 22, 25, 64]

排序完成: [11, 12, 22, 25, 64]

2. C语言实现

2.1 基础版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdbool.h>

// 交换两个元素
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}

// 基础选择排序实现
void selection_sort_basic(int arr[], int n) {
printf("开始选择排序...\n");

for (int i = 0; i < n - 1; i++) {
// 假设当前位置是最小值
int min_index = i;

// 在未排序部分寻找最小值
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[min_index]) {
min_index = j;
}
}

// 将最小值交换到当前位置
if (min_index != i) {
swap(&arr[i], &arr[min_index]);
}

// 显示每轮排序结果
printf("第%d轮后: ", i + 1);
for (int k = 0; k < n; k++) {
if (k <= i) {
printf("[%d] ", arr[k]); // 已排序部分
} else {
printf("%d ", arr[k]); // 未排序部分
}
}
printf("\n");
}
}

// 测试基础选择排序
void test_basic_selection_sort() {
printf("=== 基础选择排序测试 ===\n");

int arr[] = {64, 25, 12, 22, 11};
int n = sizeof(arr) / sizeof(arr[0]);

printf("原始数组: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n\n");

selection_sort_basic(arr, n);

printf("\n排序结果: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n\n");
}

2.2 带统计信息的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 选择排序统计结构
typedef struct {
int comparisons; // 比较次数
int swaps; // 交换次数
double time_taken; // 执行时间
} SortStats;

// 带统计的选择排序
void selection_sort_with_stats(int arr[], int n, SortStats* stats) {
clock_t start = clock();
stats->comparisons = 0;
stats->swaps = 0;

for (int i = 0; i < n - 1; i++) {
int min_index = i;

for (int j = i + 1; j < n; j++) {
stats->comparisons++;
if (arr[j] < arr[min_index]) {
min_index = j;
}
}

if (min_index != i) {
swap(&arr[i], &arr[min_index]);
stats->swaps++;
}
}

clock_t end = clock();
stats->time_taken = ((double)(end - start)) / CLOCKS_PER_SEC;
}

// 测试带统计的选择排序
void test_selection_sort_stats() {
printf("=== 选择排序性能统计测试 ===\n");

int sizes[] = {10, 100, 1000};
int num_sizes = sizeof(sizes) / sizeof(sizes[0]);

for (int s = 0; s < num_sizes; s++) {
int size = sizes[s];
int* arr = (int*)malloc(size * sizeof(int));

// 生成随机数据
srand(time(NULL));
for (int i = 0; i < size; i++) {
arr[i] = rand() % 1000;
}

SortStats stats;
selection_sort_with_stats(arr, size, &stats);

printf("数组大小: %d\n", size);
printf("比较次数: %d\n", stats.comparisons);
printf("交换次数: %d\n", stats.swaps);
printf("执行时间: %.6f秒\n", stats.time_taken);
printf("理论比较次数: %d\n", (size * (size - 1)) / 2);
printf("\n");

free(arr);
}
}

2.3 优化版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 双向选择排序(同时找最大值和最小值)
void selection_sort_bidirectional(int arr[], int n) {
printf("开始双向选择排序...\n");

int left = 0, right = n - 1;

while (left < right) {
int min_index = left, max_index = left;

// 同时找最小值和最大值
for (int i = left; i <= right; i++) {
if (arr[i] < arr[min_index]) {
min_index = i;
}
if (arr[i] > arr[max_index]) {
max_index = i;
}
}

// 将最小值放到左端
if (min_index != left) {
swap(&arr[left], &arr[min_index]);
}

// 如果最大值被移动了,需要更新索引
if (max_index == left) {
max_index = min_index;
}

// 将最大值放到右端
if (max_index != right) {
swap(&arr[right], &arr[max_index]);
}

left++;
right--;

// 显示当前状态
printf("当前状态: ");
for (int k = 0; k < n; k++) {
if (k < left || k > right) {
printf("[%d] ", arr[k]); // 已排序部分
} else {
printf("%d ", arr[k]); // 未排序部分
}
}
printf("\n");
}
}

// 测试双向选择排序
void test_bidirectional_selection_sort() {
printf("=== 双向选择排序测试 ===\n");

int arr[] = {64, 34, 25, 12, 22, 11, 90, 5};
int n = sizeof(arr) / sizeof(arr[0]);

printf("原始数组: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n\n");

selection_sort_bidirectional(arr, n);

printf("\n排序结果: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n\n");
}

2.4 递归版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 递归选择排序
void selection_sort_recursive(int arr[], int n, int start) {
// 基础情况:只有一个或没有元素
if (start >= n - 1) {
return;
}

// 找到最小元素的索引
int min_index = start;
for (int i = start + 1; i < n; i++) {
if (arr[i] < arr[min_index]) {
min_index = i;
}
}

// 交换最小元素到当前位置
if (min_index != start) {
swap(&arr[start], &arr[min_index]);
}

// 递归处理剩余部分
selection_sort_recursive(arr, n, start + 1);
}

// 递归选择排序包装函数
void selection_sort_recursive_wrapper(int arr[], int n) {
printf("开始递归选择排序...\n");
selection_sort_recursive(arr, n, 0);
}

// 测试递归选择排序
void test_recursive_selection_sort() {
printf("=== 递归选择排序测试 ===\n");

int arr[] = {29, 10, 14, 37, 13, 25, 6, 19};
int n = sizeof(arr) / sizeof(arr[0]);

printf("原始数组: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");

selection_sort_recursive_wrapper(arr, n);

printf("排序结果: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n\n");
}

3. 算法分析

3.1 时间复杂度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 时间复杂度分析演示
void analyze_time_complexity() {
printf("=== 时间复杂度分析 ===\n");

printf("选择排序的时间复杂度分析:\n");
printf("- 外层循环: n-1次\n");
printf("- 内层循环: 第i次外层循环需要 (n-1-i) 次比较\n");
printf("- 总比较次数: Σ(n-1-i) = (n-1) + (n-2) + ... + 1 = n(n-1)/2\n");
printf("- 时间复杂度: O(n²)\n\n");

// 实际测试不同规模的数据
int sizes[] = {10, 20, 40, 80};
printf("实际测试结果:\n");
printf("%-10s %-15s %-15s %-10s\n", "大小", "比较次数", "理论次数", "比值");
printf("----------------------------------------\n");

for (int i = 0; i < 4; i++) {
int n = sizes[i];
int* arr = (int*)malloc(n * sizeof(int));

for (int j = 0; j < n; j++) {
arr[j] = n - j; // 逆序数组
}

SortStats stats;
selection_sort_with_stats(arr, n, &stats);

int theoretical = n * (n - 1) / 2;
double ratio = (double)stats.comparisons / theoretical;

printf("%-10d %-15d %-15d %-10.2f\n",
n, stats.comparisons, theoretical, ratio);

free(arr);
}
printf("\n");
}

3.2 空间复杂度

1
2
3
4
5
6
7
8
9
10
// 空间复杂度分析
void analyze_space_complexity() {
printf("=== 空间复杂度分析 ===\n");

printf("选择排序的空间复杂度:\n");
printf("- 原地排序算法\n");
printf("- 只需要常数个额外变量\n");
printf("- 空间复杂度: O(1)\n");
printf("- 不需要额外的存储空间\n\n");
}

3.3 稳定性分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 稳定性测试
typedef struct {
int value;
int original_index;
} Element;

void print_elements(Element arr[], int n, const char* title) {
printf("%s: ", title);
for (int i = 0; i < n; i++) {
printf("(%d,%d) ", arr[i].value, arr[i].original_index);
}
printf("\n");
}

void selection_sort_stability_test() {
printf("=== 稳定性测试 ===\n");

// 创建包含重复元素的数组
Element arr[] = {
{5, 0}, {2, 1}, {5, 2}, {1, 3}, {5, 4}, {2, 5}
};
int n = sizeof(arr) / sizeof(arr[0]);

print_elements(arr, n, "原始数组");

// 修改后的选择排序,处理结构体
for (int i = 0; i < n - 1; i++) {
int min_index = i;

for (int j = i + 1; j < n; j++) {
if (arr[j].value < arr[min_index].value) {
min_index = j;
}
}

if (min_index != i) {
Element temp = arr[i];
arr[i] = arr[min_index];
arr[min_index] = temp;
}
}

print_elements(arr, n, "排序后 ");

printf("\n分析:\n");
printf("- 选择排序是不稳定的排序算法\n");
printf("- 交换操作可能改变相等元素的相对位置\n");
printf("- 例如:相同值5的元素,原始顺序可能被打乱\n\n");
}

4. 实际应用场景

4.1 教学演示系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// 教学演示版本
void selection_sort_teaching_demo(int arr[], int n) {
printf("=== 选择排序教学演示 ===\n");
printf("数组大小: %d\n", n);

printf("\n初始状态:\n");
for (int i = 0; i < n; i++) {
printf("[%2d] ", arr[i]);
}
printf("\n");
for (int i = 0; i < n; i++) {
printf(" %2d ", i);
}
printf("\n\n");

for (int i = 0; i < n - 1; i++) {
printf("--- 第 %d 轮 ---\n", i + 1);
printf("在位置 %d 到 %d 中寻找最小值...\n", i, n - 1);

int min_index = i;
int min_value = arr[i];

for (int j = i + 1; j < n; j++) {
printf("比较 arr[%d]=%d 和 当前最小值 %d: ", j, arr[j], min_value);
if (arr[j] < min_value) {
min_index = j;
min_value = arr[j];
printf("找到新的最小值 %d\n", min_value);
} else {
printf("保持当前最小值\n");
}
}

printf("最小值 %d 位于索引 %d\n", min_value, min_index);

if (min_index != i) {
printf("交换 arr[%d]=%d 和 arr[%d]=%d\n",
i, arr[i], min_index, arr[min_index]);
swap(&arr[i], &arr[min_index]);
} else {
printf("最小值已在正确位置,无需交换\n");
}

printf("第 %d 轮结果: ", i + 1);
for (int k = 0; k < n; k++) {
if (k <= i) {
printf("[%2d] ", arr[k]); // 已排序
} else {
printf(" %2d ", arr[k]); // 未排序
}
}
printf("\n\n");
}

printf("最终排序结果: ");
for (int i = 0; i < n; i++) {
printf("[%2d] ", arr[i]);
}
printf("\n");
}

void test_teaching_demo() {
printf("=== 教学演示测试 ===\n");

int arr[] = {64, 25, 12, 22, 11};
int n = sizeof(arr) / sizeof(arr[0]);

selection_sort_teaching_demo(arr, n);
printf("\n");
}

4.2 小数据集排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 小数据集专用优化版本
void selection_sort_small_data(int arr[], int n) {
if (n <= 1) return;

// 对于小数据集,选择排序可能比快速排序更快
for (int i = 0; i < n - 1; i++) {
int min_index = i;

// 展开内层循环,减少循环开销
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[min_index]) {
min_index = j;
}
}

// 使用位运算优化交换
if (min_index != i && arr[i] != arr[min_index]) {
arr[i] ^= arr[min_index];
arr[min_index] ^= arr[i];
arr[i] ^= arr[min_index];
}
}
}

// 性能对比测试
void test_small_data_performance() {
printf("=== 小数据集性能测试 ===\n");

const int size = 20;
const int iterations = 10000;

// 生成测试数据
int original[size];
srand(time(NULL));
for (int i = 0; i < size; i++) {
original[i] = rand() % 100;
}

// 测试基础版本
clock_t start = clock();
for (int iter = 0; iter < iterations; iter++) {
int arr[size];
memcpy(arr, original, sizeof(original));
selection_sort_basic(arr, size);
}
clock_t end = clock();
double basic_time = ((double)(end - start)) / CLOCKS_PER_SEC;

// 测试优化版本
start = clock();
for (int iter = 0; iter < iterations; iter++) {
int arr[size];
memcpy(arr, original, sizeof(original));
selection_sort_small_data(arr, size);
}
end = clock();
double optimized_time = ((double)(end - start)) / CLOCKS_PER_SEC;

printf("数据大小: %d\n", size);
printf("迭代次数: %d\n", iterations);
printf("基础版本时间: %.6f秒\n", basic_time);
printf("优化版本时间: %.6f秒\n", optimized_time);
printf("性能提升: %.2f%%\n",
((basic_time - optimized_time) / basic_time) * 100);
printf("\n");
}

5. 优缺点分析

5.1 优点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void selection_sort_advantages() {
printf("=== 选择排序的优点 ===\n");

printf("1. 算法简单易懂\n");
printf(" - 逻辑清晰,容易实现\n");
printf(" - 适合教学和理解排序概念\n\n");

printf("2. 原地排序\n");
printf(" - 空间复杂度O(1)\n");
printf(" - 不需要额外的存储空间\n\n");

printf("3. 交换次数少\n");
printf(" - 最多进行n-1次交换\n");
printf(" - 在交换成本很高的场景下有优势\n\n");

printf("4. 性能可预测\n");
printf(" - 时间复杂度始终为O(n²)\n");
printf(" - 不受输入数据分布影响\n\n");

// 交换次数对比演示
printf("交换次数对比演示:\n");
int arr1[] = {5, 4, 3, 2, 1}; // 逆序数组
int arr2[] = {1, 2, 3, 4, 5}; // 已排序数组
int n = 5;

SortStats stats1, stats2;

int temp1[5], temp2[5];
memcpy(temp1, arr1, sizeof(arr1));
memcpy(temp2, arr2, sizeof(arr2));

selection_sort_with_stats(temp1, n, &stats1);
selection_sort_with_stats(temp2, n, &stats2);

printf("逆序数组交换次数: %d\n", stats1.swaps);
printf("已排序数组交换次数: %d\n", stats2.swaps);
printf("最大可能交换次数: %d\n", n - 1);
printf("\n");
}

5.2 缺点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void selection_sort_disadvantages() {
printf("=== 选择排序的缺点 ===\n");

printf("1. 时间复杂度高\n");
printf(" - O(n²)时间复杂度在大数据集上表现差\n");
printf(" - 不适合处理大量数据\n\n");

printf("2. 不是稳定排序\n");
printf(" - 可能改变相等元素的相对位置\n");
printf(" - 在需要保持稳定性的场景下不适用\n\n");

printf("3. 不是自适应的\n");
printf(" - 即使数组已经有序,仍需O(n²)时间\n");
printf(" - 无法利用数据的现有顺序\n\n");

printf("4. 缓存性能差\n");
printf(" - 频繁的随机访问模式\n");
printf(" - 对CPU缓存不友好\n\n");

// 性能对比演示
printf("与其他算法的性能对比(1000个元素):\n");
printf("%-15s %-10s %-10s\n", "算法", "比较次数", "交换次数");
printf("----------------------------------------\n");
printf("%-15s %-10d %-10s\n", "选择排序", 499500, "≤999");
printf("%-15s %-10s %-10s\n", "插入排序", "≤499500", "≤499500");
printf("%-15s %-10s %-10s\n", "快速排序", "≈13816", "≈2048");
printf("%-15s %-10s %-10s\n", "归并排序", "≈9976", "0");
printf("\n");
}

6. 主函数和测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int main() {
printf("========================================\n");
printf(" 选择排序算法完整演示\n");
printf("========================================\n\n");

// 基础功能测试
test_basic_selection_sort();

// 性能统计测试
test_selection_sort_stats();

// 双向选择排序测试
test_bidirectional_selection_sort();

// 递归版本测试
test_recursive_selection_sort();

// 时间复杂度分析
analyze_time_complexity();

// 空间复杂度分析
analyze_space_complexity();

// 稳定性测试
selection_sort_stability_test();

// 教学演示
test_teaching_demo();

// 小数据集性能测试
test_small_data_performance();

// 优缺点分析
selection_sort_advantages();
selection_sort_disadvantages();

printf("========================================\n");
printf(" 测试完成\n");
printf("========================================\n");

return 0;
}

7. 总结

选择排序虽然不是最高效的排序算法,但它具有以下重要价值:

  1. 教学价值:算法思路清晰,是理解排序概念的好起点
  2. 理论基础:为理解其他更复杂的排序算法奠定基础
  3. 特定场景:在交换代价很高的情况下仍有应用价值
  4. 代码简洁:实现简单,适合资源受限的环境

关键要点

  • 时间复杂度:O(n²),适合小数据集
  • 空间复杂度:O(1),原地排序
  • 稳定性:不稳定
  • 自适应性:不自适应
  • 交换次数:最多n-1次

使用建议

  • 适用于小数据集(通常n < 50)
  • 教学和算法学习
  • 交换成本很高的场景
  • 内存极度受限的环境
  • 不适用于大数据集或需要稳定性的场景

选择排序的价值不在于其性能,而在于其简洁性和教育意义,是每个程序员都应该掌握的基础算法。

版权所有,如有侵权请联系我