排序算法总结

排序算法总结,第1张

排序算法是什么?有多少种?排序算法总结又是怎样?以下是为您整理的排序算法总结,供您参考!

【排序算法总结】

排序算法:一种能将一串数据依照特定的排序方式进行排列的一种算法。

排序算法性能:取决于时间和空间复杂度,其次还得考虑稳定性,及其适应的场景。

稳定性:让原本有相等键值的记录维持相对次序。也就是若一个排序算法是稳定的,当有俩个相等键值的记录R和S,且原本的序列中R在S前,那么排序后的列表中R应该也在S之前。

以下来总结常用的排序算法,加深对排序的理解。

冒泡排序

原理

俩俩比较相邻记录的排序码,若发生逆序,则交换有俩种方式进行冒泡,一种是先把小的冒泡到前边去,另一种是把大的元素冒泡到后边。

性能

时间复杂度为O(N^2),空间复杂度为O(1)。排序是稳定的,排序比较次数与初始序列无关,但交换次数与初始序列有关。

优化

若初始序列就是排序好的,对于冒泡排序仍然还要比较O(N^2)次,但无交换次数。可根据这个进行优化,设置一个flag,当在一趟序列中没有发生交换,则该序列已排序好,但优化后排序的时间复杂度没有发生量级的改变。

代码

   插入排序

原理

依次选择一个待排序的数据,插入到前边已排好序的序列中。

性能

时间复杂度为O(N^2),空间复杂度为O(1)。算法是稳定的,比较次数和交换次数都与初始序列有关。

优化

直接插入排序每次往前插入时,是按顺序依次往前找,可在这里进行优化,往前找合适的插入位置时采用二分查找的方式,即折半插入。

折半插入排序相对直接插入排序而言:平均性能更快,时间复杂度降至O(NlogN),排序是稳定的,但排序的比较次数与初始序列无关,总是需要foor(log(i))+1次排序比较。

使用场景

当数据基本有序时,采用插入排序可以明显减少数据交换和数据移动次数,进而提升排序效率。

代码

希尔排序

原理

插入排序的改进版,是基于插入排序的以下俩点性质而提出的改进方法:

插入排序对几乎已排好序的数据 *** 作时,效率很高,可以达到线性排序的效率。

但插入排序在每次往前插入时只能将数据移动一位,效率比较低。

所以希尔排序的思想是:

先是取一个合适的gap

缩小间隔gap,例如去gap=ceil(gap/2),重复上述子序列划分和排序

直到,最后gap=1时,将所有元素放在同一个序列中进行插入排序为止。

性能

开始时,gap取值较大,子序列中的元素较少,排序速度快,克服了直接插入排序的缺点其次,gap值逐渐变小后,虽然子序列的元素逐渐变多,但大多元素已基本有序,所以继承了直接插入排序的优点,能以近线性的速度排好序。

代码

选择排序

原理

每次从未排序的序列中找到最小值,记录并最后存放到已排序序列的末尾

性能

时间复杂度为O(N^2),空间复杂度为O(1),排序是不稳定的(把最小值交换到已排序的末尾导致的),每次都能确定一个元素所在的最终位置,比较次数与初始序列无关。

代码

快速排序

原理

分而治之思想:

Divide:找到基准元素pivot,将数组A[p..r]划分为A[p..pivotpos-1]和A[pivotpos+1…q],左边的元素都比基准小,右边的元素都比基准大

Conquer:对俩个划分的数组进行递归排序

Combine:因为基准的作用,使得俩个子数组就地有序,无需合并 *** 作。

性能

快排的平均时间复杂度为O(NlogN),空间复杂度为O(logN),但最坏情况下,时间复杂度为O(N^2),空间复杂度为O(N)且排序是不稳定的,但每次都能确定一个元素所在序列中的最终位置,复杂度与初始序列有关。

优化

当初始序列是非递减序列时,快排性能下降到最坏情况,主要因为基准每次都是从最左边取得,这时每次只能排好一个元素。

所以快排的优化思路如下:

