众所周知,冒泡排序这玩意儿,只在笔试面试的时候可能有用......
一、传统冒泡排序
冒泡排序为什么叫冒泡排序?这跟它的机制有关,可以理解为每次从下标为0开始(叛逆一点也可以从末端开始),元素不断冒头,跟数组下一个数进行比较,如果前一个数大于/小于后一个数,就进行交换;(假设长度为n)第1轮结束,第n个数就是有序的;第2轮结束,第n-1往后的2个数就是有序的;...;第n-1轮结束,第2往后n-1个数就是有序;第n轮不用比较,因为第2往后的数都已经绝对有序了;
以下图5个数据从小到大为例子(不要把下标看做数据了,题外话:为什么下标从0开始而不是从1,我在网上看到过一种说法是,其实叫偏移可能更容易理解,你看下标为4的不就是相对前面偏移了4个位置吗)
先上代码混个眼熟,下面结合图例继续讲解;
java
// 以下面测试用例的数据做讲解,这里排序过后是从小到大
public static void bubbleSort(int[] arr) {
// 长度是5, 下标是0~4
int n = arr.length;
// 这里i < n - 1;的意思是,i==0、i==1、i==2、i==3时需要循环比较,也就是比较4次,i==4也就是第五次不需要比较是因为
for (int i = 0; i < n - 1; i++) {
// 这里j < (n - 1) - i;的意思是,默认需要排序(n - 1)也就是4次;
// 但是由于每一次排序(i)都会比上一次多1个数字有序,那么下一次我就不用比较那么多数字了,没有意义
// 比如
// 第一次比较前:65、32、45、11、32
// 第一次比较后:32、45、11、32、65;作为最大数65已经稳稳坐在最后了,后面的数跟它再比较一次已经没有意义了
// 第一次是全员默认无序,所以-i也就是-0,也就是全员都比较;然后第一次比较完,65已经有序(1个数字也算是有序),
// 所以我第二次比较,拿下标0开始+1比较(下标0不用跟自己比较),也就是跟下标0会跟下标1、2、3分别比较,即3次;
// 每一次比较都会多1个数字有序(不需要再比较),反过来需要比较的数字就会少1个;
// 所以这里j < (n - 1) - i;为什么-i可能会有同学疑惑(至少我学的时候会疑惑),其实就是每次循环需要减少比较的次数,
// 完全可以额外设置一个临时变量替代
for (int j = 0; j < (n - 1) - i; j++) {
// 从下标0开始比较,单次循环会将两个比较数中较大的往后挪,小的往前挪;
// 整个循环过后,会将最大数放在最后位置;
if (arr[j] > arr[j + 1]) {
// 交换 arr[j] 和 arr[j+1]
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
开始比较第一趟:直接拿下标为0的数据(65),跟下一个数据(32)进行比较
如果当前数字比下一个数字要大,就交换位置,那很明显,65>32这里是要交换的;并且交换完后要继续跟下一个数据(45)进行比较;
这里拿出来比较65也可以说不是原来那个65了(不是说地址,是说下标为0),因为下标是1了(交换后);为什么要强调这一点,因为冒泡排序其实是从下标0的数据开始,跟下一个数字比较,数字大的往后挪,而不是逮着第1个数字一直往后比较;
简单来说:第二次比较,从下标为1(第2个数字)的位置拿到65,跟下标为2(第3个数字)的45进行比较,如果当前数字比下一个数字要大,就交换位置,那很明显,65>45这里是要交换的;
然后第一轮(4次比较过后),最大的数字就在最后的位置(也就是这个数字有序了,不用再比较了,下一轮比较的次数-1);
然后第二轮(3次比较过后),最后2个数字有序,不用再比较了,下一轮比较的次数再-1;
然后第二轮(2次比较过后),最后3个数字有序,不用再比较了,下一轮比较的次数再-1;
这里有几个重要的点要说明:
- 特别表明了前面的32是蓝色,后面的32是红色;会发现,第2次比较的时候蓝32=红32,所以不会交换位置,也就是说明,冒泡排序是一种稳定的排序(简单来说就是两个或以上相等的数据在排序过后不会改变其相对位置,即蓝32原来在红32的前面,那排序过后蓝32还是在红32的前面);
- 会发现,这一轮(第3轮)比较完,整个数组已经有序了,但是按照上面的代码写,还是会再比较第4轮,因为第3轮过后只是默认后3个数字有序,还不知道前2个数字是否有序;
所以很明显,这种传统的冒泡排序,无论数组是怎么样的,第一轮比较n-1次,最后一轮比较1次,总共n-1轮,时间复杂度就是O(((n-1)+1)*(n-1)/2)=O((n*(n-1))/2)=O(n^2)
,这里最后为什么是n的2次方是因为在时间复杂度表示的规则所致;
二、稍微进阶一点点的冒泡排序
是一种很简单,又出奇好用的点,就多一个标志位,如果某一轮一个数都没有被交换过,那行,说明已经有序了,直接退出!
还是先上代码混个眼熟
java
public static void optimizedBubbleSort(int[] arr) {
int n = arr.length;
// 加了个标志位
boolean swapped;
for (int i = 0; i < n - 1; i++) {
swapped = false;
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
// 一旦有比较过,就将标志位设置成true(代表有交换过)
swapped = true;
}
}
// 如果一次循环没有发生交换,则数组已经有序
if (!swapped) {
break;
}
}
}
就比如说这种已经排好序的:第一轮比较还是要,比较了n-1次,发现不需要交换,哎,我直接退出,拜拜;
传统的冒泡排序时间复杂度是O(n^2),而这种进阶版的,要分情况,最好的情况,已经完全排好序了,比较n-1次,时间复杂度就是O(n),最差的情况,完全逆序,跟传统冒泡排序一样,时间复杂度是O(n^2),也就是进阶了......但是进的不多;
所以总的来说,冒泡排序是一种效率比较差的排序,基本上......只有面试笔试可能会用到而已,真研究的话......加油!