关于软件测试:一文搞定十大排序算法动画图解

68次阅读

共计 5171 个字符,预计需要花费 13 分钟才能阅读完成。

排序的定义
排序,就是重新排列表中的元素,使表中的元素满足按关键字递增或递加的过程。为了査找不便,通常要求计算机中的表是按关键字有序的。
排序的确切定义如下:
算法的稳定性:
若待排序表中有两个元素 Ri 和 Rj,其对应的关键字 keyi = kcyj , 且在排序前 Ri 在 Rj 的后面。应用某一排序算法排序后,Ri 依然在 Rj 的后面尽的后面,则称这个排序算法是稳固的。否则称排序算法是不稳固的。
须要留神的是,算法是否具备稳定性并不能掂量—个算法的优劣,它次要针对算法的性质进行形容。只需举出一组关徤字的实例,即可阐明一个算法是不稳固的。
工夫复杂度:[1](来自百度百科)
剖析:随着模块 n 的增大,算法执行的工夫的增长率和 f(n) 的增长率成正比,所以 f(n) 越小,算法的工夫复杂度越低,算法的效率越高。
空间复杂度:[2](来自百度百科)
相似于工夫复杂度的探讨,一个算法的空间复杂度 S(n) 定义为该算法所消耗的存储空间,它也是问题规模 n 的函数。渐近空间复杂度也经常简称为空间复杂度。
空间复杂度 (SpaceComplexity) 是对一个算法在运行过程中长期占用存储空间大小的量度。一个算法在计算机存储器上所占用的存储空间,包含存储算法自身所占用的存储空间,算法的输入输出数据所占用的存储空间和算法在运行过程中长期占用的存储空间这三个方面。
算法的输入输出数据所占用的存储空间是由要解决的问题决定的,是通过参数表由调用函数传递而来的,它不随本算法的不同而扭转。存储算法自身所占用的存储空间与算法书写的长短成正比,要压缩这方面的存储空间,就必须编写出较短的算法。
算法在运行过程中长期占用的存储空间随算法的不同而异,有的算法只须要占用大量的长期工作单元,而且不随问题规模的大小而扭转,咱们称这种算法是“就地 ” 进行的,是节俭存储的算法,有的算法须要占用的长期工作单元数与解决问题的规模 n 无关,它随着 n 的增大而增大,当 n 较大时,将占用较多的存储单元,例如疾速排序和归并排序算法就属于这种状况。
算法的分类能够依照是否是比拟类的算法来分类,也能够依照排序过程中数据是否都存在于内存中来分类:
如下:
依照外部排序和内部排序分类:

依照是否为比拟类的排序来分:

插入排序(Insertion-Sort)的算法形容是一种简略直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应地位并插入。
算法形容
一般来说,插入排序都采纳 in-place 在数组上实现。具体算法形容如下:

  • 从第一个元素开始,该元素能够认为曾经被排序;
  • 取出下一个元素,在曾经排序的元素序列中从后向前扫描;
  • 如果该元素(已排序)大于新元素,将该元素移到下一地位;
  • 反复步骤 3,直到找到已排序的元素小于或者等于新元素的地位;
  • 将新元素插入到该地位后;
  • 反复步骤 2~5。
    动图演示

