适用于小规模数据的排序任务。尽管其时间复杂度较高,但由于其简单性和空间效率,在某些情况下仍然是一个不错的选择。
工作原理
选择排序通过以下步骤进行排序:
- 初始化:将数组分为已排序部分和未排序部分。初始时,已排序部分为空,未排序部分包含整个数组。
- 选择最小值:从未排序部分中找到最小值,将其与未排序部分的第一个元素交换位置。
- 扩展已排序部分:将已排序部分的边界向右移动一个位置,未排序部分缩小一个位置。
- 重复步骤:重复上述步骤,直到未排序部分为空。
例子
假设我们有一个数组:[5, 3, 8, 4, 2]
-
第一轮:
- 找到最小值
2
,将其与第一个元素5
交换,数组变为[2, 3, 8, 4, 5]
- 已排序部分
[2]
,未排序部分[3, 8, 4, 5]
- 找到最小值
-
第二轮:
- 找到未排序部分的最小值
3
,它已经在正确位置,无需交换 - 已排序部分
[2, 3]
,未排序部分[8, 4, 5]
- 找到未排序部分的最小值
-
第三轮:
- 找到未排序部分的最小值
4
,将其与第一个元素8
交换,数组变为[2, 3, 4, 8, 5]
- 已排序部分
[2, 3, 4]
,未排序部分[8, 5]
- 找到未排序部分的最小值
-
第四轮:
- 找到未排序部分的最小值
5
,将其与第一个元素8
交换,数组变为[2, 3, 4, 5, 8]
- 已排序部分
[2, 3, 4, 5]
,未排序部分[8]
- 找到未排序部分的最小值
-
结束:
- 数组已经完全有序。
代码实现
以下是选择排序的 Java 实现:
package chapter05;
public class Java03_Object2 {
public static void main(String[] args) {
// TODO 选择排序
int[] nums = {1, 4, 3, 5, 2};
// 选择排序
for (int i = 0; i < nums.length - 1; i++) {
// 假设当前 i 是最小值的索引
int minIndex = i;
// 在未排序部分寻找最小值的索引
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] < nums[minIndex]) {
minIndex = j;
}
}
// 将找到的最小值与当前 i 位置的值交换
int temp = nums[i];
nums[i] = nums[minIndex];
nums[minIndex] = temp;
}
// 打印排序后的数组
for (int num : nums) {
System.out.println(num);
}
}
}
步骤解释
-
外层循环:
for (int i = 0; i < nums.length - 1; i++)
- 这个循环用于遍历数组的每一个元素,假设当前索引
i
是最小值的索引。
- 这个循环用于遍历数组的每一个元素,假设当前索引
-
假设最小值索引:
int minIndex = i;
- 初始化最小值索引为当前索引
i
。
- 初始化最小值索引为当前索引
-
内层循环:
for (int j = i + 1; j < nums.length; j++)
- 这个循环用于在未排序部分寻找真正的最小值。
-
更新最小值索引:
if (nums[j] < nums[minIndex])
- 如果找到更小的值,更新
minIndex
。
- 如果找到更小的值,更新
-
交换:
int temp = nums[i]; nums[i] = nums[minIndex]; nums[minIndex] = temp;
- 将找到的最小值与当前
i
位置的值交换。
- 将找到的最小值与当前
复杂度分析
-
时间复杂度:
- 最坏情况:
O(n^2)
,每次都需要遍历剩余的未排序部分。 - 最好情况:
O(n^2)
,即使数组已经有序,选择排序依然需要进行相同数量的比较。 - 平均情况:
O(n^2)
,无论数组初始状态如何,选择排序的比较次数是固定的。
- 最坏情况:
-
空间复杂度:
O(1)
,选择排序是原地排序算法,不需要额外的存储空间。
优缺点
优点:
- 简单易懂:选择排序的实现非常简单,适合初学者理解排序算法的基本原理。
- 原地排序:选择排序不需要额外的存储空间,空间复杂度为
O(1)
。
缺点:
- 时间复杂度高:选择排序的时间复杂度为
O(n^2)
,对于大规模数据排序效率较低。 - 不稳定:选择排序在某些情况下会破坏相同元素的相对顺序,因此是不稳定的排序算法。