24-经典排序算法之 冒泡排序

1. 冒泡排序(Bubble Sort)

冒泡排序只会操作相邻的两个数据。每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。(如果是从小到大排序,总会将最大的值送到序列的末尾。)

我用一个例子,带你看下冒泡排序的整个过程。我们要对一组数据 4,5,6,3,2,1,从小到大进行排序。第一次冒泡操作的详细过程就是这样:

在这里插入图片描述可以看出,经过一次冒泡操作之后,6 这个元素已经存储在正确的位置上。要想完成所有数据的排序,我们只要进行 6 次这样的冒泡操作就行了。
这里说得一次冒泡排序是一个总称:其实数据交换了三次, 第一次 6-3 第二次 6-2 第三次 6-1。
每一次冒泡,都将未处理数组最大值移到最末尾,作为已数组处理的一部分

实际上,刚讲的冒泡过程还可以优化。当某次冒泡操作已经没有数据交换时,说明已经达到完全有序不用再继续执行后续的冒泡操作。我这里还有另外一个例子,这里面给 6 个元素排序,只需要 4 次冒泡操作就可以了。
冒泡排序可能会提前结束。这是一个优化点。

在这里插入图片描述

2. 代码实现


// 冒泡排序,a表示数组,n表示数组大小
public void bubbleSort(int[] a, int n) 
{
  if (n <= 1) return;
 
  for (int i = 0; i < n; ++i) 
  {
    // 提前退出冒泡循环的标志位
    boolean flag = false;
    for (int j = 0; j < n - i - 1; ++j)
     {
        if (a[j] > a[j+1]) 
        { // 交换
          int tmp = a[j];
          a[j] = a[j+1];
          a[j+1] = tmp;
          flag = true;  // 表示有数据交换      
        }
    }
    if (!flag) break;  // 没有数据交换,提前退出
  }
}

3.算法分析

结合分析排序算法的三个方面,有三个问题要掌握。

第一,冒泡排序是原地排序算法吗?

冒泡的过程只涉及相邻数据的交换操作,只需要常量级的临时空间,所以它的空间复杂度为 O(1),是一个原地排序算法。

第二,冒泡排序是稳定的排序算法吗?

在冒泡排序中,只有交换才可以改变两个元素的前后顺序。为了保证冒泡排序算法的稳定性,当有相邻的两个元素大小相等的时候,我们不做交换,相同大小的数据在排序前后不会改变顺序,所以冒泡排序是稳定的排序算法。

第三,冒泡排序的时间复杂度是多少?

最好情况下,要排序的数据已经是有序的了,我们只需要进行一次冒泡操作,就可以结束了,所以最好情况时间复杂度是 O(n)。
而最坏的情况是,要排序的数据刚好是倒序排列的,我们需要进行 n 次冒泡操作,所以最坏情况时间复杂度为 O(n2)。

由上图可知,当有4 个石子的时候排完序需要 3 趟,第一趟需要比较3次,第二趟需要比较2次,第三趟需要比较1次,那一共比较了 3 + 2 + 1 次;

那如果有 n 个石子呢?

那就需要 (n-1) + (n-2) +…+2+1 次,这不就是一个等差数列吗,很显然:

在这里插入图片描述
根据复杂度的规则,去掉低阶项(也就是 n/2),并去掉常数系数,那复杂度就是 O(n2)了;

平均情况下的时间复杂度是 O(n2)。需要概率论的定量分析。
基于比较的排序算法中,逆序度 = 需要交换的次数,因此大致估算逆序度可以得到排序过程大致的平均时间复杂度。

4. 代码优化

4.1思路

还有一个问题。那如果数列中前半部分是无序的,后半部分是有序的呢?
比如(3,4,2,1,5,6,7,8)这个数组,其实后面的许多元素已经是有序的了,但是每一轮还是白白比较了许多次呢?例如:

第一轮:
在这里插入图片描述

元素 3 和元素 4 比较,4 大于 3,所以位置不变。

接着元素 4 和元素 2 比较,4 大于 2,所以 4 和 2 交换

在这里插入图片描述

接着元素 4 和元素 1 交换

在这里插入图片描述

再接着就发现后面其实就是有序数列了,但是还是要每一次两两相比,这样就白白比较了很多次了。

对于这个问题,关键在于对这数列有序区的界定。如果按冒泡排序代码原始版来分析的话,有序区的长度和排序的轮数是相等的。比如第一轮排序过后的有序区长度是1,第二轮排序过后的有序区长度是2 ……

但是呢,实际数列真正的有序区可能会大于这个长度,也就是上面这个例子,第二轮中后面 5 个实际上都已经属于有序区了。因此后面的比较是没有意义的了。

我们可以这样做来避免这种情况:在每一轮排序的最后,记录一下最后一次元素交换的位置,那个位置也就是无序数列的边界,再往后就是有序区了。

4.2 算法实现

public static int[] bubbleSort(int[] arr) 
{
     if (arr == null || arr.length < 2) 
     {
          return arr;
     }
    //记录最后一次交换的位置
    int lastExchangeIndex = 0;
    //无序数列的边界,每次比较只需要比到这里为止
    int sortBorder = arr.length - 1;
    
    for (int i = 0; i < arr.length - 1; i++)
     {
         boolean isSorted  = true;//有序标记,每一轮的初始是true
         for (int j = 0; j < sortBorder; j++) 
         {
             if (arr[j + 1] < arr[j]) 
             {
                 isSorted  = false;//有元素交换,所以不是有序,标记变为false
                 int t = arr[j];
                 arr[j] = arr[j+1];
                 arr[j+1] = t;
                 lastExchangeIndex = j;
             }
         }
        sortBorder = lastExchangeIndex
         //一趟下来是否发生位置交换,如果没有交换直接跳出大循环
         if(isSorted )
              break;
     }
     return arr;
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值