C 代码实现
算法剖析
插入排序在实现上,通常采纳 in-place 排序(即只需用到 O(1)的额定空间的排序),因此在从后向前扫描过程中,须要重复把已排序元素逐渐向后挪位,为最新元素提供插入空间。
1959 年 Shell 创造,第一个冲破 O(n2)的排序算法,是简略插入排序的改进版。它与插入排序的不同之处在于,它会优先比拟间隔较远的元素。希尔排序又叫放大增量排序。
算法形容
先将整个待排序的记录序列宰割成为若干子序列别离进行间接插入排序,具体算法形容:
1. 抉择一个增量序列 t1,t2,…,tk,其中 ti > tj,tk=1;
2. 按增量序列个数 k,对序列进行 k 趟排序;
3. 每趟排序,依据对应的增量 ti,将待排序列宰割成若干长度为 m 的子序列,别离对各子表进行间接插入排序。仅增量因子为 1 时,整个序列作为一个表来解决,表长度即为整个序列的长度。
动图演示
C 代码实现
算法剖析
希尔排序是基于插入排序的以下两点性质而提出改良办法的:

  • 插入排序在对简直曾经排好序的数据操作时,效率高,即能够达到线性排序的效率
  • 但插入排序一般来说是低效的,因为插入排序每次只能将数据挪动一位
    工夫复杂度:最坏状况下为 O(n^2),均匀工夫复杂度为 O(nlogn);
    空间复杂度:归并排序须要一个大小为 1 的长期存储空间用以保留合并序列,所以空间复杂度为 O(1);
    算法稳定性:从下面图片中能够看出,数字 5 在排序后替换了地位,所以它是不稳固的算法。
    抉择排序 (Selection-sort) 是一种简略直观的排序算法。它的工作原理:首先在未排序序列中找到最小(大)元素,寄存到排序序列的起始地位,而后,再从残余未排序元素中持续寻找最小(大)元素,而后放到已排序序列的开端。以此类推,直到所有元素均排序结束。
    算法形容
    n 个记录的间接抉择排序可通过 n - 1 趟间接抉择排序失去有序后果。具体算法形容如下:
    4. 初始状态:无序区为 R[1…n],有序区为空;
    5. 第 i 趟排序(i=1,2,3…n-1) 开始时,以后有序区和无序区别离为 R[1…i-1]和 R(i…n)。该趟排序从以后无序区中 - 选出关键字最小的记录 R[k],将它与无序区的第 1 个记录 R 替换,使 R[1…i]和 R[i+1…n)别离变为记录个数减少 1 个的新有序区和记录个数缩小 1 个的新无序区;
    6.n- 1 趟完结,数组有序化了。
    动图演示

    C 语言实现
    算法剖析
    体现最稳固的排序算法之一,因为无论什么数据进去都是 O(n2)的工夫复杂度,所以用到它的时候,数据规模越小越好。惟一的益处可能就是不占用额定的内存空间了吧。实践上讲,抉择排序可能也是平时排序个别人想到的最多的排序办法了吧。
    堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。沉积是一个近似齐全二叉树的构造,并同时满足沉积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。
    算法形容
    7. 将初始待排序关键字序列(R1,R2….Rn) 构建成大顶堆,此堆为初始的无序区;
    8. 将堆顶元素 R[1]与最初一个元素 R[n]替换,此时失去新的无序区 (R1,R2,……Rn-1) 和新的有序区 (Rn), 且满足 R[1,2…n-1]<=R[n];
    9. 因为替换后新的堆顶 R[1] 可能违反堆的性质,因而须要对以后无序区 (R1,R2,……Rn-1) 调整为新堆,而后再次将 R[1]与无序区最初一个元素替换,失去新的无序区 (R1,R2….Rn-2) 和新的有序区 (Rn-1,Rn)。一直反复此过程直到有序区的元素个数为 n -1,则整个排序过程实现。
    动图演示

    代码实现:
    算法剖析:
    堆排序是一种抉择排序,整体次要由构建初始堆 + 替换堆顶元素和开端元素并重建堆两局部组成。其中构建初始堆经推导复杂度为 O(n),在替换并重建堆的过程中,需替换 n - 1 次,而重建堆的过程中,依据齐全二叉树的性质,[log2(n-1),log2(n-2)…1]逐渐递加,近似为 nlogn。所以堆排序工夫复杂度个别认为就是 O(nlogn)级。
    冒泡排序是一种简略的排序算法。它反复地走访过要排序的数列,一次比拟两个元素,如果它们的程序谬误就把它们替换过去。走访数列的工作是反复地进行直到没有再须要替换,也就是说该数列曾经排序实现。这个算法的名字由来是因为越小的元素会经由替换缓缓“浮”到数列的顶端。
    算法形容
    10. 比拟相邻的元素。如果第一个比第二个大,就替换它们两个;
    11. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最初一对,这样在最初的元素应该会是最大的数;
    12. 针对所有的元素反复以上的步骤,除了最初一个;
    13. 反复步骤 1~3,直到排序实现。
    动图演示

    C 语言实现
    算法剖析
    若文件的初始状态是正序的,一趟扫描即可实现排序。所需的关键字比拟次数 C 和记录挪动次数 M 均达到最小值:Cmin = N – 1, Mmin = 0。所以,冒泡排序最好工夫复杂度为 O(N)。
    若初始文件是反序的,须要进行 N -1 趟排序。每趟排序要进行 N – i 次关键字的比拟 (1 ≤ i ≤ N – 1),且每次比拟都必须挪动记录三次来达到替换记录地位。在这种状况下,比拟和挪动次数均达到最大值:
    Cmax = N(N-1)/2 = O(N2)
    Mmax = 3N(N-1)/2 = O(N2)
    冒泡排序的最坏工夫复杂度为 O(N2)。因而,冒泡排序的均匀工夫复杂度为 O(N2)。
    疾速排序的根本思维:通过一趟排序将待排记录分隔成独立的两局部,其中一部分记录的关键字均比另一部分的关键字小,则可别离对这两局部记录持续进行排序,以达到整个序列有序。
    算法形容
    疾速排序应用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法形容如下:
    14. 从数列中挑出一个元素,称为“基准”(pivot);
    15. 从新排序数列,所有元素比基准值小的摆放在基准后面,所有元素比基准值大的摆在基准的前面(雷同的数能够到任一边)。在这个分区退出之后,该基准就处于数列的两头地位。这个称为分区(partition)操作;
    16. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
    动图演示
    C 语言实现
    算法剖析:
    当数据有序时,以第一个关键字为基准分为两个子序列,前一个子序列为空,此时执行效率最差。
    而当数据随机散布时,以第一个关键字为基准分为两个子序列,两个子序列的元素个数靠近相等,此时执行效率最好。
    所以,数据越随机散布时,疾速排序性能越好;数据越靠近有序,疾速排序性能越差。
    归并排序是建设在归并操作上的一种无效的排序算法。该算法是采纳分治法(Divide and Conquer)的一个十分典型的利用。将已有序的子序列合并,失去齐全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为 2 - 路归并。
    算法形容
    17. 把长度为 n 的输出序列分成两个长度为 n / 2 的子序列;
    18. 对这两个子序列别离采纳归并排序;
    19. 将两个排序好的子序列合并成一个最终的排序序列。
    动图演示

    C 语言实现
    算法剖析
    归并排序是一种稳固的排序办法。和抉择排序一样,归并排序的性能不受输出数据的影响,但体现比抉择排序好的多,因为始终都是 O(nlogn)的工夫复杂度。代价是须要额定的内存空间。
    计数排序不是基于比拟的排序算法,其外围在于将输出的数据值转化为键存储在额定开拓的数组空间中。作为一种线性工夫复杂度的排序,计数排序要求输出的数据必须是有确定范畴的整数。
    算法形容
    20. 找出待排序的数组中最大和最小的元素;
    21. 统计数组中每个值为 i 的元素呈现的次数,存入数组 C 的第 i 项;
    22. 对所有的计数累加(从 C 中的第一个元素开始,每一项和前一项相加);
    23. 反向填充指标数组:将每个元素 i 放在新数组的第 C(i) 项,每放一个元素就将 C(i)减去 1。
    动图演示
    C 语言实现
    算法剖析
    计数排序是一个稳固的排序算法。当输出的元素是 n 个 0 到 k 之间的整数时,工夫复杂度是 O(n+k),空间复杂度也是 O(n+k),其排序速度快于任何比拟排序算法。当 k 不是很大并且序列比拟集中时,计数排序是一个很无效的排序算法。
    基数排序是依照低位先排序,而后收集;再依照高位排序,而后再收集;顺次类推,直到最高位。有时候有些属性是有优先级程序的,先按低优先级排序,再按高优先级排序。最初的秩序就是高优先级高的在前,高优先级雷同的低优先级高的在前。
    算法形容
    24. 获得数组中的最大数,并获得位数;
    25.arr 为原始数组,从最低位开始取每个位组成 radix 数组;
    26. 对 radix 进行计数排序(利用计数排序实用于小范畴数的特点);
    动图演示
    C 语言实现
    算法剖析
    基数排序基于别离排序,别离收集,所以是稳固的。但基数排序的性能比桶排序要略差,每一次关键字的桶调配都须要 O(n)的工夫复杂度,而且调配之后失去新的关键字序列又须要 O(n)的工夫复杂度。如果待排数据能够分为 d 个关键字,则基数排序的工夫复杂度将是 O(d*2n),当然 d 要远远小于 n,因而基本上还是线性级别的。
    基数排序的空间复杂度为 O(n+k),其中 k 为桶的数量。一般来说 n >>k,因而额定空间须要大略 n 个左右。
    桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的要害就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假如输出数据遵从均匀分布,将数据分到无限数量的桶里,每个桶再别离排序(有可能再应用别的排序算法或是以递归形式持续应用桶排序进行排)。
    算法形容
    27. 设置一个定量的数组当作空桶;
    28. 遍历输出数据,并且把数据一个一个放到对应的桶里去;
    29. 对每个不是空的桶进行排序;
    30. 从不是空的桶里把排好序的数据拼接起来。
    动图演示
    C 语言实现
    算法剖析
    桶排序最好状况下应用线性工夫 O(n),桶排序的工夫复杂度,取决与对各个桶之间数据进行排序的工夫复杂度,因为其它局部的工夫复杂度都为 O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的工夫也会越少。但相应的空间耗费就会增大。(end)

点一下难看,就少一个 Bug!

正文完
 0