排序算法解析

排序算法可以分为外部和内部排序两大类。

当要排序的数据文件过大,大过内存空间时,需要将其分成若干个子文件分别进行内部排序,然后再进行多路归并排序。

我们通常讲的都是内部排序。

 

主要分为这几大类:

插入排序(直接插入排序和希尔排序)

选择排序(直接选择排序和堆排序)

交换排序(冒泡排序和快速排序)

归并排序

 

排序算法的几个指标:

时间复杂度,空间复杂度(最好情况:完全有序,最差情况:逆序,平均),稳定性

稳定性:相等的元素在排序之后是否能保持原本的前后顺序不变

其中,算法稳定的是直接插入排序,冒泡排序,基数排序和归并排序。

由于只有快速排序和归并排序是递归的,所以二者的空间复杂度不是o(1)

1、直接插入排序

每次将一个待排序的数据元素,插入到前面已经排好序的数列中的适当位置,使数列依然有序;直到待排序数据元素全部插入完为止。

缺点是,在插入的时候,要不断移动已经排序好的数组

最好情况:完全有序,每次比较完无需移动,时间复杂度o(n)

最差情况:完全逆序,需要移动的次数为,1,2,...,n-1次,所以时间复杂度o(n^2)

平均为o(n^2)

2、希尔排序

针对直接插入排序要不断移动已经排序好的数组,提出的。将一个长序列由增量分成若干个子序列,分别进行插入排序,这时的序列是一个部分有序的序列,再进行插入排序,就无需移动多次数组。

先取一个小于n的证书d1作为第一个增量,把文件的全部记录分成d1组。所有距离为d1的倍数的记录放在同一组中。先在各组内进行直接插入排序,然后取第二 个增量d2<d1重复上述的分组和排序,直到所取的增量dt=1,即所有记录放在同一组中进行直接插入排序为止。该方法实际上是一种分组插入方法。

希尔排序的增量序列会影响排序的性能。平均时间 O(nlogn) 最差时间O(n^s) 1<s<2

3、选择排序

每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在已排好序的数列的最后(交换),直到全部待排序的数据元素排完。

比较时间固定:(n-1)+(n-2)+...+1     o(n^2)

交换时间不定:最好情况:完全有序,无需交换

                         最差情况:n-1次

因此,时间复杂度就是o(n^2)

因为每次外循环会把最小值和相应位置交换,因此是不稳定的。

比如:1,9,3,4,6,9,2,

在第二次循环时,最小值为2,要把它和第二个位置上的9进行交换,而这样就改变了两个9的前后顺序。

4、堆排序

它是使用了二叉树结构的选择排序。主要优化的是外循环每次选最大/小值这个问题。

二叉树有一个特点:

最大堆:子节点的值永远不能大于父节点的值

最小堆:子节点的值永远不能小于父节点的值

一般都用数组来表示堆,i结点的父结点下标就为(i – 1) / 2。它的左右子结点下标分别为2 * i + 1和2 * i + 2。

首先,将未排序的数组初始化成一个堆;

然后不断输出并删除堆顶元素,将堆底元素补到堆顶,并进行元素调整,使被打乱的二叉树重新变成一个堆。

由于每次重新恢复堆的时间复杂度为O(logN),共N - 1次重新恢复堆操作,再加上前面建立堆时N / 2次向下调整,每次调整时间复杂度也为O(logN)。二次操作时间相加还是O(N * logN)。故堆排序的时间复杂度为O(N * logN)。

5、冒泡排序

两两比较待排序数据元素的大小,发现两个数据元素的次序相反时即进行交换,直到没有反序的数据元素为止。

其实它和选择排序的思路有点像,每次外循环确定一个元素的位置,二者的区别在于:它是在内部循环进行交换元素,而选择排序是在外循环进行交换的,因此,二者的比较时间是一样的,差就差在了交换时间上。

所以可以看出,选择排序的效率要比冒泡高,尽管二者的时间复杂度一样,都为o(n^2)。

但是,冒泡排序有一些改进版的算法还是很不错的。

比如:当某一次外循环,没有出现任何的交换时,说明此时序列是完全有序的,可以提前终止。

