经典的排序算法
- 冒泡排序(O(n2)):排序区间按N、N - 1、N - 2、……、2的规律变化,有序区间按1、2、3、……、N的规律变化,每次通过冒泡法将当前排序区间中的最大值冒泡到有序区间的最前面。
- 选择排序(O(n2)):排序区间按N、N - 1、N - 2、……、2的规律变化,有序区间按1、2、3、……、N的规律变化,每次从排序区间中选择最大的值放置在有序区间的最前面。
- 插入排序(O(n2)):排序区间按N、N - 1、N - 2、……、2的规律变化,有序区间按1、2、3、……、N的规律变化,步长为1的希尔排序,每次排序区间的第一个元素和有序区间最后一个、倒数第二个、倒数第三个、……元素比较,把当前元素插入到有序区间的合适位置,使有序区间长度加1。
- 归并排序(O(n * log(n))):有序区间的大小呈2的指数及增加,有序区间数呈2的指数级减少,每次排序都合并相邻的两个有序区间为一个有序区间。
- 快速排序(O(n * log(n))):选取基准值的方式有:固定位置选取、随机选取、三数取中法,基准选取完后就可以根据基准划分了。
优化方法:
1、当待排序序列分割到一定长度后使用插入排序。
2、一次排序后,可以将与基准相等的值放在一起,下次就不用考虑了。
3、使用尾递归优化递归操作。 - 希尔排序(O(n * log(n))):多步长插入排序,最后一趟的步长为1。
- 堆排序(O(n * log(n))):每趟排序为调节使成为大根堆的过程。每趟将顶部最大值与堆中的最后一个值交换,并脱落交换后的最后一个值(最大值)将其放入有序区间中。
- 桶排序思想(O(n, 不是基于比较的排序)的两个实现:
计数排序:根据待排序序列的不同值建桶,使每个值都有对应的桶,最后按照桶的顺序将待排序序列倒出,倒出顺序即有序。
基数排序:根据待排序数值各个数位的不同值建桶,使每个数位的不同值都有对应的桶,从低位开始基于每个数位进桶、按桶顺序出桶,每趟进桶、出桶之后都会产生新的排列,按最高位进桶、出桶后的序列即有序。
排序算法的空间复杂度:
- O(1):插入排序、选择排序、冒泡排序、堆排序、希尔排序
- O(n):快速排序(可以通过手摇算法优化为O(1))
- O(logn)~O(n):快速排序
- O(m):技术排序,基数排序,m为桶的数量
排序算法的稳定性
- 稳定的算法:冒泡排序、插入排序、贵宾排序、基于桶排序思想的排序
- 不稳定的算法:选择排序(2,2,2,1)、堆排序(5,5,5)、快速排序(4,3,3,3,5)、希尔排序_步长为2(5,1,1,5)
补充说明
- 补充说明一:排序算法无绝对优劣通常不能随便说哪种排序算法好。这个和要排序的元素相关。例如对人的年龄排序或者身高排序,因为这种数据范围通常比较小,可以考虑采用计数排序。但是对于均匀分布的整数,计数排序就不合适了。除非面试题特别说明,否则认为要排序的数据范围是均匀分布的。
- 补充说明二:为什么叫快速排序快速排序之所以叫快速排序,并不代表它比堆排序和归并排序优良。在最好情况下,它的渐进复杂度与·堆排序和归并排序是相同的。只是快速排序的常量系数比较小而已。
- 补充说明三:工程上的排序
1、工程上的排序是综合排序。
2、数组较小时,插入排序。
3、数组较大时,快速排序或其它O(N*logN)的排序。 - 补充说明四:排序算法总结
一、8大排序算法可以分为3对2单,即插入排序(希尔排序),选择排序(堆排序),冒泡排序(快速排序),归并排序,基数排序;
二、3对中,括号里面的都是它前面排序算法的优化,有话过程中对时间复杂度、空间复杂度、稳定性都有影响,具体为:
1、时间复杂度:三种基本算法的时间复杂度情况为,最好为O(n)(直接选择排序的最好情况为O(n2)),最差为O(n2),平均为O(n2);三种优化后的算法时间复杂度情况为:
(1)希尔排序:最好为O(n),最差为O(n2),平均为O(n1.3);
(2)堆排序:最好为O(n * log2n),最差为O(n * log2n),平均为O(n * log2n)。没优化之前全是O(n2);
(3)快速排序:最好为O(n * log2n),最差为O(n2),平均为O(n * log2n)。
三种优化算法对应三种基本算法,优化效果主要体现在平均时间复杂度上,各算法的优化收获也有细微差别,需要注意的是优化后个别算法的最低时间复杂度反而增加了。
2、空间复杂度:三种基本排序算法的时间复杂度都是O(1), 优化后只有快排变为O(log2n)~O(n)——只有快排划分过程中用了递归,其他的还都为O(1)
3、稳定性:优化后的三种排序算法都不稳定,优化前的选择排序也不稳定,其他稳定
三、比较说明归并排序和堆排序:
归并排序时间复杂度:最好为O(n * log2n),最差为O(n * log2n),平均为O(n * log2n);
堆—排序时间复杂度:最好为O(n * log2n),最差为O(n * log2n),平均为O(n * log2n);
归并排序空间复杂度为O(n)——归并排序合并过程中需要申请最长为n的额外空间,堆排序空间复杂度为O(1);
归并排序稳定,堆排序不稳定。
四、基数排序:
时间复杂度情况为:最好为O(d(r + n)),最差为O(d(r + n)),平均为O(d(r + n));其中,一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(r),共进行d趟分配和收集
空间复杂度:O(rd + n)
稳定性:稳定。
注:r代表关键字基数,d代表长度,n代表关键字个数 - 补充说明五:
冒泡排序:
1、冒泡排序是一种用时间换空间的排序方法,n小时好
2、最坏情况是把顺序的排列变成逆序,或者把逆序的数列变成顺序,最差时间复杂度O(N^2)只是表示其操作次数的数量级
3、最好的情况是数据本来就有序,复杂度为O(n)
快速排序:
1、n大时好,快速排序比较占用内存,内存随n的增大而增大,但却是效率高不稳定的排序算法。
2、划分之后一边是一个,一边是n-1个,
这种极端情况的时间复杂度就是O(N^2)
3、最好的情况是每次都能均匀的划分序列,O(N*log2N)
归并排序:
1、n大时好,归并比较占用内存,内存随n的增大而增大,但却是效率高且稳定的排序算法。
高频题
-
一、已知基本有序数组(基本有序:排序过程中,为了使数组有序,元素的最大移动次数不会超过k,k相对于数组长度很小),请用合适的算法排序
1、考虑时间复杂度O(n)的算法:基数排序、计数排序
因为不知道数值具体范围,所以不考虑
2、考虑时间复杂度为O(n<sup2>)的算法:冒泡排序,选择排序的时间复杂度与初始序列无关,都为O(n<sup2>);插入排序:时间复杂度与初始序列相关,此题为O(n * k);
3、考虑时间复杂度为O(n * log2n)的算法:快速排序:和原始数据无关;归并排序:和原始数据无关
4、希尔排序的时间复杂度不确定
这道题的答案为:改进的堆排序(时间复杂度为:O(n * log2k),具体实现为:每次在一个长度为k的子序列中调整小根堆,然后弹出堆顶最小值到数组的第一个位置,然后新数据入堆,再调整小根堆,这样循环,知道所有数据都经过小根堆弹出。 -
二、判断一个序列中是否有重复的元素,要求算法空间复杂度为O(1)
1、如果没有空间复杂度限制,用hash表实现
2、正确方案:先选择合适的排序算法排序,再在遍历的过程中判断是否有重复值。此处最佳选择为堆排序,但是大部分堆排序用递归实现,此时空间复杂度为O(log2n),所以我们需要用非递归版本的堆排序。 -
三、把两个有序数组合并为一个数组。第一个数组空间正好可以容纳两个数组的元素。
这道题的关键是先处理大的元素,并从后往前覆盖第一个数组。 -
四、荷兰国旗问题(一个序列中只存在0、1、2三个值,设计算法将0全放在左边,1全放在中间,2全放在右边):
1、最优解的时间复杂度为O(n),空间复杂度为O(1)
2、最优解:类似于快排的思想,从左到右遍历数组,遇到1时跳过,遇到0时将0和0区域下一个元素交换再遍历下一个位置,遇到2时将2和2区域下一个元素交换再遍历当前位置。 -
五、在每行和每列都有序的二维数组中检索一个数是否存在:
1、最优解的时间复杂度为O(m + n),空间复杂度为O(1)
2、最优解:从二维数组的右上角开始考察,如果要查找的数大于当前值则向下移动,如果要找的数小于当前值则向左移动。 -
六、在一个无序数组中求要排列的最长子序列的长度:
1、最优解的时间复杂度为O(n),空间复杂度为O(1)
2、最优解:(1)从左到右遍历数组并记录遍历过的最大值继续向右走;(2)记录比该最大值小的最右位置;(3)循环1、2直到遍历完整个序列。(3)从右到左遍历数组并记录遍历过的最小值继续向左走,(4)记录比该最小值大的最左位置;(5)循环3、4直到遍历完整个序列;(6)从最左位置到最右位置即为要排序的最短区间。 -
七、给定一个无序数组,求排序后相邻元素之间的最大差值:
1、最优解的时间复杂度为O(n),空间复杂度为O(n)
2、最优解:类似于桶排序思想,先求得数组中的最大值和最小值,然后将它们之间的差平均分成n份(n为数组长度)形成桶,将各数值按桶排序思想入桶,最大值放在第n + 1个桶,考察相邻桶中后一个桶的最小值和前一个桶的最大值之间的差值,记录所有的差值,其中最大的值就是最大差值。
外部排序
本段引自原文
在内存中进行的排序称为内部排序,而在许多实际应用中,经常需要对大文件进行排序,因为文件中的记录很多,信息量庞大,无法将整个文件拷贝进内存进行排序。因此,需要将带排序的记录存储在外存上,排序时再把数据一部分一部分的调入内存进行排序,在排序中需要多次进行内外存的交互,对外存文件中的记录进行排序后的结果仍然被放到原有文件中。这种排序方法就称外部排序。
由于外存设备的不同,外部排序通常分为磁盘文件排序和磁带文件排序。磁盘是直接存取设备,磁带是顺序存取设备。
文件通常是按块存储在磁盘上,操作系统也是按块对磁盘上的信息进行读写的。因为磁盘读写的机械运动所需的时间远远超过内存计算的时间。因此,在外部排序过程中的时间代价主要考虑访问磁盘的次数,即i/o次数。
外部排序通常采用归并排序:它包括两个相对独立的阶段:首先,根据内存缓存区的大小,将外存上含有n个记录的文件分割成若干长度为 h 的子文件,依次的读入内存并利用有效的内部排序方法对他们进行排序,并将排序后得到的有序子文件重新写回外存,通常称这些有序子文件为归并段或者顺串。
然后,对这些归并段进行逐趟归并,使得归并段(有序的子文件)逐渐由小到大,直至得到整个有序文件为止。
之后还可以采用多路平衡归并和败者树,置换-选择排序,最佳归并树进行处理,这里就不一一展开了。