11.讲排序(上):为什么插入排序比冒泡排序更受欢迎

文章目录

  • 1. 如何分析⼀个“排序算法”?
    • 1.1 排序算法的执⾏效率
    • 1.2 排序算法的内存消耗
    • 1.3 排序算法的稳定性
  • 2.冒泡排序(Bubble Sort)
  • 3.插⼊排序(Insertion Sort)
  • 4.选择排序(Selection Sort)
  • 5. 解答为什么插入排序比冒泡排序更受欢迎
  • 6. 总结

最经典的、最常用的:冒泡排序、插入排序、选择排序、归并排序、快速排序、计数排序、基数排序、桶排序。
11.讲排序(上):为什么插入排序比冒泡排序更受欢迎_第1张图片
思考题:插入排序和冒泡排序的时间复杂度相同,都是O(n2),在实际的软件开发里,为什么我们更倾向于使用插入排序算法而不是冒泡排序算法呢?

1. 如何分析⼀个“排序算法”?

1.1 排序算法的执⾏效率

从以下几方面考虑:

  • 最好情况、最坏情况、平均情况时间复杂度。考虑算法在不同数据下的性能表现;
  • 时间复杂度的系数、常数 、低阶。时间复杂度是反应n规模很大时候的趋势,忽略系数、常数 、低阶,但是实际软件开发,排序数据是样本量有限的数据,必须考虑系数、常数 、低阶;
  • ⽐较次数和交换(或移动)次数。

1.2 排序算法的内存消耗

原地排序(Sorted in place):就是特指空间复杂度是O(1)的排序算法。

1.3 排序算法的稳定性

稳定排序算法:数据中相同的值经过排序后,相对位置不变。

应用价值:订单有两个属性,⼀个是下单时间,另⼀个是订单⾦额。如果我们现在
有10万条订单数据,我们希望按照⾦额从⼩到⼤对订单数据排序。对于⾦额相同的订单,我们希望按照下单时间从早到晚有序。

解决方案:应用稳定的排序算法,先根据下单时间排序,然后根据订单金额排序,因为是稳定算法,所以对于金额相同的订单,下单时间还是有序的。

2.冒泡排序(Bubble Sort)

// 插入排序,a表示数组,n表示数组大小
public void insertionSort(int[] a, int n) {
  if (n <= 1) return;

  for (int i = 1; i < n; ++i) {
    int value = a[i];
    int j = i - 1;
    // 查找插入的位置
    for (; j >= 0; --j) {
      if (a[j] > value) {
        a[j+1] = a[j];  // 数据移动
      } else {
        break;
      }
    }
    a[j+1] = value; // 插入数据
  }
}

冒泡是原地排序,稳定排序。

最好情况时间复杂度是O(n)。以最坏情况时间复杂度为O(n )。

平均复杂度怎么分析?

  • 有序度是数组中具有有序关系的元素对的个数。
    11.讲排序(上):为什么插入排序比冒泡排序更受欢迎_第2张图片
    对于⼀个完全有序的数组,⽐如1,2,3,4,5,6,有序度就是n*(n-1)/2,也就是15。我们把这种完全有序的数组的有序度叫作满有序度

  • 逆序度,与有序度相反

  • 逆序度=满有序度-有序度

冒泡排序包含两个操作原⼦,⽐较和交换。每交换⼀次,有序度就加1。不管算法怎么改进,交换次数总是确定的,即为逆序度,也就是n*(n-1)/2–初始有序度。此例中就是15–3=12,要进⾏12次交换操作。

对于包含n个数据的数组进⾏冒泡排序,平均交换次数是多少呢?最坏情况下,初始状态的有序度是0,所以要进⾏n*(n-1)/2次交换。最好情况下,初始状态的有序度是n*(n-1)/2,就不需要进⾏交换。我们可以取个中间值n*(n-1)/4,来表示初始有序度既不是很⾼也不是很低的平均情况。

换句话说,平均情况下,需要n*(n-1)/4次交换操作,比较操作肯定要比交换操作多,而复杂度的上限是O(n2),所以平均情况下的时间复杂度就是O(n2)。

3.插⼊排序(Insertion Sort)

public static void insertionSort2(int[] arr) {
    if (arr == null || arr.length < 2) {
      return;
    }
    for (int i = 1; i < arr.length; i++) {
      int temp = arr[i];
      int j = i - 1;
      for (; j >= 0; j--) {
        if (arr[j] > temp) {
          // 数据移动
          arr[j + 1] = arr[j];
        } else {
          break;
        }
      }
      // 数据交换
      arr[j + 1] = temp;
    }
  }

插入排序是原地,稳定的排序算法。

最好的时间复杂度为O(n),最坏情况时间复杂度为O(n2)。

4.选择排序(Selection Sort)

选择排序是原地,非稳定的排序算法。
最好最坏时间复杂度为O(n^2)。

5. 解答为什么插入排序比冒泡排序更受欢迎

两个都是原地,稳定的排序,时间复杂度都是O(n^2), 移动次数都是原始数据的逆序度。
为什么插入排序更好呢?

冒泡排序中数据的交换操作:
if (a[j] > a[j+1]) { // 交换
   int tmp = a[j];
   a[j] = a[j+1];
   a[j+1] = tmp;
   flag = true;
}

插入排序中数据的移动操作:
if (a[j] > value) {
  a[j+1] = a[j];  // 数据移动
} else {
  break;
}

逆序度是K的数组,冒泡是3k单位时间,插入是1k单位时间,显然插入更好。

6. 总结

11.讲排序(上):为什么插入排序比冒泡排序更受欢迎_第3张图片

你可能感兴趣的:(#,数据结构和算法,-,极客时间,王争,排序算法,算法,java)