相关概念
- 稳定:如果 a 原本在 b 前面,而 a = b,排序之后 a 任在 b 前面
- 不稳定:如果 a 原本在 b 前面,a = b,排序之后 a 可能会出现在 b 后面
- 时间复杂度:对排序数组总的操作次
- 空间复杂度:算法在计算机内执行时所需存储空间的度量
冒泡排序(BUBBLE SORT)
原理
比较两个相邻元素,将值大的元素交换至右端。
动态效果示意图
思路
1. 依次比较两个元素,将小数放在前面,大数放在后面。即在第一趟:首先比较第一个和第二个数,将小数放在前面,大数放在后面。然后比较第二个和第三个数,小数放前,大数放后;直至比较到最后两个数,小数放前,大数放后。
2. 第一趟比较完成后,最后一个数一定是数组中最大的那个数,所以第二趟比较的时候最后一个数不参与。
3. 依次类推,每一趟比较次数减1。
- 举例说明
6, 3, 8,2, 9,1
第一趟排序:
第一次排序:6和3比较,6大于3,交换位置:3, 6, 8,2, 9,1
第二次排序:6和8比较,6小于8,不交换位置:3, 6, 8,2, 9,1
第三次排序:8和2比较,8大于2,交换位置:3, 6, 2,8, 9,1
第四次排序:8和9比较,8小于9,不交换位置:3, 6, 2,8, 9,1
第五次排序:9和1比较,9大于1,交换位置:3, 6, 2,8, 1,9
第一趟进行了五次比较,排序结果:3, 6, 2,8, 1,9
========================================================================
第二趟排序:
第一次排序:3和6比较,3小于6,不交换位置:3, 6, 2,8, 1,9
第二次排序:6和2比较,6大于2,交换位置:3, 2, 6,8, 1,9
第三次排序:6和8比较,6小于8,不交换位置:3, 2, 6,8, 1,9
第四次排序:8和1比较,8大于1,交换位置:3, 2, 6,1, 8,9
第二趟总共进行了四次比较,排序结果:3, 2, 6,1, 8,9
========================================================================
。。。。。。
。。。。。。
。。。。。。
最终结果:1,2,3,6,8,9
========================================================================
由此可见,N 个数字要完成排序,总共要进行 N - 1 趟排序,每 i 趟的排序次数为(N - i)次,所以可以使用双重循环语句,外层控制循环多少趟,内层控制每一趟循环的次数。
/*
时间复杂度分析:
1、这是没有经过优化的最初级冒泡排序程序
2、由程序可以看出,比较次数是不受原始数据的是否有序影响的,循环的次数是固定的
3、其外层循环 N-1 次,内层最多走 N-1 次,最少走 1 次,一共比较 (N-1)+(N-2)+...+2+1,共 (N-1)N/2 次,时间复杂度为 O(n^2)
*/
public static void bubbleSort(int[] arr){
//冒泡排序算法
for (int i = 0; i < arr.length-1; i++) { //外层循环控制趟数
for (int j = 0; j < arr.length-1-i; j++) { //内层循环控制每一趟排序的次数
if(arr[j] > arr[j+1]){
//交换arr[j] 和 arr[j+1]
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
冒泡排序的优点
每进行一趟排序,就会少比较一次,因为每进行一趟排序就会找出一个较大的值,这样在一定程度上减少了算法的量。
用时间复杂度来说:
对于没有改进的冒泡初级程序,比较次数是不受原始数据的是否有序影响的,循环的次数是固定的,即 (N-1)N/2 ,时间复杂度为 O(n^2) 。
改进冒泡排序算法的性能
改进冒泡排序算法的思想是:假设第 n 遍数据已经排好序不需要改变了,扫描第 n+1 次的时候只是比较而没有交换,这是就可以跳出循环了,因为这是说明已经排好序了。因此可以设置一个标识变量 flag ,在每一遍扫描之前将 flag 设为 false,如果有数据交换则设为 true,扫描完一遍之后看 flag 的值,如果 flag 为 false 说明这遍已经没有数据交换了,数据已经是有序的了,就跳出循环。
public static void bubbleSort2(int[] arr){
for (int i = 0; i < arr.length-1; i++) { //外层循环控制趟数
//flag 标志用于指示这一趟的循环是否发生了交换
boolean flag = false;
for (int j = 0; j < arr.length-1-i; j++) { //内层循环控制每一趟排序的次数
if(arr[j] > arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
//flag 为 true 说明发生了交换操作
flag = true;
}
}
if(flag == false)
break;
}
}
改进后的冒泡排序的时间复杂度分析
因为加了标志位 flag 来判断在一趟循环之后是否发生了交换,如果在一趟排序之后没有发生交换则说明数组已经是排好序的了,则不需要再进行循环了,退出循环即可。所以,对于一个有序数组,使用冒泡排序的时间复杂度为 O(n)。在最坏的情况下,即数组是逆序的,就像没有改进过的冒泡排序一样了,需要执行 (N-1)N/2 次的比较,这时的时间复杂度为 O(n^2)。
综合来看,改进过的冒泡排序的时间复杂度为 O(n^2)。