07_排序之冒泡、插入、选择

1.如何分析一个排序算法?

排序算法的执行效率

  • 最好情况、最坏情况、平均情况时间复杂度
  • 时间复杂度的系数、常数 、低阶
  • 比较次数和交换(或移动)次数

排序算法的内存消耗
也就是空间复杂度,特别的,空间复杂度为O(1)的排序算法也叫原地排序算法
排序算法的稳定性
如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。

2.冒泡排序

算法代码

public static void sort(int[] array, int n){
    for (int i = n - 1;i > 0;i--) {
        boolean isChange = false;
        for (int j = n - 1; j > n - i - 1; j--) {
            if (array[j] < array[j - 1]) {
                int tmp = array[j - 1];
                array[j - 1] = array[j];
                array[j] = tmp;
                isChange = true;
            }
        }
        if (!isChange) break;
    }
}

分析:
冒泡排序是原地排序算法,只用了常量级的空间
冒泡排序是稳定的排序算法
时间复杂度:最好O(n) 最坏O(n2) 平均怎么算?

有序度和逆序度
概念:
有序度

有序元素对:a[i] <= a[j], 如果i < j

逆序度

逆序元素对:a[i] > a[j], 如果i < j

满有序度为n*(n-1)/2
逆序度 = 满有序度 - 有序度
由于每交换一次,有序度就加1,交换次数就和逆序度相对应
最坏情况下,初始有序度为0,要进行n*(n-1)/2次交换;最坏情况下,初始有序度为n*(n-1)/2,不需要交换,我们取个中间值n*(n-1)/4
也就是说,平均情况下,需要n*(n-1)/4次交换操作,比较操作肯定更多,而复杂度上限为O(n2),故平均时间复杂度就是O(n2)

3.插入排序

// 插入排序,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(n2)
平均时间复杂度 数组中插入一个数据的平均时间复杂度为O(n),对插入排序来说,每次插入都相当于在数组中插入一个数据,执行n次操作,故平均时间复杂度为O(n2)

4.选择排序

public static void selectSort(int[] array, int n) {
    for (int i = 0; i < n - 1; i++) {
        int min = i; //min记录最小值的下标
        //从i+1开始,搜索最小值下标
        for (int j = i + 1; j < n; j++)
            if (array[j] < array[min]) min = j;
        //交换
        if (i != min){
            int tmp = array[i];
            array[i] = array[min];
            array[min] = tmp;
        }
    }
}

选择排序是原地排序算法
选择排序是不稳定算法
最好时间复杂度O(n) 最坏时间复杂度O(n2) 平均时间复杂度O(n2)

5.为什么插入比冒泡更受欢迎?

冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要 3 个赋值操作,而插入排序只需要 1 个
我们把执行一个赋值语句的时间粗略地计为单位时间(unit_time),然后分别用冒泡排序和插入排序对同一个逆序度是 K 的数组进行排序。用冒泡排序,需要 K 次交换操作,每次需要 3 个赋值语句,所以交换操作总耗时就是 3*K 单位时间。而插入排序中数据移动操作只需要 K 个单位时间。

冒泡排序中数据的交换操作:
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;
}

参考课程:极客时间王争老师的《数据结构与算法之美》

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页