排序算法
表格速览
排序算法 | 平均复杂度 | 最坏复杂度 | 最佳复杂度 | 空间复杂度 | 稳定性 | 复杂性 |
---|---|---|---|---|---|---|
插入排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | 稳定 | 简单 |
冒泡排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | 稳定 | 简单 |
快速排序 | O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n) | O ( n 2 ) O(n^2) O(n2) | O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n) | O ( l o g 2 n ) O(log_{2}n) O(log2n) | 不稳定 | 较复杂 |
选择排序 | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( n 2 ) O(n^2) O(n2) | O ( 1 ) O(1) O(1) | 不稳定 | 简单 |
堆排序 | O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n) | O ( 1 ) O(1) O(1) | 不稳定 | 较复杂 |
归并排序 | O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n) | O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n) | O ( n ) O(n) O(n) | 稳定 | 较复杂 |
具体实现
插入排序
第一步:把待排序的数组分成已排序和未排序两部分,初始的时候把第一个元素认为是已排好序的。
第二步:从第二个元素开始,在已排好序的子数组中寻找到该元素合适的位置并插入该位置。
重复上述过程直到最后一个元素被插入有序子数组中
代码优化
插入排序由于O( n2 )的复杂度,在数组较大的时候不适用。但是,在数据比较少的时候,是一个不错的选择,一般做为快速排序的扩充。例如,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序。又如,在JDK 7 java.util.Arrays所用的sort方法的实现中,当待排数组长度小于47时,会使用插入排序。
冒泡排序
第一步:比较相邻的元素。如果第一个比第二个大,就交换它们两个;
第二步:对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
第三步:针对所有的元素重复以上的步骤,除了最后一个;
重复步骤1~3,直到排序完成。
代码优化
在数据完全有序的时候展现出最优时间复杂度,为O(n)。其他情况下,几乎总是O( n2 )。因此,算法在数据基本有序的情况下,性能最好。要使算法在最佳情况下有O(n)复杂度,需要做一些改进,增加一个swap的标志,当前一轮没有进行交换时,说明数组已经有序,没有必要再进行下一轮的循环了,直接退出。
public static void bubbleSort(int[] arr) {
int temp = 0;
boolean swap;
for (int i = arr.length - 1; i > 0; i--) { // 每次需要排序的长度
swap=false;
for (int j = 0; j < i; j++) { // 从第一个元素到第i个元素
if (arr[j] > arr[j + 1]) {
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swap=true;
}
}//loop j
if (swap==false){
break;
}
}//loop i
}// method bubbleSort
快速排序
第一步:从数列中挑出一个元素,称为"基准"(pivot)
第二步:重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
第三步:递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序。
稳定性与适用场景
快速排序并不是稳定的。这是因为我们无法保证相等的数据按顺序被扫描到和按顺序存放。快速排序在大多数情况下都是适用的,尤其在数据量大的时候性能优越性更加明显。但是在必要的时候,需要考虑下优化以提高其在最坏情况下的性能。
选择排序
第一步:在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
第二步:从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复前两步,直到所有元素均排序完毕。
代码优化
用数组实现的选择排序是不稳定的,用链表实现的选择排序是稳定的。不过,一般提到排序算法时,大家往往会默认是数组实现,所以选择排序是不稳定的。但是由于固有的O(n2)复杂度,选择排序在海量数据面前显得力不从心。因此,它适用于简单数据排序。
堆排序
堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。
稳定性与适用场景
堆排序存在大量的筛选和移动过程,属于不稳定的排序算法。堆排序在建立堆和调整堆的过程中会产生比较大的开销,在元素少的时候并不适用。但是,在元素比较多的情况下,还是不错的一个选择。尤其是在解决诸如“前n大的数”一类问题时,几乎是首选算法。
归并排序
递归法
第一步:申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
第二步:设定两个指针,最初位置分别为两个已经排序序列的起始位置
第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针到达序列尾
第四步:将另一序列剩下的所有元素直接复制到合并序列尾
迭代法
第一步:将序列每相邻两个数字进行归并操作,形成n/2个序列,排序后每个序列包含一个或两个元素
第二步:若此时序列数不是1个则将上述序列再次归并,形成n/4个序列,每个序列包含四个或者三个元素
重复步骤2,直到所有元素排序完毕,即序列数为1