冒泡排序
每一轮循环都会有一个元素被排好序,元素之间进行两两比较。如果大于(按正向前提下),则交换。
冒泡排序是一种很基本的排序算法。基本步骤如下
-
指向数组中的两个相邻的元素(最开始是数组的头两个元素),比较他们的大小。
- 【2】【1】【3】【5】 – 将 2 和 1 进行比较
-
如果顺序错了(即左边的值大于右边),就互换位置。
- 【1】【2】【3】【5】 – 如果位置是正确的,则什么都不做。
-
将两个指针向右移动一位
- 【1】【2】【3】【5】 – 比较 2 和 3 的大小
-
重复上面的三步,直到数组变有序为止。(上面三步也叫一个轮回)
实战
假设对【4】【2】【7】【1】【3】进行排序。目标是产生一个包含相同元素、升序的数组。
第一轮循环之后:
【2】【4】【1】【3】【[7]】
可以看到,最大的一个数已经冒到了最后一位上。
第二轮循环之后:
【2】【1】【3】【[4]】【[7]】
可以看到,第二大的数已经冒到了倒数第二个位置上。
第三轮循环之后:
【1】【2】【[3]】【[4]】【[7]】
可以看到 3 已经到了最后位置上。(实际上此时的数组已经是有序的了,但是计算机不知道,它仍然会进行下一轮循环)
第四轮循环之后:
【[1]】【[2]】【[3]】【[4]】【[7]】
没有任何交换,此时排序结束。
注意:每轮排序完毕后,已经排好序的元素不需要再参与下一次轮回。因为它已经在正确的位置上了。
Java 代码实现
public static int[] bubbleSort(int[] nums) {
if(nums == null || nums.length == 1) {
return nums;
}
//用来标记是否已经排序完成
boolean finish;
while (true) {
//每次循环将该值改成 true
finish = true;
for(int i = 0; i < nums.length-1; i++) {
int temp;
if(nums[i] > nums[i+1]) { //如果已经是有序的了,则不会进入该该判断
finish = false;
temp = nums[i];
nums[i] = nums[i+1];
nums[i+1] = temp;
}
}
System.out.println("排序结果: " + Arrays.toString(nums));
if(finish){
break;
}
}
return nums;
}
这里是简易版本的实现,(这里存在的问题就是每次循环都会将最后一位数字进行比较,实际上不需要,可以使用一个标记位来标记,避免已经确定是正确位置的元素再参与下一论循环)
改进一下:
public static int[] bubbleSortEnhance(int[] nums){
if(nums == null || nums.length == 1){
return nums;
}
//用来标记是否已经排序完成
boolean finish;
//指针,初始为数组最后的索引位
int pointIndex = nums.length-1;
while (true){
//每次循环将该值改成 true
finish = true;
System.out.println("本轮该排序的索引位为 [0, " + pointIndex + "]");
for(int i = 0; i < pointIndex; i++){
int temp;
if(nums[i] > nums[i+1]) { //如果已经是有序的了,则不会进入该该判断
finish = false;
temp = nums[i];
nums[i] = nums[i+1];
nums[i+1] = temp;
}
}
//执行一次,就会有一个元素排好序,然后将 pointIndex--,避免已经排好序的元素进入下一轮的循环
pointIndex--;
System.out.println("排序结果: " + Arrays.toString(nums));
if(finish){
break;
}
}
return nums;
}
使用 pointIndex
这个值来限制最后参与排序的元素
//输入
private static int[] nums = new int[]{2,3,9,7,5,6,12,10,43, 32};
//输出
本轮该排序的索引位为 [0, 9]
排序结果: [2, 3, 7, 5, 6, 9, 10, 12, 32, 43]
本轮该排序的索引位为 [0, 8]
排序结果: [2, 3, 5, 6, 7, 9, 10, 12, 32, 43]
本轮该排序的索引位为 [0, 7]
排序结果: [2, 3, 5, 6, 7, 9, 10, 12, 32, 43]
排序完毕之后的结果:[2, 3, 5, 6, 7, 9, 10, 12, 32, 43]
时间复杂度
试想一下,对于一个 5 个元素的数组来说,最差的情况下(完全倒序通过冒泡排序变成正序),
- 第一个元素冒到最后正确的位置上需要执行 4(5-1)次比较
- 第二个元素冒到最后正确的位置上需要执行 4(3-1)次比较
以此类推,5 个元素(最差的情况下)需要执行 4 + 3 + 2 + 1 = 10 次比较,同样需要执行 4 + 3 + 2 + 1 = 10 次交换。
那么 5 个元素(最差的情况下)需要执行 20 步才能完全排好序。
10 个元素(最差的情况下)需要执行 9 + 8 + 7 + 6 +5 + 4 + 3 + 2 + 1 = 45 次比较,另外还需要 45 次交换,那么就是 90 次才能完全排好序。
那么 n 个元素进行的比较次数就是 (n-1)+ (n-2) + (n-3) + … + 1,交换次数同样也为 (n-1)+ (n-2) + (n-3) + … + 1,两者相加就等于 n2-n
根据这个关系,我们可以类推出以下的结论
元素个数 | 比较 | 交换 | 总次数 |
---|---|---|---|
5 个元素 | 10 | 10 | 20 |
10 个元素 | 45 | 45 | 90 |
20 个元素 | 190 | 190 | 380 |
… | … | … | … |
n 个元素 | n ( n − 1 ) 2 {n(n-1)\over 2} 2n(n−1) | n ( n − 1 ) 2 {n(n-1)\over 2} 2n(n−1) | n 2 − n {n^2-n} n2−n |
当 n 足够大时,n2 增长的速度远超 n 增长的速度。所以冒泡排序的时间复杂度近似等于 O(N2),也被称之为二次时间。