排序(一)

排序算法(一)

排序算法时间复杂度是否基于比较
冒泡、插入、选择O(n²)
快排、归并O(nlogn)
桶、计数、基数O(n)×

如何分析一个排序算法

一、执行效率

  1. 最好情况、最坏情况、平均情况时间复杂度
  2. 时间复杂度的系统,常数、低阶
  3. 比较次数和交换(移动)次数

二、内存消耗

内存消耗可以通过空间复杂度衡量。原地排序:特指空间复杂度为O(1)的排序算法,不需要使用额外空间。

三、稳定性

稳定性:排序之前存在有两个值相等的元素,排序之后,相等元素之间原有的相对位置不变

例子:订单中有两个属性,金额和时间。现在希望想要按照金额由小到大进行排序,金额相同的再按照时间从早到晚排序。

解决办法:利用排序算法稳定性解决比较方便。首先按照时间从早到晚进行排序,排完序之后再使用稳定排序算法对金额进行排序,因为稳定性,对于相同金额的订单,排序前后的相对位置是不会发生改变,所以订单就按照预期排好了。

排序算法

排序算法是否为原地排序算法是否稳定最好时间复杂度平均时间复杂度最坏时间复杂度
冒泡排序是,空间复杂度为O(1)稳定O(n)O(n²)O(n²)
插入排序是,空间复杂度为O(1)稳定O(n)O(n²)O(n²)
选择排序是,空间复杂度为O(1)不稳定O(n²)O(n²)O(n²)

一、冒泡排序

冒泡排序只会比较相邻两个元素进行比较,看是否满足大小关系,不满足则互换位置,每次冒泡会让至少一个元素移动到它应该在的位置,重复n次。

移动元素次数等于逆序度

优化:当某次冒泡没有数据交换时,就说明已经排好序了,不需要再继续执行后续冒泡操作了。

// 冒泡排序,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;  // 没有数据交换,提前退出
  }
}

二、插入排序

思想:动态向有序集合中插入数据,并一直保持集合有序

插入排序描述:将数组分为两部分,分别是:已排序区间和未排序区间。初始已排序区间只有一个元素,即数组的第一个元素,之后不断从未排序区间中取出元素,并插入到已排序区间的合适位置,保证已排序区间一直有序。重复这个过程直到未排序区间中元素为空。

移动元素次数等于逆序度

// 插入排序,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; // 插入数据
  }
}

三、选择排序

选择排序思路:数组也分为已排序区间和未排序区间,每次在未排序区间中从第一位元素开始找,找到最小元素,将其与未排序区间第一位元素交换位置,就变为了已排序区间的末尾。

// 选择排序,a表示数组,n表示数组大小
public static void selectionSort(int[] a, int n) {
    if (n <= 1) return;

    for (int i = 0; i < n - 1; ++i) {
        // 查找最小值
        int minIndex = i;
        for (int j = i + 1; j < n; ++j) {
            if (a[j] < a[minIndex]) {
                minIndex = j;
            }
        }

        // 交换
        int tmp = a[i];
        a[i] = a[minIndex];
        a[minIndex] = tmp;
    }
}

思考

冒泡排序和插入排序的时间空间复杂度,稳定性都相同,但为什么插入排序更受欢迎?

冒泡排序中需要交换的元素个数,和选择排序需要移动元素个数是固定相同的,即都为原始数据的逆序度

但是冒泡排序数据交换要比插入排序数据移动复杂,冒泡排序需要3个赋值操作,而插入排序只需要1个


//冒泡排序中数据的交换操作:
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的数组进行排序:

冒泡排序:需要K次交换操作,每次3个赋值语句,所以总耗时时间为3*K;

插入排序:需要K次移动操作,每次1个赋值语句,所以总耗时时间我K;

所以,虽然时间复杂度都为O(n²),但插入排序的性能更加极致优化。如果需要对插入排序进一步优化,可以了解下希尔排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值