冒泡排序
冒泡排序恐怕是我们计算机专业课程上以第一个接触到的排序算法,也算是一种入门级的排序算法。他同样是三大排序算法之一,想要学习其他的排序算法,这个都是基础。它的基本思想是:两两比较相邻记录的关键字,如何反序则交换,直到没有反序的记录为止。
冒泡排序算法原理:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
- 针对所有的元素重复以上的步骤,除了最后一个。
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
一次比较过程如图所示:
代码实现
代码a:基础版
我们通常容易想到最简单的实现代码:
public class Test09 {
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
private static void printArr(int[] arr) {
for (int anArr : arr) {
System.out.print(anArr + " ");
}
}
private static void bubbleSort(int[] arr) {
if (arr == null)
return;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = i + 1; j < arr.length; j++) {
//i在这个循环中不变,是一个值一直和后面的元素比较
if (arr[i] > arr[j])
swap(arr, i, j);
}
}
}
public static void main(String[] args) {
int[] arr = {6, 4, 2, 1, 8, 3, 7, 9, 5};
bubbleSort(arr);
printArr(arr);
}
}
严格地讲,上面的算法并不是冒泡排序,因为 它完全不符合两两相邻比较。它更应该是最最简单的就交换排序而已。它的思路是让每一个关键字,都和它后面的每一个关键字比较,如果大则交换,这样第一位置的关键字在一次循环后一定变成最小值。
我们不妨来看看正宗的冒泡排序算法。
代码b :进阶
public class Test09 {
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
private static void printArr(int[] arr) {
for (int anArr : arr) {
System.out.print(anArr + " ");
}
}
private static void bubbleSort(int[] arr) {
if (arr == null)
return;
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 1; j < arr.length - i; j++) {
//一直是相邻的两个元素进行比较,且j一直累加
if (arr[j - 1] > arr[j]) {
swap(arr, j - 1, j);
}
}
}
}
public static void main(String[] args) {
int[] arr = {6, 4, 2, 1, 8, 3, 7, 9, 5};
bubbleSort(arr);
printArr(arr);
}
}
上述代码是否完美了呢?答案是否定的,我们假设待排序的序列是 {2,1,3,4,5,6,7,8,9},也就是说,除了第一和第二个关键字需要交换外,别的都应该是正常的顺序,当 i = 1 时,交换了 2 和 1 的位置,此时已经有序,但是算法依然不依不挠地将 i = 2 到 9 以及每一个内循环都执行了一遍,尽管没有交换数据,但之后的大量比较还是大大的多余了。所以我们完全可以设置一个标记位 isSort
,当我们比较一次后都没有交换,则代表数组已经有序了,此时直接退出循环即可。
既然思路已经确定,那代码自然是很信手拈来了。
代码c :进阶的进阶
public class Test09 {
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
private static void printArr(int[] arr) {
for (int anArr : arr) {
System.out.print(anArr + " ");
}
}
private static void bubbleSort(int[] arr) {
if (arr == null)
return;
// 定义一个标记 isSort,当其值为 true 的时候代表已经有序。
boolean isSort;
for (int i = 0; i < arr.length - 1; i++) {
isSort = true;
for (int j = 1; j < arr.length - i; j++) {
if (arr[j - 1] > arr[j]) {
swap(arr, j - 1, j);
isSort = false;
}
}
//第二个for每循环一次结束后进行判断,只要是isSort没有变化,则已有序
if (isSort)
break;
}
}
public static void main(String[] args) {
int[] arr = {6, 4, 2, 1, 8, 3, 7, 9, 5};
bubbleSort(arr);
printArr(arr);
}
}
Perfect 的代码,但冒泡排序在数组长度较大的时候,效率真的很低下,所以在实际生产中,我们也很少使用这种算法。
我们用一个 boolean 变量 isSort 来判断是否已经排序完成,当一整趟遍历都没有发生数据交换的时候,说明已经排序完成,直接 break 退出循环即可。
我们试想一下这样的场景
:假设有 100 个数字的数组,仅仅前 10 个无序,后面 90 个均有序并且都大于前面 10 个数字。
我们采用上面的终极算法可以明显看到,第一趟排序后,最后发生交换的位置必定大于 10,且这个位置之后的数据必定已经有序了,但我们还是会去做徒劳的 90 次遍历,而且我们还要遍历 10 次!(前十个数每个都要进行徒劳的90次遍历)
显然我们可以找到这样的思路
,因为是找最大的放在最后,放在最后的一定是最大的或者说最后的一定是有序的,因此,
在第一次排序后,就记住最后发生交换的位置,第二次只要从数组头部遍历到这个位置就 OK 了。
我们不妨直接看看代码实现:
代码 d :终极版
public class Test09 {
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
private static void printArr(int[] arr) {
for (int anArr : arr) {
System.out.print(anArr + " ");
}
}
private static void bubbleSort(int[] arr) {
if (arr == null)
return;
int flag = arr.length;
int k;
for (int i = 0; i < arr.length - 1; i++) {
k = flag;
//用k来存贮最后检测到的长度
flag = 0;
for (int j = 1; j < k; j++) {
// j<k,使每次遍历都在最后一次遍历的点停止
if (arr[j - 1] > arr[j]) {
swap(arr, j - 1, j);
flag = j;
}
}
if (flag == 0)
break;
}
}
public static void main(String[] args) {
int[] arr = {6, 4, 1, 2, 3, 5, 7, 8, 9};
bubbleSort(arr);
printArr(arr);
}
}
其实算法也就那么一回事儿,用心去理解它的原理,理解后,无论是用哪种语言实现起来都是非常简单的。
冒泡排序时间空间复杂度及算法稳定性分析
对于长度为 n 的数组,冒泡排序需要经过 n(n-1)/2 次比较,最坏的情况下,即数组本身是倒序的情况下,需要经过 n(n-1)/2 次交换,所以其
冒泡排序的算法时间平均复杂度为 O(n²)。空间复杂度为 O(1)。
可以想象一下:如果两个相邻的元素相等是不会进行交换操作的,也就是两个相等元素的先后顺序是不会改变的。如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个元素相邻起来,最终也不会交换它俩的位置,所以相同元素经过排序后顺序并没有改变。
所以冒泡排序是一种稳定排序算法。所以冒泡排序是稳定排序。这也正是算法稳定性的定义:
排序算法的稳定性:通俗地讲就是能保证排序前两个相等的数据其在序列中的先后位置顺序与排序后它们两个先后位置顺序相同。
冒泡排序总结:
- 冒泡排序的算法时间平均复杂度为 O(n²)。
- 空间复杂度为 O(1),原地交换,不需要额外空间。
- 冒泡排序为稳定排序。
转自公众号nanchen