问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

冒泡排序、选择排序、插入排序详解:时间复杂度、空间复杂度、稳定性全面分析

创作时间:
作者:
@小白创作中心

冒泡排序、选择排序、插入排序详解:时间复杂度、空间复杂度、稳定性全面分析

引用
CSDN
1.
https://blog.csdn.net/u013546788/article/details/105671228

排序算法是计算机科学中的基础算法之一,掌握其原理和性能分析对于理解算法设计和优化至关重要。本文详细分析了三种基本排序算法:冒泡排序、选择排序和插入排序,从最好/最坏/平均时间复杂度、稳定性、内存消耗等多个维度进行了深入探讨。

如何分析排序算法

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

  • 算法的时间复杂度,会随着排序集合的有序性而改变。我们需要分析不同算法在不同数据下的表现
  • 最好时间复杂度:在完全有序的情况下的时间复杂度(满有序度)
  • 最坏时间复杂度:在最坏情况下的时间复杂度(有序度为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 次遍历
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号