前言:
冒泡排序、选择排序、插入排序都是时间复杂度为n^2的排序算法,也是我们学习后续学习其他排序算法的基础,接下来我会尽可能详细地给大家详解这三种排序算法的时间和优化方案,如果大家有所收获,请留下一个大大的赞。
冒泡排序:

基础代码:
/**
* 普通的冒泡排序
* 双重for循环 外部循环控制交换的总次数 内部循环控制当前循环交换的范围;
* 本方法是每一次进行交换,都会将当前循环范围下最大的值放置本次范围的末尾;
* @param nums 待排序数组
*/
public static void bubblingSort01(int[] nums) {
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < nums.length - i - 1; j++) {
if (nums[j] > nums[j+1]){
swap(nums,j,j+1);
}
}
}
}
public static void swap(int [] array,int m ,int n){
int temp = array[m];
array[m] = array[n];
array[n] = temp;
}
问题:从我们上方照片中举的{6,1,2,3,5,4}例子可以发现,如果我们的数组存在部分有序,就有可能出现我们不需要进行全部循环排序的情况下,我们的数组已经有序了。这意味着我们的冒泡排序可以进行优化,如果本次循环不再进行数组元素的交换,就直接结束循环(只有有序,数组才不会进行交换)。
优化后的代码:
public static void bubblingSort02(int[] nums) {
for (int i = 0; i < nums.length; i++) {
//定义一个boolean变量,初始为true,如果本次循环没有发生交换,就直接退出循环
boolean flag = true;
for (int j = 0; j < nums.length - i - 1; j++) {
if (nums[j] > nums[j + 1]) {
swap(nums, j, j + 1);
flag = false;//一旦本次循环发生了交换,令flag = false;
}
}
if (flag) break;
}
}
public static void swap(int[] array, int m, int n) {
int temp = array[m];
array[m] = array[n];
array[n] = temp;
}
问题:从我们上方照片中举的{6,1,2,3,5,4}例子可以发现,我们每一次内部交换的范围会不断缩小,越来越逼近于0。这种范围的缩小,我们是可以进行更加精准的进行控制。本次交换的范围永远都小于上一次交换元素的下标,从而减少内部循环的总次数。
进一步优化后的代码:
public static void bubblingSort03(int[] nums) {
//我们通过lastSwappedIndex变量来控制循环的交换范围,从而减少内部循环的次数
int lastSwappedIndex = nums.length - 1;
int count = 0;
for (int i = 0; i < nums.length; i++) {
boolean flag = true;
int swappedIndex = lastSwappedIndex-i;
for (int j = 0; j < lastSwappedIndex; j++) {
if (nums[j] > nums[j + 1]) {
swap(nums, j, j + 1);
flag = false;
//将当前进行交换的元素的下标赋值给swappedInedx;
swappedIndex = j;
}
}
if (flag) break;
//将进行交换的最后一个元素的下标赋值给lastSwappedIndex,从而缩小下次循环交换的范围;
lastSwappedIndex = swappedIndex;
}
}
public static void swap(int[] array, int m, int n) {
int temp = array[m];
array[m] = array[n];
array[n] = temp;
}
冒泡排序就像是水底的气泡不断向上升起,在符合条件的情况下,不断同相邻的数组元素进行交换,每一次都把最大或者最小的数组放置到当前数组范围的起始点或者末尾。
选择排序
选择排序的思想是:双重循环遍历数组,每经过一轮比较,找到最小元素的下标,将其交换至首位。
基础代码:
/**
* 选择排序01:
* 外层循环控制循环的次数,内层循环寻找本次循环范围内的最小值
* @param nums 待排序数组
*/
public static void selectSort01(int[] nums) {
for (int i = 0; i < nums.length; i++) {
int minIndex = i;
for (int j = i + 1; j < nums.length; j++) {
//如果遇到比当前最小下标元素还小的元素,就进行最小下标的更换
if (nums[minIndex] > nums[j]) {
minIndex = j;
}
}
if (i != minIndex) {
swap(nums, i, minIndex);
}
}
}
public static void swap(int[] array, int m, int n) {
int temp = array[m];
array[m] = array[n];
array[n] = temp;
}
问题:对于上述的选择排序,我们在每一轮比较中找出当前数组范围的最小值,其实我们也可以在找出最小值的同时寻找最大值,较少比较的次数,这就是二元选择排序,在每一轮比较中同时寻找最大值和最小值,并进行元素的交换。
优化后二元选择排序代码:
/**
* 二元选择排序
* 外层循环控制循环的次数,内层循环寻找本次循环范围内的最大值和最小值
* 由于我们一次循环可以寻找最大值和最小值,所以我们的外层循环可以缩短为原来的一倍
* @param nums 待排序数组
*/
public static void selectSort02(int[] nums) {
for (int i = 0; i < nums.length / 2; i++) {
int minIndex = i;
int maxIndex = i;
for (int j = i + 1; j < nums.length-i; j++) {
if (nums[minIndex] > nums[j]) {
minIndex = j;
}
if (nums[maxIndex] < nums[j]) {
maxIndex = j;
}
}
//如果最大值和最小值的下标相同,就意味着剩下的元素不需要再进行排序了(同等大小的值或者只剩下最中间的元素)
if (minIndex == maxIndex) break;
//进行最小值的交换
int temp = nums[i];
nums[i] = nums[minIndex];
nums[minIndex] = temp;
//由于我们先进行最小值的交换,这时如果我们的最大值下标等于i,我们就需要最maxIndex进行更新
if (i == maxIndex) maxIndex = minIndex;
temp = nums[maxIndex];
nums[maxIndex] = nums[nums.length - i - 1];
nums[nums.length - i - 1] = temp;
}
}
插入排序
插入排序是将待排序数组分为两部分,一部分是排序好的数组,另一部分是为排序数组。插入排序的过程就是不断从未排序数组中取出元素,插入到有序数组中合适的位置。其中插入的过程有两种方法,一种是交换插入法,一种是位移交换法。交换和位移只是不同的插入方法,其中,位移交换法是交换插入法的优化。