优化基准,不每次都从左边取,可以进行三路划分,分别取最左边,中间和最右边的中间值,再交换到最左边进行排序或者进行随机取得待排序数组中的某一个元素,再交换到最左边,进行排序。

在规模较小情况下,采用直接插入排序

代码

归并排序

原理

分而治之思想:

Divide:将n个元素平均划分为各含n/2个元素的子序列

Conquer:递归的解决俩个规模为n/2的子问题

Combine:合并俩个已排序的子序列。

性能

时间复杂度总是为O(NlogN),空间复杂度也总为为O(N),算法与初始序列无关,排序是稳定的。

优化

优化思路:

在规模较小时,合并排序可采用直接插入

在写法上,可以在生成辅助数组时,俩头小,中间大,这时不需要再在后边加俩个while循环进行判断,只需一次比完。

代码

堆排序

原理

堆的性质:

是一棵完全二叉树

每个节点的值都大于或等于其子节点的值,为最大堆反之为最小堆。

堆排序思想:

将待排序的序列构造成一个最大堆,此时序列的最大值为根节点

依次将根节点与待排序序列的最后一个元素交换

再维护从根节点到该元素的前一个节点为最大堆,如此往复,最终得到一个递增序列

性能

时间复杂度为O(NlogN),空间复杂度为O(1),因为利用的排序空间仍然是初始的序列,并未开辟新空间。算法是不稳定的,与初始序列无关。

使用场景

想知道最大值或最小值时,比如优先级队列,作业调度等场景。

代码

计数排序

原理

先把每个元素的出现次数算出来,然后算出该元素所在最终排好序列中的绝对位置(最终位置),再依次把初始序列中的元素,根据该元素所在最终的绝对位置移到排序数组中。

性能

时间复杂度为O(N+K),空间复杂度为O(N+K),算法是稳定的,与初始序列无关,不需要进行比较就能排好序的算法。

使用场景

算法只能使用在已知序列中的元素在0-k之间,且要求排序的复杂度在线性效率上。

代码

桶排序

原理

根据待排序列元素的大小范围,均匀独立的划分M个桶

将N个输入元素分布到各个桶中去

再对各个桶中的元素进行排序

此时再按次序把各桶中的元素列出来即是已排序好的。

性能

时间复杂度为O(N+C),O(C)=O(M(N/M)log(N/M))=O(NlogN-NlogM),空间复杂度为O(N+M),算法是稳定的,且与初始序列无关。

使用场景

算法思想和散列中的开散列法差不多,当冲突时放入同一个桶中可应用于数据量分布比较均匀,或比较侧重于区间数量时。

基数排序

原理

对于有d个关键字时,可以分别按关键字进行排序。有俩种方法:

MSD:先从高位开始进行排序,在每个关键字上,可采用计数排序

LSD:先从低位开始进行排序,在每个关键字上,可采用桶排序

性能

时间复杂度为O(d*(N+K)),空间复杂度为O(N+K)。

   总结

以上排序算法的时间、空间与稳定性的总结如下:

最近在慕课网上学习了O(n2)时间复杂度的相关算法,总算是对这些算法的优缺点有了详细的特点。其实对于任何的算法,没有优点和缺点,而是有相应的特点。所以我们应该结合不同的排序环境来选择不同的排序算法,从而达到在实现时间和执行效率上的平衡。这是因为,越是简单的排序算法,实现起来肯定是越容易,而且出现BUG的概率也不会太大。相反,复杂算法可能效率更高,但是出现问题的可能性也会更大。下面,我就结合O(n2)时间复杂度的四个经典排序算法,为您详细讲解这四个算法的特点。

定义:选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

图示说明:

源码实现:

分析:通过选择排序的图示和源码我们可以看出来,选择排序要进行两次循环,而且最关键的是内层循环在每一次执行时都是全部执行完的。那我们有没有办法让内层循环不用每次都执行完呢?方法肯定是有的,这就是冒泡排序。

定义:冒泡排序(Bubble Sort),是一种计算机科学领域的较简单的排序算法。它重复地走访过要排序的元素列,一次比较两个相邻的元素,如果他们的顺序(如从大到小、首字母从A到Z)错误就把他们交换过来。走访元素的工作是重复地进行直到没有相邻元素需要交换,也就是说该元素已经排序完成。

