一、什么是冒泡排序?
冒泡排序(Bubble Sort)是一种最为基础的交换排序,相信学过C语言的,都接触过这种排序算法。
这篇文章重点应该放在优化上面。
二、冒泡排序的实现思想:
将数组里面相邻的元素两两比较,根据大小来交换元素位置,举个栗子:
这里有一个数组array[4, 6, 5, 8, 9, 3, 2, 1, 7],
首先4和6比较,4小于6,位置不变,接下来6和5比较,6大于5,所以6和5的位置对调,数组变成[4, 5, 6, 8, 9, 3, 2,1, 7],由于6和5位置对调,接着是6和8比较,6小于8,所以位置不变,如此类推,第一轮排序后,数组变成[4, 5, 6, 8, 3, 2, 1, 7, 9],第二轮又从第一个元素4开始比较,但是最终比较的元素不是9而是7,因为第一轮比较,已经是确定将最大的元素放到了最后的位置,所以没有必要与最后的元素进行比较,这一轮最终结果为[4, 5, 6, 3, 2, 1, 7, 8, 9],
如此类推,完成全部排序总共需要array.length x( array.length-1)/2次比较(这个是等差数列计算出来的,有兴趣的可以自己算一下)。因为每一轮都要全部比较,所以最原始的冒泡排序叫做稳定排序。
根据这种原始思想,可以得到冒泡排序的原始版:
public void sortArray(int[] array){ int temp; for(int i=0; i<array.length; i++){ for(int j=0; j<array.length-i-1; j++){ if(array[j] > array[j+1]){ temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; } } } }
我们把每一轮结果罗列出来时,
第三轮结果:[4, 5, 3, 2, 1, 6, 7, 8, 9]
第四轮结果:[4, 3, 2, 1, 5, 6, 7, 8, 9]
第五轮结果:[3, 2, 1, 4, 5, 6, 7, 8, 9]
第六轮结果:[2, 1, 3, 4, 5, 6, 7, 8, 9]
第七轮结果:[1, 2, 3, 4, 5, 6, 7, 8, 9]
第八轮结果:[1, 2, 3, 4, 5, 6, 7, 8, 9]
从结果可以看出,程序做了些“无用功”,为了避免程序做这些“无用功”,要对基础版本程序作出一些修改,
优化第一版:
public void sortArray(int[] array){ int temp; for(int i=0; i<array.length; i++){ boolean isSorted = true; for(int j=0; j<array.length-i-1; j++){ if(array[j] > array[j+1]){ temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; isSorted = false; } } if(isSorted){ break; } } }
程序定义了一个boolean类型的isSorted变量,用来判断往后的循环当中,数组是否已经是有序的,每一轮循环都会设置其值为true,当有元素对调位置时,就将isSorted的值设置为false,表示该数组还不是有序数组。每一轮都要判断isSorted的值,如果判断当前一轮操作没有元素有位置调换,那么可以提前结束所有的循环。当然,本次栗子中用到的数组还是需要进行8轮循环,因为,第7轮的时候isSorted的值会被设置为false,到了第八轮才是true,读者可以自行举例别的数组检验。
还是拿回每一轮运行结果出来:
第三轮结果:[4, 5, 3, 2, 1, 6, 7, 8, 9]
第四轮结果:[4, 3, 2, 1, 5, 6, 7, 8, 9]
第五轮结果:[3, 2, 1, 4, 5, 6, 7, 8, 9]
第六轮结果:[2, 1, 3, 4, 5, 6, 7, 8, 9]
第七轮结果:[1, 2, 3, 4, 5, 6, 7, 8, 9]
第八轮结果:[1, 2, 3, 4, 5, 6, 7, 8, 9]
这里讲解得详细一点,以第三轮结果,在第四轮运行操作中,4和5比较,4<5,不调换位置,5和3比较,5>3,位置对调,数组变成[4, 3, 5, 2, 1, 6, 7, 8, 9],5和2比较,5<2,位置对调,变成[4, 3, 2, 5, 1, 6, 7, 8, 9],5和1比较,5>1,位置对调,变成[4, 3, 2, 1, 5, 6, 7, 8, 9]。后面就是5和6比较,6和7比较,7和8比较,8和9比较,但是这四次比较对数组排序都没有任何“贡献”,同理,在第五轮循环操作中,没有“贡献”的操作会增加一次,这是不希望出现的。
这里要介绍一个概念——有序区,有序区指数组有序的区域,这里只数组末尾的有序元素组成的区域,在极端的情况,如[9, 8, 7, 6, 5, 4, 3, 2, 1],按照从小到大顺序排序,每一轮排序,有序区只增加一位元素,但更多的情况有序区元素是大于循环轮次,当有序区元素等于数组长度时,可以认为这个数组已经排序完成,所以下面给出第二次优化,
优化第二版:
public void sortArray(int[] array){ int border = array.length-1; int lastIndex = 0; int temp; for(int i=0; i<array.length; i++){ boolean isSorted = true; for(int j=0; j<border; j++){ if(array[j] > array[j+1]){ temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; lastIndex = j; isSorted = false; } } border = lastIndex; if(isSorted){ break; } } }
这一版新增了两个int类型变量,一个是border,表示无序项的边界,同时也是每一轮循环的次数设定值,另一个是lastIndex,用来记录最后元素需要交换的下标值,进行一轮循环后,将这个值赋值给border,作为下一轮循环的次数。每一轮循环,当有元素需要调换位置时,记录j的位置,当前轮次循环结束,就将lastIndex赋值给border,最为新一轮循环操作的边界。
以第五轮结果为栗子,[3, 2, 1, 4, 5, 6, 7, 8, 9],
在进行第六轮循环操作时,3和2比较,3>2,位置对调,变成[2, 3, 1, 4, 5, 6, 7, 8, 9],此时lastIndex = j = 0,3和1比较,3>1,位置对调,变成[2, 1, 3, 4, 5, 6, 7, 8, 9],此时lastIndex = j = 1,3和4比较,3<4,位置不变,如此类推,本轮循环结束时,lastIndex = 1,那么此时border = 1,在第七轮循环里面,只需要进行1次比较就可以结束第七轮循环。
但是,优化第二版仍不是最优方案,上面的两种优化方案只是减少每轮的操作次数,还有一种可以直接减少循环的轮数,那就是鸡尾酒算法排序,这个留到下一篇更新。