详细分析冒泡排序、选择排序、插入排序(最好/最坏/平均时间复杂度(有序度)、稳定性、内存消耗(原地排序))

如何分析排序算法

最好、最坏、平均时间复杂度

  • 算法的时间复杂度,会随着排序集合的有序性而改变。我们需要分析不同算法在不同数据下的表现
    • 最好时间复杂度:在完全有序的情况下的时间复杂度(满有序度)
    • 最坏时间复杂度:在最坏情况下的时间复杂度(有序度为0)

内存消耗

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

算法的稳定性

  • 如果待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变。

有序度、逆序度

  • 有序度是数组中具有有序关系的元素对的个数
  • 逆序度 = 满有序度 - 有序度
  • 排序的过程就是一种增加有序度,减少逆序度的过程,最后达到满有序度,就说明排序完成了。

实例

  • 对于一个倒序排列的数组,比如 6,5,4,3,2,1,有序度是 0
  • 对于一个完全有序的数组,比如 1,2,3,4,5,6,有序度就是 n*(n-1)/2,也就是 15
  • 我们把完全有序的数组的有序度叫作满有序度

分析

冒泡排序的原理和性能分析

原理

  • 冒泡排序只会操作相邻的两个数据。
  • 冒泡排序会将相邻的两个数据两两比较,如果不符合要求则进行位置交换
  • 优化:当某次遍历没有数据交换的时候,则停止排序

 

代码

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

性能分析

  • 是否是原地排序算法
    • 只进行了比较和交换操作,没有借助额外的空间。
  • 是否是稳定的
    • 当有相邻的两个元素大小相等的时候,不会交换顺序
  • 时间复杂度分析
    • 最好:O(n) 完全有序。不需要重排序,只需要进行一次遍历
    • 最坏:O(n²) 倒序。需要进行遍历 O(n);每次遍历都需要冒泡,共需要 N 次
    • 平均:O(n²)
  • 有序度
    • 冒泡排序包含两个操作原子,比较和交换。
    • 每交换一次,有序度就加 1。不管算法怎么改进,交换次数总是确定的,即为逆序度。
    • 平均情况下,需要 n*(n-1)/4 次交换操,比较操作肯定要比交换操作多,而复杂度的上限是 O(n2),所以平均情况下的时间复杂度就是 O(n2)
      • 最坏情况,有序度为0。最好情况,有序度为 (n-1)n/2

 

插入排序的原理和性能分析

原理

  • 将数据集分为了 已排序 和 待排序。每次的排序过程就是从待排序中取出一个,插入到已排序中的相应位置

代码

public int[] insertSort(int[] items) {
    if (items.length < 1) return items;

    for (int i = 1; i < items.length; i++) {
        int cur = items[i];
        //1. 从未排序的队列,拿出一个
        int j = i - 1;
        //2. 在已排序的队列,找到合适的位置
        for (; j > 0; j--) {
            if (items[j] > cur) {
                items[j + 1] = items[j];
            } else {
                //找到位置
                break;
            }
        }
        //3. 插入到合适的位置
        items[j] = cur;
    }
    return items;
}

性能分析

  • 是否为原地排序
    • 是。只是数据的比较和移动
  • 是否稳定
    • 是。对于值相同的元素,按照先来后到的顺序排列
  • 时间复杂度
    • 最好:O(n)。完全有序
    • 最坏:O(n²)。每次都插入到已排序序列的队头
    • 平均:O(n²)。每次插入操作都相当于在数组中插入一个数据,循环执行 n 次插入操作,所以平均时间复杂度为 O(n2)。

选择排序的原理和性能分析

原理

  • 也是将数据及分为 已排序 和 待排序 两个区间。
  • 排序过程:选取待排序区间中最小/最大的数据,放到已排序区间的队尾(与待排序区间队头交换位置)

代码

public int[] selectSort(int[] items) {
    if (items.length < 1) return items;

    for (int i = 0; i < items.length; i++) {
        int minIndex = i;
        for (int j = i; j < items.length - 1; j++) {
            if (items[j + 1] < items[j]) {
                minIndex = j + 1;
            }
        }
        //swap
        if (minIndex != i) {
            int tmp = items[i];
            items[i] = items[minIndex];
            items[minIndex] = tmp;
        }
    }

    return items;
}

性能分析

  • 是否为原地排序
  • 是否稳定
    • 否,涉及到交换位置。每次都要找剩余未排序元素中的最小值,并和前面的元素交换位置。
  • 时间复杂度   
    • 最好/最坏/平均:O(n²)。即使完全有序,也需要经过 ( n + 1 ) n / 2 次遍历
  • 11
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值