冒泡排序及其优化(三种优化)

普通的冒泡排序

1
2
3
4
5
6
7
8
9
public void bubbleSort(int[] a) {
for (int i = 0; i < a.length - 1; i++) { // 外层控制循环次数
for (int j = 0; j < a.length - i - 1; j++) { // 内层保证 a[n-i-1] 是最大的
if (a[j] > a[j+1]) {
swap(a, j, j+1);
}
}
}
}

最简单版本的冒泡排序,没啥好说的,其中 swap 方法用于交换数组元素,之后都会用到:

1
2
3
4
5
private void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}

优化一:设置标志位

可以设置一个标志位,记录上一次排序是否有交换,没有交换就可以提前结束了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void bubbleSort_b1(int[] a) {
boolean hasSwap = true;
for (int i = 0; i < a.length - 1; i++) { // 外层控制循环次数
if (!hasSwap) { // 上一轮没有交换,提前结束
break;
}
hasSwap = false;
for (int j = 0; j < a.length - i - 1; j++) { // 内层保证 a[n-i-1] 是最大的
if (a[j] > a[j+1]) {
swap(a, j, j+1);
hasSwap = true;
}
}
}
}

优化二:设置结束边界

记录上一次最后交换的位置,作为下一次循环的结束边界。

最后一次比较说明在那之后的元素都已经排好序,无需再比较,可以避免一些无意义的比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void bubbleSort_b2(int[] a) {
boolean hasSwap = true;
int lastSwapIndex = a.length-1; // 最后一次交换的位置,初始时设为“数组长度 - 1”
for (int i = 0; i < a.length - 1; i++) { // 外层控制循环次数
if (!hasSwap) { // 上一轮没有交换,提前结束
break;
}
hasSwap = false;
int end = lastSwapIndex; // 上一次的最后交换位置作为这次循环的边界
for (int j = 0; j < end; j++) {
if (a[j] > a[j+1]) {
swap(a, j, j+1);
hasSwap = true;
lastSwapIndex = j; // 更新最后交换位置
}
}
}
}

优化三:双向冒泡排序

双向冒泡排序,又叫鸡尾酒排序(Cocktail Sort)。

它的过程是:先从左往右比较一次,再从右往左比较一次,然后又从左往右比较一次,以此类推。

它是为了优化前面的大部分元素都已经排好序的数组的排序,例如对于数组 [2,3,4,5,6,7,8,1],如果使用普通的冒泡排序,需要比较七次;而换成双向冒泡排序,只需比较三次。

简单起见,先看下单纯的双向冒泡排序(没有结合前面两种优化):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void cocktailSort(int[] a) {
int left = 0;
int right = a.length - 1;

while (left < right) {
for (int i = left; i < right; i++) { // 保证 a[right] 是最大的
if (a[i] > a[i+1]) {
swap(a, i, i+1);
}
}
right--;
for (int i = right; i > left; i--) { // 保证 a[left] 是最小的
if (a[i] < a[i-1]) {
swap(a, i, i-1);
}
}
left++;
}
}

最终优化:优化一 + 优化二 + 双向冒泡

将设置标志位和记录最后一次交换位置的优化思路应用在双向冒泡上,就形成了最终的优化版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void cocktailSort_b(int[] a) {
int left = 0;
int right = a.length - 1;
int lastSwapIndex = 0; // 记录最后一次交换的位置
boolean hasSwap = false; // 标志位

while (left < right) {
for (int i = left; i < right; i++) { // 保证 a[right] 是最大的
if (a[i] > a[i+1]) {
swap(a, i, i+1);
hasSwap = true;
lastSwapIndex = i;
}
}
right = lastSwapIndex; // 将最后一次交换的位置作为右边界
if (!hasSwap) { // 上一轮没有交换,提前结束
break;
}
hasSwap = false;
for (int i = right; i > left; i--) { // 保证 a[left] 是最小的
if (a[i] < a[i-1]) {
swap(a, i, i-1);
hasSwap = true;
lastSwapIndex = i;
}
}
left = lastSwapIndex; // 将最后一次交换的位置作为左边界
if (!hasSwap) { // 上一轮没有交换,提前结束
break;
}
hasSwap = false;
}
}

写在最后

这篇文章主要是记录了冒泡排序的优化思路和实现方式。没有什么图片说明,可能比较抽象,如果需要看图来理解的话推荐你看下小灰的两篇介绍冒泡排序和鸡尾酒排序的文章。我就是看了小灰的文章才理解了双向冒泡排序优化在哪里。小灰的文章链接我会在下面参考资料中给出。

参考

-------------    本文到此结束  感谢您的阅读    -------------
0%