选择排序是一种经典的排序算法。
选择排序思想
将数组分为两个子集,前一个子集是排序的,后一个子集是未排序的。
每一轮遍历从未排序的子集中选择最小的元素放入排序子集的末端。
重复以上过程,直到整个数组有序。
经典实现
为了能更好地展示冒泡排序的主要思想,我将数据准备工作定义在 main() 方法中。
public static void main(String[] args) {
int[] nums = {5,9,7,4,6,1,3,2,8};
System.out.println("排序前 " + Arrays.toString(nums));
selectionSort(nums);
System.out.println("排序后 " + Arrays.toString(nums));
}
private static void selectionSort(int[] nums) {
}
完成选择排序的代码:
private static void selectionSort(int[] nums) {
// i 表示每轮选择最小元素需要交换到的位置
for (int i = 0; i < nums.length - 1; i++) {
// 最小元素的索引需要暂存
int min = i;
for (int j = min + 1; j < nums.length; j++) {
if(nums[min] > nums[j]){
min = j;
}
}
System.out.println("交换 " + nums[min] + " 与 " + nums[i] + "; ");
swap(nums, min, i);
}
}
private static void swap(int[] nums, int m, int n) {
int tmp = nums[m];
nums[m] = nums[n];
nums[n] = tmp;
}
排序前 [5, 9, 7, 4, 6, 1, 3, 2, 8]
交换 1 与 5;
交换 2 与 9;
交换 3 与 7;
交换 4 与 4;
交换 5 与 6;
交换 6 与 6;
交换 7 与 7;
交换 8 与 9;
排序后 [1, 2, 3, 4, 5, 6, 7, 8, 9]
交换 1 与 5;
交换 2 与 9;
交换 3 与 7;
交换 4 与 4;
交换 5 与 6;
交换 6 与 6;
交换 7 与 7;
交换 8 与 9;
排序后 [1, 2, 3, 4, 5, 6, 7, 8, 9]
可以发现,其中有些交换没有必要:当两个数相等的时候,可以不需要交换:
减少交换次数
private static void selectionSort(int[] nums) {
// i 表示每轮选择最小元素需要交换到的位置
for (int i = 0; i < nums.length - 1; i++) {
// 最小元素的索引需要暂存
int min = i;
for (int j = min + 1; j < nums.length; j++) {
if(nums[min] > nums[j]){
min = j;
}
}
// 只有 min 与 i 不是相同元素的时候,才发生交换
if(min != i){
System.out.println("交换 " + nums[min] + " 与 " + nums[i] + "; ");
swap(nums, min, i);
}
}
}
排序前 [5, 9, 7, 4, 6, 1, 3, 2, 8]
交换 1 与 5;
交换 2 与 9;
交换 3 与 7;
交换 5 与 6;
交换 8 与 9;
排序后 [1, 2, 3, 4, 5, 6, 7, 8, 9]
交换 1 与 5;
交换 2 与 9;
交换 3 与 7;
交换 5 与 6;
交换 8 与 9;
排序后 [1, 2, 3, 4, 5, 6, 7, 8, 9]
完整代码
public class Selection {
public static void main(String[] args) {
int[] nums = {5,9,7,4,6,1,3,2,8};
System.out.println("排序前 " + Arrays.toString(nums));
selectionSort(nums);
System.out.println("排序后 " + Arrays.toString(nums));
}
private static void selectionSort(int[] nums) {
// i 表示每轮选择最小元素需要交换到的位置
for (int i = 0; i < nums.length - 1; i++) {
// 最小元素的索引需要暂存
int min = i;
for (int j = min + 1; j < nums.length; j++) {
if(nums[min] > nums[j]){
min = j;
}
}
if(min != i){
System.out.println("交换 " + nums[min] + " 与 " + nums[i] + "; ");
swap(nums, min, i);
}
}
}
private static void swap(int[] nums, int m, int n) {
int tmp = nums[m];
nums[m] = nums[n];
nums[n] = tmp;
}
}
选择与冒泡排序
选择与冒泡排序有很多相似的地方,经常拿二者相比较。
维度 | 选择排序 | 冒泡排序 |
---|---|---|
时间复杂度 | n² | n² |
交换次数 | 最多 n 次 | 最少 n 次 |
效率 | 交换次数少,相对效率高 | 交换次数多,相对效率低 |
场景 | 适合非常无序的场景 | 有序度高时,相对效率高,因为外层遍历次数少 |
稳定性 | 不稳定 | 稳定 |
为什么冒泡排序有时效率反而更高?
对于有序的数组,冒泡排序未发生数据交换,第一次遍历直接跳出外层循环,排序结束。
对于有序的数组,选择排序无法知道当前数据是否有序,没法提前结束循环,需要将外层循环遍历到最后。