冒泡排序
冒泡排序是一种简单的排序算法,它是重复地遍历要排序的元素列,依次对两个相邻的元素进行比较,如果他们的顺序(如从大到小、首字母从A到Z)相反则将他们进行交换,这样每一趟遍历会将最大或者最小的元素移动到最右端,如果一趟走访没有相邻的元素需要交换,则说该元素列已经有序,有n个元素的元素列最多需要走访n-1趟。
实现思路
- 比较相邻的两个元素,如果第一个比第二个大(小),则交换他们位置。
- 对每一对相邻元素做上述的工作,从开始第一对到结尾的最后一对,则最后的元素应该会是最大(最小)的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每趟对越来越少的元素重复上面的步骤,直到没有任何一对元素需要交换。
举例说明
待排数组:{21,10,52,4,9,2}
第一趟排序过程:
第一次比较:21和10比较,21比10大,交换位置,得:10,21,52,4,9,2
第二次比较:21和52比较,21比52小,不用交换位置,得:10,21,52,4,9,2
第三次比较:52和4比较,52比4大,交换位置,得:10,21,4,52,9,2
第四次比较:52和9比较,52比9大,交换位置,得:10,21,4,9,52,2
第五次比较:52和2比较,52比2大,交换位置,得:10,21,4,9,2,52
第一趟排序结束,排序结果:10,21,4,9,2,52
依照上面的操作方法,可得到:
第一趟排序结果:10,21,4,9,2,52
第二趟排序结果:10,4,9,2,21,52
第三趟排序结果:4,9,2,10,21,52
第四趟排序结果:4,2,9,10,21,52
第五趟排序结果:2,4,9,10,21,52
最终排序结果:2,4,9,10,21,52
代码实现
冒泡排序一般实现
/**
* 最简单的实现方法
* @param a
*/
public static void bubbleSort(int[] a) {
int n = a.length;
for (int i = 1; i < n; i++) { // 一共n-1趟排序
for (int j = 0; j < n - i; j++) { // 第i趟排序从第0个元素到第n-i个元素
if (a[j] > a[j + 1]) {
int temp;
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
优化一:
对于一些连片有序而整体无序的序列,如{2,4,6,8,7},按照上面的排序方式,第一趟排序后将8和7交换后,该数组已经有序,接下来的3趟排序就是多余的。因此我们可以增加一个标志,如果某一趟排序没有交换元素,说明这组数据已经有序,不用再继续下去,就可以结束排序。具体的实现代码请看下面:
/**
* 优化一:设置了一个boolean的标志flag,如果这趟排序发生了数据的交换,则flag置为true,否则flag置为false。
* @param a
*/
public static void bubbleSort1(int[] a) {
int n = a.length;
boolean flag = true; // 第一次判断时,将flag置为true
for (int i = 1; i < n && flag; i++) {
flag = false; // 每趟排序前先将flag置为false
for (int j = 0; j < n - i; j++) {
if (a[j] > a[j + 1]) {
int temp;
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
flag = true; // 表示有数据交换
}
}
}
}
优化二:
优化一仅仅适用于连片有序而整体无序的数据(例如:1, 2,3 ,4 ,7,6,5)。但是对于前面大部分是无序而后边小半部分有序的数据(3,2,4,7,4,3,7,8,9,10)排序效率也不可观,对于种类型数据,我们可以继续优化。怎么优化呢?我们可以记下最后一次交换的位置,在最后一次交换的位置后面,必然是有序的,然后下一趟排序从第一个比较到上次记录的位置结束即可,具体的实现代码请看下面:
/**
* 优化二:在优化一的基础上增加了一个位置变量pos,用于记录这趟排序最后发生交换的位置,然后作为下一趟排序比较结束的位置
* @param a
*/
public static void bubbleSort2(int[] a) {
int n = a.length;
boolean flag = true;
int rightPos = n; // 最后一次发生交换的位置
int right = n - 1;
for (int i = 1; i < n && flag; i++) {
flag = false;
for (int j = 0; j < right; j++) {
if (a[j] > a[j + 1]) {
int temp;
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
flag = true;
rightPos = j; // 记录发生交换的位置
}
}
right = rightPos; // 下一次只需比较到pos的位置即可
}
}
优化三:
除了上面提到的两种优化方法,还有一种优化方法可以继续提高效率。大致思想是这样:每一趟遍历可以确定两个值,从左到右扫描将最大元素放到最后面,然后从右到左扫描将最小元素放到最前面。例如:待排序列为{1,2,3,4,0},第一趟从做到又扫描得到:{1,2,3,0,4},再一趟从右到左扫描得到{0,1,2,3,4},这样该序列就已经有序了。具体的实现代码请看下面:
public static void bubbleSort3(int[] a) {
int n = a.length;
boolean flag = true;
int leftPos = 0; // 左部分最后发生交换的位置
int left = 0;
int rightPos = n; // 右部分最后一次发生交换的位置
int right = n - 1;
for (int i = 1; i < n; i++) {
flag = false;
// 从左向右扫描将最大值移到最后
for (int j = 0; j < right; j++) {
if (a[j] > a[j + 1]) {
int temp;
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
flag = true;
rightPos = j; // 记录发生交换的位置
}
}
if(flag) {
flag = false;
} else {
break;
}
right = rightPos;
// 从右到左扫描将最小值移到最前面
for(int j = right; j > left; j--) {
if(a[j] < a[j - 1]) {
int temp = a[j];
a[j] = a[j - 1];
a[j - 1] = temp;
flag = true;
leftPos = j;
}
}
left = leftPos;
if(!flag) {
break;
}
}
}
算法性能分析:
- 空间复杂度:
从空间复杂度上看,冒泡排序仅用了一个辅助单元,空间复杂度为O(1)。
- 时间复杂度:
从时间复杂度上看,最好的情况是序列已经有序时,在第一趟遍历过程中,一次交换都没发生,所以在执行一趟排序之后就结束,这时只需要比较n-1次,不需要交换元素;最坏的情况为逆序状态,总共要进行n-1趟排序,在第i趟排序中,比较次数为n-i次,交换次数为3(n-i)次,则总的比较次数为 (n-1)+(n-2)+(n-3)+...+2+1=n*(n-1)/2次;总的交换次数为3(n-1)+3(n-2)+3(n-3)+...+6+3=3n(n-1)/2次。因此,冒泡排序算法的时间复杂度为O(n²)。
- 算法稳定性:
冒泡排序是一种稳定的排序算法。