排序算法时间复杂度为O(n²)的排序算法

Posted leduoi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排序算法时间复杂度为O(n²)的排序算法相关的知识,希望对你有一定的参考价值。

排序算法(一)

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

如何分析一个排序算法

一、执行效率

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

二、内存消耗

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

三、稳定性

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

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

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

排序算法

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

一、冒泡排序

冒泡排序只会比较相邻两个元素进行比较,看是否满足大小关系,不满足则互换位置,每次冒泡会让至少一个元素移动到它应该在的位置,重复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(n2),但插入排序的性能更加极致优化。如果需要对插入排序进一步优化,可以了解下希尔排序

以上是关于排序算法时间复杂度为O(n²)的排序算法的主要内容,如果未能解决你的问题,请参考以下文章

时间为O(nlg n)的排序算法 如快速排序 堆排序 nlg是啥意思。好象是lgn。 啥意思?

Java学习笔记——排序算法之O(n²)排序

算法:排序

指针实现时间复杂度为O(n*logN)的排序算法(归并排序算法)

排序—时间复杂度为O(n)的三种排序算法

算法面试与实战-05排序算法- 时间复杂度为O(n)的排序算法(桶排序)