0. 原理
选择排序和冒泡排序一样是一种简单直观的排序算法,工作原理如下:
- 在未排序序列中找到最大或者最小的数字,放到最前或者最后的位置
- 再从剩余的序列中找到最大或者最小的数字,放到剩余序列的最前或者最后的位置
- 重复操作直到序列有序
对[2, 8, 1, 5, 3]这个数组排序,完整流程如下
round 1 start
2 8 1 5 3 (exchange data: 2 <-> 1) 1 8 2 5 3
round 1 finish
round 2 start
1 8 2 5 3 (exchange data: 8 <-> 2) 1 2 8 5 3
round 2 finish
round 3 start
1 2 8 5 3 (exchange data: 8 <-> 5) 1 2 5 8 3
1 2 5 8 3 (exchange data: 5 <-> 3) 1 2 3 8 5
round 3 finish
round 4 start
1 2 3 8 5 (exchange data: 8 <-> 5) 1 2 3 5 8
round 4 finish
可见第一趟将最小的1放到了第一位,第二趟将第二小的2放到了第二位,依此类推。
和冒泡排序类似,每一趟都会使得一个元素放到它最终的位置。和冒泡排序的差别就是它并不是两两相邻交换,而是两两可能跨越多个数字进行交换。
1. 实现
关键代码如下:
public int[] sort(int[] data) {
if (data == null || data.length <= 1) {
return data;
}
for (int i = 0; i < data.length - 1; i++) {
System.out.println("round " + (i + 1) + " start");
for (int j = i + 1; j < data.length; j++) {
if (data[i] > data[j]) {
swap(data, i, j);
}
}
System.out.println("round " + (i + 1) + " finish");
}
return data;
}
具体实现可以查看:
GitHub/SelectionSort.java
可以看到其实代码实现和冒泡很类似,同样的两个循环,只不过内循环的遍历顺序有差别,内循环的比较也不同,冒泡是相邻比较,选择则是跨越比较。
2. 复杂度分析
同样从代码和原理分析,选择排序外层循环需要n - 1次, 内层循环需要n - i次,也就是n(n-1)/2次。内循环赋值操作三次,比较操作一次。因此最坏的情况应该是整个序列是逆向的,时间复杂度是O(n²),最好的情况是序列时正序的一次扫描即可,时间复杂度为O(n),平均复杂度为O(n²)。由于不需要额外空间,空间复杂度为O(1)。
3. 优化
选择排序的优化目前只找到一个版本,就是在一次遍历的时候,找到最大和最小值,一次外循环能同时找到最大和最小值并且放到最终位置。这个优化平均可以减少一半遍历的次数,但是每个循环的比较和交换操作相应也多了,因此优化应该小于一半的时间。
下面为代码实现:
@Override
public int[] sort(int[] data) {
if (data == null || data.length <= 1) {
return data;
}
int leftIndex = 0;
int rightIndex = data.length - 1;
int minIndex;
int maxIndex;
int roundCount = 1;
while (leftIndex < rightIndex) {
System.out.println("round " + roundCount + " start");
minIndex = leftIndex;
maxIndex = rightIndex;
for (int i = leftIndex; i <= rightIndex; i++) {
if (data[i] < data[minIndex]) {
minIndex = i;
} else if (data[i] > data[maxIndex]) {
maxIndex = i;
}
}
swap(data, maxIndex, rightIndex);
swap(data, minIndex, leftIndex);
leftIndex++;
rightIndex--;
System.out.println("round " + roundCount + " finish");
roundCount++;
}
return data;
}
具体实现可以查看:
GitHub/SelectionSort.java
还是对[2, 8, 1, 5, 3]这个数组排序运行结果如下:
round 1 start
2 8 1 5 3 (exchange data: 8 <-> 3) 2 3 1 5 8
2 3 1 5 8 (exchange data: 1 <-> 2) 1 3 2 5 8
round 1 finish
round 2 start
1 3 2 5 8 (exchange data: 5 <-> 5) 1 3 2 5 8
1 3 2 5 8 (exchange data: 2 <-> 3) 1 2 3 5 8
round 2 finish
可以看到,与优化前相比遍历轮数少了一半(优化前4次,优化后2次),但是交换操作次数其实只减少了1次,少了一次left == right时的交换。偶数个元素的话交换次数应该是一致的。