一、选择排序
顾名思义,选择排序就是每次选出待选数组中最大的元素,放到待选数组右侧即可。为了实现O(1)的空间复杂度呢,这个放到右侧的操作通过元素在数组间交换来进行。
第一次扫描整个大小为n的数组,最大的放数组最右边;此后从左到右扫描数组前n-1、n-2...项,因为数组右端已经排序完成。下面是代码实现:
public void chooseSort(int[] nums) {
// 选择排序
for(int j = nums.length - 1; j > 0; j--) {
int maxIndex = 0;
for(int i = 1; i <= j; i++) {
if(nums[i] > nums[maxIndex]) {
maxIndex = i;
}
}
swap(nums, j, maxIndex);
}
}
时间复杂度:代码有两重for循环,而且无论什么情况下循环都不会提前中止。因此最坏、最好情况下,时间复杂度均为O(n2);空间复杂度:显然为O(1);稳定性:当原数组中有大小相同的元素时,相同元素最左一个在排序过程中会被换到右边去,因此不稳定。
二、冒泡排序
这应该是很多同学接触最早的排序算法了。和选择排序类似,冒泡排序保证每一趟排序后,找出最大的元素放到数组最右侧,只是实现方案不同。选择排序通过缓存、更新最大元素的index,最终把index对应元素一次交换到最右方进行;冒泡排序则通过控制相邻元素的交换与不交换来把最大元素转移到右侧。以下是实现代码:
public void bubbleSort(int[] nums) {
// 冒泡排序
for(int i = nums.length - 1; i > 0; i--) {
boolean flag = true; // 若此处不加flag位来提前结束,则算法循环结构与选择排序完全一致,最好情况下复杂度也为二次
for(int j = 1; j <= i; j++) {
if(nums[j-1] > nums[j]) {
swap(nums, j-1, j);
flag = false;
}
}
if(flag) {
break;
}
}
}
时间复杂度:可以看出,冒泡排序的循环结构和选择排序是类似的,但在数组整体已经有序的情况下可以提前结束循环。因此最好情况为O(n)、最差为O(n2)、平均为O(n2);空间复杂度:O(1);稳定性:数组中有相同大小元素时,相同大小元素若不相邻,则会在排序过程中被逐渐交换到相邻,过程中相对位置明显不改变。相邻之后,相同大小元素区间内不会再进行交换,因此算法稳定。
在原数组“基本有序”的情况下,冒泡排序时间复杂度可以接近线性。反序情况下,与选择排序同为O(n2)时间复杂度,且因为交换次数过多,效率低于选择排序。