vlambda博客
学习文章列表

读书笔记——冒泡排序

什么是冒泡排序?

最近开始重学算法。先从最简的冒泡排序开始。这里做个笔记方便后面复习。

对于冒泡排序算法的定义,相对于其他资料,百度百科里的介绍相对详细一点——冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。

它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小、首字母从Z到A)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。

结论——冒泡排序它通过交换数据元素的位置进行排序,是一种典型的交换排序算法。

算法步骤

  1. 比较相邻两个元素的大小,如果第一个比第二个大,交换他们的位置。

  2. 向下顺移,对每一对元素做第1步的操作,直到末尾。至此最大的数会被挪动到末尾。

  3. 回到开始位置,重复步骤1、步骤2。(注意排除末尾的那个数,因为它的位置已经确定,是“有序”的)

  4. 持续对越来越少的元素重复步骤1、步骤2、步骤3,直到没有数字需要比较

尝试实现

假设我们现在有一个数组,这里以Java代码为例:

int[] array = {14, 12, 1, 15, 13};

//测试代码为
public static void main(String[] args) {
    int[] array = {14, 12, 1, 15, 13};
    bubbleSort(array);
    System.out.println(Arrays.toString(array));
}
  • 实现步骤1

    public static void bubbleSort(int[] array) {
        if(array[0] > array[1]){
             int temp = array[0];
             array[0] = array[1];
             array[1] = temp;
        }
    }
    //运行结果为:
    [12, 14, 1, 15, 13]
    //这里发现比较后元素12和14发生交换
  • 实现步骤2

    步骤2是因为要往后开始比较,所以考虑用for循环。初步实现如下

    public static void bubbleSort(int[] array) {
       for (int i = 0; i < array.length; i++) {
           //步骤1,比较和交换位置
           if (array[i] > array[i + 1]) {
               int temp = array[i];
               array[i] = array[i + 1];
               array[i + 1] = temp;
          }
      }
    }

    这里有个问题,开始下标i是从0开始,和它相邻的元素下标为i+1。当i遍历到末尾数组的最后一个元素时再用array[i+1]肯定会出现数据越界问题java.lang.ArrayIndexOutOfBoundsException。所以修改后步骤2的实现如下:

    public static void bubbleSort(int[] array) {
       for (int i = 0; i < array.length - 1; i++) {
           //步骤1,比较和交换位置
           if (array[i] > array[i + 1]) {
               int temp = array[i];
               array[i] = array[i + 1];
               array[i + 1] = temp;
          }
      }
    }
    //运行结果为:
    [12, 1, 14, 13, 15]
    //这时最大元素15已经挪动到了末尾
  • 步骤3

    重复一次,相当于遍历数组第二趟

    public static void bubbleSort(int[] array) {
       //第一趟
       for (int i = 0; i < array.length - 1; i++) {
           //步骤1,比较和交换位置
           if (array[i] > array[i + 1]) {
               int temp = array[i];
               array[i] = array[i + 1];
               array[i + 1] = temp;
          }
      }
       //第一次重复,也就是第二趟遍历。应为第一趟末尾数字已经遍历ok变得有序
       //所以这一次i的最大值要减1
       for (int i = 0; i < array.length - 1 - 1; i++) {
           //步骤1,比较和交换位置
           if (array[i] > array[i + 1]) {
               int temp = array[i];
               array[i] = array[i + 1];
               array[i + 1] = temp;
          }
      }
       //第2次重复,也就是第3趟遍历。应为第2趟末尾2个数字已经遍历ok变得有序
       //所以此次i的最大值要在上一次的基础上再减1
       for (int i = 0; i < array.length - 1 - 2; i++) {
           //步骤1,比较和交换位置
           if (array[i] > array[i + 1]) {
               int temp = array[i];
               array[i] = array[i + 1];
               array[i + 1] = temp;
          }
      }
    }
  • 步骤4

    这里我们发现测试数组总共有5个item,也就是说遍历5趟就能完成数组的冒泡排序动作。

    但是因为到最后一趟时只剩一个数,也就不需要进行遍历排序了。

    所以最终4趟遍历就能完成此次冒泡排序

    public static void bubbleSort(int[] array) {
           //第一趟
           for (int i = 0; i < array.length - 1; i++) {
               //步骤1,比较和交换位置
               if (array[i] > array[i + 1]) {
                   int temp = array[i];
                   array[i] = array[i + 1];
                   array[i + 1] = temp;
              }
          }
           //第一次重复,也就是第二趟遍历。应为第一趟末尾数字已经遍历ok变得有序
           //所以这一次i的最大值要减1
           for (int i = 0; i < array.length - 1 - 1; i++) {
               //步骤1,比较和交换位置
               if (array[i] > array[i + 1]) {
                   int temp = array[i];
                   array[i] = array[i + 1];
                   array[i + 1] = temp;
              }
          }
           //第2次重复,也就是第3趟遍历。应为第2趟末尾2个数字已经遍历ok变得有序
           //所以此次i的最大值要在上一次的基础上再减1
           for (int i = 0; i < array.length - 1 - 2; i++) {
               //步骤1,比较和交换位置
               if (array[i] > array[i + 1]) {
                   int temp = array[i];
                   array[i] = array[i + 1];
                   array[i + 1] = temp;
              }
          }
           //第四趟
           for (int i = 0; i < array.length - 1 - 3; i++) {
               //步骤1,比较和交换位置
               if (array[i] > array[i + 1]) {
                   int temp = array[i];
                   array[i] = array[i + 1];
                   array[i + 1] = temp;
              }
          }
      }
    //运行结果如下:
    [1, 12, 13, 14, 15]
    //符合预期

因为每次需要排序的数组长度是不固定,我们不可能每次都先提前算好需要排序数据的长度N,然后在代码中重复写N-1趟遍历代码。所以上面的代码可以合并优化为如下代码:

public static void bubbleSort(int[] array) {
   //本行for循环示需要遍历的趟数
   for (int i = 0; i < array.length -1 ; i++) {
       //这里的第二个for循环表示的是每次需要遍历多个数,第i趟需要遍历length-i个
       //或者说换种说法,表示的是每趟比较的次数,第i趟比较len-i次
       for (int j = 0; j < array.length - 1 - i; j++) {
           //步骤1,比较和交换位置;升序为左大于右,降序的话反着来即可
           if (array[j] > array[j + 1]) {
               int temp = array[j];
               array[j] = array[j + 1];
               array[j + 1] = temp;
          }
      }
  }
}

算法分析

  • 时间复杂度

    1. 最好情况

      最好情况就是数组已经是一个排好序的数组,一趟遍历即可完成。算法复杂度为O(n)

    2. 最坏情况

      最坏情况就是队列逆序排列,需要N-1趟进行排序。并且呢每次要进行N-i次的比较才能完成。

      i的取值为1<=i<=N-1。时间复杂度O(n^2)

    3. 平均情况

      数组乱序排列时,数组的平均复杂度为O(n^2)

  • 空间复杂度

    因为交换的时候需要一个额外的空间来辅助完成元素交换动作。所以空间复杂度为O(1)

  • 算法的稳定性

    冒泡排序算法两个相邻元素比较时如果相等是不会发生交换动作的。如果两个相等的元素没有相邻,那么即使通过前面的交换,让两个元素相邻起来也不会交换。因此相同元素的前后顺序并没有改变。结论——冒泡排序是一种稳定排序算法

    注:

    算法稳定性指的是在一组待排序记录中,如果存在任意两个相等的记录R和S,且在待排序记录中R在S前,如果在排序后R依然在S前,即它们的前后位置在排序前后不发生改变,则称为排序算法为稳定的。