冒泡排序及简单优化
1、冒泡排序为两个相邻的数据进行对比,然后根据排序规则,进行位置对换
2、每次循环找出一个数字按照规则排序的位置,最小循环次数为n-1,n为数组长度
如下为冒泡排序的一个代码实现
public static int[] bubbleSorted(int[] arr) { int len = arr.length; int tmp; for (int i = 0; i < len - 1; i++) { // 控制大循环次数 for (int j = 0; j < len - 1 -i; j++) { // 控制每次大循环中相邻数据的大小对比次数,-i为了提高性能,每次对比排除掉已经排序的数据 if (arr[j] > arr[j + 1]) { tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } return arr; }
以上代码中,对于大循环,最坏的情况是需要循环n-1次,如对数组{5,4,3,2,1}进行升序排列,则就至少需要n-1 = 4次外层大循环
每次循环排序结果为:
[4, 3, 2, 1, 5] [3, 2, 1, 4, 5] [2, 1, 3, 4, 5] [1, 2, 3, 4, 5]
但我们一般在排序时,很少会有刚好从倒序排序为升序或者从升序排序为倒序,如数组{1,5,3,2,4},此时n-1的每次循环排序结果为:
[1, 3, 2, 4, 5] [1, 2, 3, 4, 5] [1, 2, 3, 4, 5] [1, 2, 3, 4, 5]
可以看到,在第二次排序结束后,该数组已经是一个有序数组,后续的操作都是多余的,所以需要对外层大循环做优化,使其在达到有序数列后,就结束循环
由于冒泡排序,当发现有两个相邻数据与排序规则不符时,即发生交换,那么反过来思考,如果整个循环都没有数据交换,就证明当前数组已经是一个有序数组了,所以可通过添加当前循环是否发生了数据交换标志位进行优化
public static int[] bubbleSorted(int[] arr) { int len = arr.length; int tmp; for (int i = 0; i < len - 1; i++) { // 控制大循环次数 boolean isExchange = false; // 默认未发生数据交换,即当前数组已经为有序数组 for (int j = 0; j < len - 1 - i; j++) { // 控制每次大循环中相邻数据的大小对比次数 if (arr[j] > arr[j + 1]) { tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; isExchange = true; } } if (!isExchange) { // 已经为有序数组,跳出循环 break; } System.out.println(Arrays.toString(arr)); // 测试 } return arr; }
测试打印结果为:
[1, 3, 2, 4, 5]
[1, 2, 3, 4, 5]
此时,我们发现已经少了两次外层循环,且数组已经为有序数组。
下面看另外一个数组:{ 1, 5, 4, 3, 6, 7, 8 }
使用上述添加交换标志位排序方法进行测试,输出
[1, 4, 3, 5, 6, 7, 8]
[1, 3, 4, 5, 6, 7, 8]
第一次循环:
1和5对比,未交换
5和4对比,交换
5和3对比,交换
5和6对比,未交换
6和7对比,未交换
7和8对比,未交换
第一次循环对比结束后的数据:
[1, 4, 3, 5, 6, 7, 8]
第二次循环:
1和4对比,未交换
4和3对比,交换
4和5对比,未交换
5和6对比,未交换
6和7对比,未交换
7和8对比,未交换
第二次循环对比结束后数据:
[1, 3, 4, 5, 6, 7, 8]
已经是一个有序数列,在下次循环时,将不会有位置交换,循环结束
通过以上对比交换情况发现,两次都对5和6,6和7,7和8进行了对比,但并未发生交换,第二次循环中的5和6,6和7,7和8的对比是无意义的,在第一次循环时已经可以知道他们之间不会发生互换,已经是一个有序序列。
这种方式可以通过添加上次数据交换标志,在下次对比时,只对比到交换标志处为止来优化
public static int[] bubbleSorted(int[] arr) { int len = arr.length; int tmp; int innerloopEndIndex = arr.length -1; for (int i = 0; i < len - 1; i++) { // 控制大循环次数 int lastExchangeIndex = 0; boolean isExchange = false; for (int j = 0; j < innerloopEndIndex - i; j++) { // 控制每次大循环中相邻数据的大小对比次数 if (arr[j] > arr[j + 1]) { tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; isExchange = true; lastExchangeIndex = j + 1; } } innerloopEndIndex = lastExchangeIndex; if (!isExchange) { break; } } return arr; }
对经典的冒泡排序优化可以分为两部分
1、外层循环优化:记录交换标志,如果未发生交换,则跳出循环,排序结束
2、内层循环优化:记录上次排序位置,下次循环对比到该位置
对于类似{2,3,4,5,6,1}数组,可通过正向和反向双向冒泡来优化