排序算法时间复杂度为O(n²)的排序算法
Posted leduoi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了排序算法时间复杂度为O(n²)的排序算法相关的知识,希望对你有一定的参考价值。
排序算法(一)
排序算法 | 时间复杂度 | 是否基于比较 |
---|---|---|
冒泡、插入、选择 | O(n2) | √ |
快排、归并 | O(nlogn) | √ |
桶、计数、基数 | O(n) | × |
如何分析一个排序算法
一、执行效率
- 最好情况、最坏情况、平均情况时间复杂度
- 时间复杂度的系统,常数、低阶
- 比较次数和交换(移动)次数
二、内存消耗
内存消耗可以通过空间复杂度衡量。原地排序:特指空间复杂度为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。 啥意思?