目录
1.什么是冒泡排序?
冒泡排序的英文是bubble sort,是一种最基础的交换顺序。在可口可乐里面常常有许多小小的气泡飘到上面来,这是因为组成小气泡的二氧化碳比水的密度小,所以小气泡可以一点一点向上移动。而我们的冒泡排序之所以叫做冒泡排序就是因为这种排序算法的每一个元素都可以像小气泡一样根据自身大小一点一点向数组的一侧移动。
具体如何移动看下面的例子:
以上是由8个数组成的一个无序数列,我们希望它可以从小到大排序。按照冒泡排序的思想,我们要把相邻的元素两两比较,根据大小来交换位置,过程如下:
至此,元素9已经是数列中的最大元素,就像可乐里的小气泡一样一直飘到最右侧。这个时候我们的冒泡排序的第一轮结束,数组最右侧的元素9即可认为是一个有序区域,有序区域目前只有一个元素。
以此类推,第二轮排序结束后我们数列右侧的有序区有了两个元素分别是8和9,顺序如下:
第三轮排序结束后数列右侧的有序区有了三个元素分别是7,8,9.其中顺序如下:
第四轮排序结束后数列右侧的有序区有了四个元素分别是6,7,8,9,其顺序如下:
第五轮排序结束后数列右侧的有序区有了五个元素分别是5,6,7,8,9,其顺序如下:
第六轮排序结束后数列右侧的有序区有了六个元素分别是3,5,6,7,8,9,其顺序如下:
第七轮排序结束后数列右侧的有序区有了七个元素分别是2,3,5,6,7,8,9,其顺序如下:
第八轮排序结束后数列右侧的有序区有了八个元素分别是1,2,3,5,6,7,8,9,其顺序如下:
到此为止,所有的元素都有了顺序,这就是冒泡排序的整体思路。
以上这种原始的冒泡排序是稳定排序。由于该算法的每一轮要遍历所有的元素,轮转的次数和元素的数量相当,所以时间复杂度是O(N*N)。
2.原始冒泡排序的Java代码实现
package maopaopaixu;
public class MaoPaoPaiXu {
public static void paixu(int array[]){
int huan = 0;//数据交换时的中间变量
for(int i = 0;i<array.length;i++){//外层for循环控制所有回合
for(int j = 0;j<array.length - (i + 1);j++){//内层for循环代表每一轮的冒泡处理
if(array[j]>array[j+1]){//进行元素比较
//进行元素交换
huan = array[j];
array[j] = array[j+1];
array[j+1] = huan;
}
}
}
}
public static void main(String[] args) {
int[] array = new int[]{5,8,6,3,9,2,1,7};
paixu(array);
for(int i = 0;i<array.length;i++){
System.out.println(array[i]);
}
}
}
其运行结果就可以把整个数列按照从小到大的顺序输出至控制台。代码使用双循环来进行排序,外部循环控制所有的回合,内部循环代表每一轮的冒泡处理,先进行元素比较,再进行元素交换。
3.原始冒泡排序的优化方案一
回顾一下刚刚描述的排序细节,当排序算法分别执行到第6,7,8轮的时候,整个数列就已经有了正确的顺序,但是算法依然继续了第七轮和第八轮。这种情况下,如果我们能判断出数列已经有序,并且做出标记,剩下的几轮排序就可以不必执行。
package maopaopaixu;
public class MaoPaoPaiXu {
public static void paixu(int array[]){
int huan = 0;//数据交换时的中间变量
boolean panduan = true;//有序标记
for(int i = 0;i<array.length-1;i++){//外层for循环控制所有回合
for(int j = 0;j<array.length - (i + 1);j++){//内层for循环代表每一轮的冒泡处理
if(array[j]>array[j+1]){//进行元素比较
//进行元素交换
huan = array[j];
array[j] = array[j+1];
array[j+1] = huan;
panduan = false;//如果进入if循环,就说明有元素交换,所以不是有序,便将标记变为false
}
}
if(panduan){//如果元素交换没有发生,则有序标记为true,退出外层for循环,下面的回合不再执行
break;
}
}
}
public static void main(String[] args) {
int[] array = new int[]{5,8,6,3,9,2,1,7};
paixu(array);
for(int i = 0;i<array.length;i++){
System.out.println(array[i]);
}
}
}
此优化的核心思想就是如果本轮排序中元素有交换,那么就说明数列无序,如果没有元素交换,就说明数列已经有序,便直接跳出大循环。
4.原始冒泡排序的优化方案二
找一个新的数列:
3 | 4 | 2 | 1 | 5 | 6 | 7 | 8 |
这个数列的特点是:前半部分(3,4,2,1)无序,后半部分(5,6,7,8)升序,并且后半部分的元素已经是数列的最大值。而用之前的排序算法的话,就会进行几次无用的元素比较。其主要原因就在于对数列有序区的界定。按照现有的逻辑,有序区的长度和排序的轮数是相等的。比如第一轮排序过后的有序区的长度是1,第二轮排序过后的有序区的长度是2,,,,,,实际上,数列的真正的有序区可能会大于这个长度。而避免这种情况出现的方法就是:记录下最后一次元素交换的位置,那个位置也就是无序数列的边界,再往后就是有序区了。
代码如下:
package maopaopaixu;
public class MaoPaoPaiXu {
public static void paixu(int array[]){
int tmp = 0;
int lastExchangeIndex = 0;
int sortBorder = array.length - 1;//无序数列的边界,每次比较只需要比到这里为止
for(int i = 0;i<array.length;i++){
boolean isSorted = true;//有序标记,每一轮的初始都是true
for(int j = 0;j<sortBorder;j++){
if(array[j]>array[j+1]){
tmp = array[j];
array[j] = array[j+1];
array[j+1] = tmp;
isSorted = false;//有元素交换,所以不是有序,标记变为false
lastExchangeIndex = j;//把无序数列的边界更新为最后一次交换元素的位置
}
}
sortBorder = lastExchangeIndex;
if(isSorted){
break;
}
}
}
public static void main(String[] args) {
int[] array = new int[]{3,4,2,1,5,6,7,8};
paixu(array);
for(int i = 0;i<array.length;i++){
System.out.println(array[i]);
}
}
}