图示说明:

源码实现:

分析:从图示和源码可以看出来,从执行次数上来说,冒泡排序是比选择排序的循环次数更少的。那是不是就可以说,如果待排序的数组中元素比较合适,冒泡排序在时间复杂度上是不是会比选择排序更好呢?真的是这样的吗?

其实不是的,经过多次测试验证,冒泡排序基本上是比选择排序的时间复杂度要差的,这是为什么呢?从源码中我们可以很明显的看出来,虽然冒泡排序是比选择排序执行次数少了,但是交换的次数明显增多了,而如果你对计算机程序指令的实现原理只要有一个基本的认识,就应该知道交换动作比赋值动作是需要更多指令 *** 作的。所以说,最终冒泡排序大部分情况下,比选择排序的时间复杂度都要高。

既然交换动作这么消耗资源,那有没有一种方法,即能够减少内层循环的执行次数,又可以减少甚至是无需交换 *** 作呢?这就要请出插入排序了。

定义:插入排序(Insertion Sort)的基本 *** 作就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,即每步将一个待排序的记录,按其关键码值的大小插入前面已经排序的文件中适当位置上,直到全部插入完为止。

图示说明:

源码实现:

分析:从图示和源码可以看出来,插入排序(优化后的)是没有交换 *** 作的,而且对于内层循环来说,如果待排序的元素是比较大的值,那内层循环执行的次数会非常的少。因此,如果原始数据基本上是有序的,那使用插入排序的效率会非常的高。在O(n2)级别的排序算法还可以再优化吗?如果可以从哪里优化呢?下面我们来介绍希尔排序,正是这个排序算法的提出,使得排序算法打破了O(n2)时间复杂度的禁锢。

定义:希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。该算法的基本思想是:把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,排序算法便终止。

对于希尔排序来说,最关键的就是增量该如何选取。这个增量该怎么确定,这还真是个数学难题,至今没有解答。但是通过大量的实验,还是有个经验值的。我们的例子给出的增量选取公式是:h = 3 * h + 1,下面请看图示说明。

图示说明:

源码实现:

分析:从插入排序中我们知道,插入排在待排序数组基本有序时,插入排序的算法效率会非常高,所以我们可以这样认为,希尔排序的最终思想就是:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,在对全体进行一次直接插入排序。

而希尔排序的效率之所以很高,就是因为这个基本思想确实很有用:即当h值大的时候,数据项每一趟排序需要移动元素的个数很少,但数据项移动的距离很长。这是非常有效率的。而当h减小时,每一趟排序需要移动的元素的个数增多,但是此时数据项已经接近于它们排序后最终的位置,这对于插入排序可以更有效率。正是这两种情况的结合才使希尔排序效率那么高。

对于增量的选取,可以称得上是一种魔法。在希尔的原稿中,他建议初始的间距为N/2,简单地把每一趟排序分成了两半。但是,这被证明并不是最好的数列。尽管对于大多数的数据来说这个方法还是比插入排序效果好,但是这种方法有时会使运行时间降到O(N2),这并不比插入排序的效率更高。间隔序列中的数字互质通常被认为很重要:也就是说,除了1之外它们没有公约数。这个约束条件使每一趟排序更有可能保持前一趟排序已排好的效果。希尔最初以N/2为间隔的低效性就是归咎于它没有遵守这个准则。

总结:上面就是四种经典O(n2)级别排序算法的相关说明。其实在各种场合下选择排序和冒泡排序基本上是不会使用的,因为使用场景基本没有。而对于插入排序和希尔排序来说,在待排序数据基本有序的情况下,使用场景还是有的,比如一些日志文件中存储的日志,可能大部分的日志记录都是基于时间排序,只是在某些极端情况下导致一些日志晚存储了导致时间不一致。

我是徐建航, 这是我写的第31篇文章,欢迎你加入007社群,七天写一篇,一起写七年,七年之后一起去南极。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/12075116.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-20
下一篇 2023-05-20

发表评论

登录后才能评论

评论列表(0条)

保存