6、快速排序

在 当前无序区R[1..H]中任取一个数据元素作为比较的"基准"(不妨记为X),用此基准将当前无序区划分为左右两个较小的无序区:R[1..I-1]和 R[I+1..H],且左边的无序子区中数据元素均小于等于基准元素,右边的无序子区中数据元素均大于等于基准元素,而基准X则位于最终排序的位置上,即 R[1..I-1]≤X.Key≤R[I+1..H](1≤I≤H),当R[1..I-1]和R[I+1..H]均非空时,分别对它们进行上述的划分过 程,直至所有无序子区中的数据元素均已排序为止。

它其实就是一种分治的思想,最好的情况,肯定就是每次划分的比较均匀,o(nlogn);最坏的情况就是将长度为n的序列划分成1和n-1的子序列,o(n^2),此时,快速排序将退化为冒泡排序;平均的时间复杂度为o(nlogn)。

在所有平均时间复杂度为 O(nlogn)的算法中,快速排序的平均性能是最好的。

可见,基准元素的选择很重要,在普林斯顿那门算法课里,它的基准是随机选择的,这样可以使该算法性能较优。

常用基准关键字的选取方式如下:

(1)三者取中。三者取中是指在当前序列中,将其首、尾和中间位置上的记录进行比较,选择三者的中值作为基准关键字,在划分开始前交换序列中的第一个记录与基准关键字的位置。

(2)取随机数。取 left(左边)和 right(右边)之间的一个随机数 m(left⩽m⩽rightleft⩽m⩽right),用 n[m] 作为基准关键字。这种方法使得 n[ left ] 和 n[ right ] 之间的记录是随机分布的,采用此方法得到的快速排序一般称为随机的快速排序。

另外,由于采用了递归,需要借助辅助的内存空间。空间复杂度为o(logn)~o(n)。

具体编程的步骤:(因为要交换元素,所以算法不稳定)

1.首先选择一个中间元素(一般选左端或者右端)。
2.分别获取除中间元素外的左右两端的索引。
3.由左右两端逐渐向中间迭代,每迭代一步比较一下索引中的元素和中间元素,当左边出现比中间元素大的元素的时候,暂停左边的迭代,当右边迭代出比中间元素小的元素的时候,右边迭代也暂停,交换左右两边的元素。
4.重复步骤3,直到左右两边的索引相遇,然后将中间元素移动到中间,这时中间元素左边的元素都比它小,右边的元素都比它大。
5.将上面的中间元素左右两边当成两个数组,分别进行上述过程。
6.重复以上步骤直到数组不可再分。
(链接:https://www.jianshu.com/p/655db46e161d)

快速排序的改进:由于现实中重复相等的元素特别多,所以可以分成三部分:小于,大于,相等,进一步降低排序的工作量
 

7、归并排序

和快速排序不同的是,它是自底向上的分治法。

归并排序在分解时,只是单纯地将原问题分解为两个规模减半的子问题;在分解过程中,没有任何对原问题所含信息的利用,没有任何尝试对问题求解的动作;这种分解持续进行,直到子问题规模降足够小(为1),这时子问题直接得解;然后,自底向上地合并子问题的解,这时才真正利用原问题的特定信息,执行求解动作,对元素进行比较。

而快速排序与归并排序不同,它是自顶向下,对原问题进行单纯的对称分解;其求解动作在分解子问题开始前进行,而问题的分解基于原问题本身包含的信息;然后,自顶向下地递归求解每个子问题。

快速和归并排序,如果分组策略越简单,后面的合并策略就越复杂,因为快速排序在分组时,已经根据元素大小来分组了,而合并时,只需要把两个分组合并起来就行了,归并排序则需要对两个有序的数组根据大小合并。

归并算法没有最好和最坏情况之分,它每次都是均匀的二分法。

T(n)=2T(n/2)+o(n),o(n)是对两个半长的子序列进行归并排序时所需要的时间复杂度。

T(n)=o(nlogn)

 

 

在给千万级别的数组排序的情况下:
Quick > Merge > Shell > Insertion > Selection

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值