交换插入法:
/**
* 交换插入法
* 外层for控制循环的次数并获取当前待插入的数组元素
* 内层while循环,不断将待插入的数组元素同已排序好的数组部分进行比较,为待插入的数组寻找合适的位置
* @param nums 待排序数组
*/
public static void insertSort03(int[] nums) {
for (int i = 1; i < nums.length; i++) {
int j = i;
//以当前下标为起始,不断向前进行遍历,直到为i找到合适的位置
//为i寻找合适位置的过程,是下标i同其他下标元素不断进行交换的过程
while (j >= 1 && nums[j - 1] > nums[j]) {
swap(nums, j, j - 1);
j--;
}
}
}
public static void swap(int[] array, int m, int n) {
int temp = array[m];
array[m] = array[n];
array[n] = temp;
}
问题:通过交换插入法的交换过程可以明白,我们对待排序元素进行交换,可能并不是待排序元素的最终位置,还需要再进行不断地交换,交换的次数过多。优化方向:只有待排序的元素到达合适位置时才进行交换。寻找的过程是将j-1的元素不断赋值给j元素,实现数组元素的向后位移。
位移插入法:
/**
* 位移插入法
* @param nums 待排序数组
*/
public static void insertSort04(int[] nums) {
for (int i = 1; i < nums.length; i++) {
int j = i;
// 声明临时变量,存储待排序的元素
int temp = nums[i];
// j 控制交换的范围,防止 j-1 出现下标越界的情况
// 让待排序元素同已排序的数组部分进行比较,如果已排序的数组元素大于待排序数组,就进行数组元素的后移操作
while (j >= 1 && nums[j - 1] > temp) {
nums[j] = nums[j-1];
j--;
}
// 通过之前的while循环,已经为j找到了合适的位置,直接继续插入即可
nums[j] = temp;
}
}
总结:
冒泡排序、选择排序、插入排序都是时间复杂度为n^2,空间复杂度为1的算法。基本结构是for循环,难度较为简单,易于理解。