引言
- 排序算法是重要的基础算法;
- 排序算法的效率至关重要;
- 任何算法的空间效率和时间效率不可兼得,所以没有最好的算法,只有最合适的算法;
- 本文总结了几种排序算法,并分析了他们的适用场景,使用的语言为java和js;
选择排序(selection sort)
- 思想:对于一个数组a,找出数组中的最小元素并和a[0]交换,然后忽略a[0],再次找出数组中的最小元素并与a[1]交换,以此类推;
- java实现:
1 | package algorithm; |
- js实现:
1 | var nums = [1, 54, 6, 3, 78, 34, 12, 45]; |
插入排序(Insertion sort)
- 思想:从数组的第二个元素开始,将数组中的每一个元素按照规则插入到已排好序的数组中以达到排序的目的;
- java排序:
1 | package algorithm; |
- js实现:
1 | var nums = [3,4,1,7,2,9,10,0]; |
希尔排序(shell sort)
- 希尔排序是改进后的插入排序;
- 思想:由于数组越接近有序,插入排序时间复杂度越低,所以希尔排序不是直接对数组进行插入排序,而是先对数组分成若干组,对每一组分别进行插入排序,使得数组变得”更加有序”之后再扩大每组的元素个数,再对每组进行插入排序,以此类推,直至元素全部有序,这是一种为了降低时间复杂度的”渐进式”的插入排序;
- java实现:
1 | package algorithm; |
- js实现:
1 | var nums = [3,1,4,2,7,2,3,10]; |
归并排序(Merge Sort)
- 归并排序是分治法在排序算法中的的典型实现;
- 归并排序是将一个数组分为两半A、B(或更多部分),对这两半分别排序后,再将他们归并为一个有序数组;
- 合并的方法:将A数组中的一个元素与B数组中的一个元素相比较,并将较小的元素复制到第三个新数组中,当其中一个数组到达末尾时,将另一个数组中剩余元素复制到第三个数组中即可;
- java实现:
1 | package algorithm; |
- js实现:
1 | var nums = [1, 54, 6, 3, 78, 34, 12, 45]; |
冒泡排序(Bubble sort)
- 从第一个元素开始,比较相邻的元素,如果第一个比第二个大,就交换他们两个;
- 依次往后,对每一对相邻元素作同样的工作,直到序列最后,最后的元素应该会是最大的数;
- 从第一个元素开始,重复上面两步,直到倒数第二个元素就停止(最后一个元素肯定是序列中最大的,不用再次比较);
- 重复上面的过程,直到序列末尾;
- java实现:
1 | package algorithm; |
- js实现:
1 | var nums = [1,5,3,6,2,7,9]; |
快速排序(Quick Sort)
- 思想:选择一个基准元素,通常选择第一个元素或者最后一个元素,通过一趟扫描,将待排序列分成两部分,一部分比基准元素小,一部分大于等于基准元素,此时基准元素在其排好序后的正确位置,然后再用同样的方法递归地排序划分的两部分;
- java实现:
1 | package algorithm; |
- js实现:
1 | var nums = [1, 54, 6, 3, 78, 34, 12, 45]; |
堆排序(Heap Sort)
- 堆的基本概念:
- 堆是节点含有可比较对象,并以如下方式组织的完全二叉树:
- 每个节点含有的对象不小于(最大堆)/不大于(最小堆)其后代中的对象;
- 最大(小)堆的根含有堆中最大(小)的对象;
- 最大(小)堆中任何节点的子树也是最大(小)堆;
- 堆排序原理:
- 这里讲解的很清楚;
- java实现:
1 | package algorithm; |
- js实现:
1 | var nums = [8,3,5,0,2,7,9,10]; |
基数排序(radix sort)
- 基数排序和其他排序方法的思路不同,不是以比较对象大小为基础;
- 基础排序先将待排序对象的长度补全成相同的长度,然后从最后一位开始:将所有元素按照最后一位的大小排序,排序后元素的最后一位是有序的;再将所有元素按照倒数第二位的大小排序,排序后元素的倒数第二位是有序的,在倒数第二位相同的元素中,它们的最后一位是有序的;再将所有元素按照倒数第三位的大小排序,排序后元素的倒数第三位是有序的,在倒数第三位相同的元素中,它们的倒数第二位是有序的,在倒数第二位相同的元素中,它们的最后一位是有序的;
- 以此类推,直到元素的第一位排序完成;
- 此时元素已经是有序序列;
- java实现:
1 | package test8; |
各种排序算法对比
排序算法 | 最好时间复杂度 | 平均时间复杂度 | 最坏时间复杂度 | 稳定性 | 空间复杂度 |
---|---|---|---|---|---|
冒泡排序 | O(n) | O(n^2) | O(n^2) | 稳定 | O(1) |
选择排序 | O(n^2) | O(n^2) | O(n^2) | 不稳定 | O(1) |
插入排序 | O(n) | O(n^2) | O(n^2) | 稳定 | O(1) |
希尔排序 | O(n) | O(n^1.5) | O(n^2) | 不稳定 | O(1) |
归并排序 | O(n*logn) |
O(n*logn) |
O(n*logn) |
稳定 | O(1) |
快速排序 | O(n*logn) |
O(n*logn) |
O(n^2) | 不稳定 | O(logn)~O(n) |
堆排序 | O(n*logn) |
O(n*logn) |
O(n*logn) |
不稳定 | O(1) |
基数排序 | 约为O(n) | 约为O(n) | 约为O(n) | 稳定 | O(rd+n) |
- 各种排序方法的适用条件
- 当待排序总数较小,用插入排序或选择排序,虽然他们的时间复杂度高,但是在排序总数较小时时间并不是主要矛盾;
- 当待排序对象基本有序时,用插入排序、冒泡排序、快速排序,因为他们在序列接近有序的情况下的时间复杂度比序列完全无序时低;
- 若待排序对象非常多,选择快速排序(首选)、堆排序、归并排序,因为他们的时间复杂度为O(nlgn),即用空间换取时间,以便解决“时间太长”这个主要矛盾;
- 若要求排序算法稳定,则选用归并排序;