今天我们就来总结一下在数据结构中那一些万恶的排序算法吧~ (如果是单纯的应付考试的话会用冒泡排序就ok啦) ,如果想要深度学习数据结构的话,此篇只建议当做复习资料食用。
- 排序算法分类
- 排序算法比较表
相关概念:
稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总的操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。
备注:
基数排序时间复杂度为O(N*K),其中N为数据个数,K为数据位数。
O(n)这样的标志叫做渐近时间复杂度,是个近似值.各种渐近时间复杂度由小到大的顺序如下
O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) < O(n^n)
- 冒泡排序(Bubble Sort)
动态演示
冒泡排序是一种简单的排序算法。它从数列中第一个元素开始不停比较数列中相邻两个元素,如果第一个元素比第二个元素大(小)则交换位置直到整个数列为有序数列。
冒泡排序的时间复杂度为 O(n2)。
C语言 实现该算法 代码如下:
建议代码都自己敲一遍
- 快速排序(Quicksort)
动态演示
快速排序尤其适用于对大数据的排序,它的高速和高效无愧于“快速”两个字。虽然说它是“最常用”的,可对于初学者而言,用它的人却非常少。因为虽然很快,但它也是逻辑最复杂、最难理解的算法,因为快速排序要用到递归和函数调用。快速排序所采用的思想是分治的思想。所谓分治,就是指以一个数为基准,将序列中的其他数往它两边“扔”。以从小到大排序为例,比它小的都“扔”到它的左边,比它大的都“扔”到它的右边,然后左右两边再分别重复这个操作,不停地分,直至分到每一个分区的基准数的左边或者右边都只剩一个数为止。这时排序也就完成了。
时间复杂度为O(nlgn)
同样快速排序是大规模递归的算法,它比大部分排序算法都要快。一般用于数据个数比较多的情况。尽管可以在某些特殊的情况下写出比快速排序快的算法,但是就通常情况而言,没有比它更快的了。快速排序是递归的,对于内存非常有限的机器来说,它不是一个好的选择。
详细讲解:
1、设置两个变量 i、j,排序开始的时候:i=0,j=n–1。
2、以数组第一个元素为关键数据,赋给变量 key,即 key=A[0]。
3、从 j 开始向前搜索,即由后开始向前搜索(j--),找到第一个小于 key 的值 A[j],将 A[j] 和 A[i] 互换。
4、然后再从 i 开始向后搜索,即由前开始向后搜索(++i),找到第一个大于 key 的 A[i],将 A[i] 和 A[j] 互换。
重复第 3、4 步,直到 i=j。此时就能确保序列中所有元素都与 key 比较过了,且 key 的左边全部是比 key 小的,key 的右边全部是比 key 大的。
第一轮比较后序列就以 key 为中心分成了左右两部分,然后分别对左右两部分分别递归执行上面几个步骤,直到排序结束。
C语言 实现该算法 代码如下:
- 选择排序(跟冒泡排序很像不要弄混)
动态演示
其思想为:对于选择排序,首先理解排序的思想。给定一个数组,这种思想首先假定数组的首元素为最大(最小)的。此时就要利用3个变量i,j,k表示元素的下标。i表示当前,j表示找到的最大(最小)的下标,k用于存放每次循环中最大值的下标。
其时间复杂度为O(n^2)
C语言 实现该算法 代码如下:
- 堆排序(Heapsort)
耐心看完
这个我可能讲的不是特别清楚
可能有些小伙伴还对堆不是特别了解,所以先来讲一下什么是堆吧
堆是一棵完全二叉树,所谓完全二叉树即叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。
二叉树
提到二叉树就不得不提父节点与左、右子节点的概念。在二叉树中,一个父节点的子节点数的取值范围为[0,2],子节点数为零的父节点常被称为叶子节点。每一个父节点又可以看成是其子树分支的根节点。
综合数组和二叉树解结构来看,在知道了某一数字在数组中的索引后,可以很方便地计算出二叉树中该数字的左右子节点,或者父节点在数组中的索引。
以数字5为例,它的数组表示为A[1],根据二叉树,其父节点为4,即A[0];同时其左节点为2,右节点为6,分别对应A[3],A[4]。
故可得出一个结论,A[i]的左节点为A[2i+1],右节点为A[2i+2],父节点为A[i/2],它从数组索引的角度描述了数字与数字在二叉树中的位置关系。(1、在应用此结论时需确认A[i]存在相应的左节点、右节点、根节点;2、此结论在后面会反复用到;3、此结论实际上是完全二叉树的一个基本性质,这也是为什么堆的结构性要求其满足完全二叉树的形式,就是为了使用此结论),知道元素下沉的小伙伴就会知道,堆排序其实就是元素下沉的一个过程。
元素下沉也就是构造一个大根堆的过程,通俗点讲就是在一个大根堆中,一个节点的元素在其子树所有元素组成的集合中必定是最大值。
该算法的时间复杂度为O(nlogn)
如果还是不清楚请参考:https://www.cnblogs.com/Java3y/p/8639937.html
C语言 实现该算法 代码如下:
- 直接插入排序(Straight Insertion Sort)
动态演示
这个算法也比较简答,就是先比较数列中 V[0] 和 V[1] 两个元素,如果V[0]大于V[1],那么我们就把 V[1] 赋给我们所构建的哨兵 temp ,然后在我们已经排序好的数列中从后往前去找到合适的位置,然后把元素插入到该位置。
该时间复杂度为O(n)
C语言 实现该算法 代码如下:
- 希尔排序(Shell's Sort)
动态演示
希尔排序的思想特别像在插入排序基础上得到了改进,希尔排序也成为“缩小增量排序”,其基本原理是,现将待排序的数组元素分成多个子序列,使得每个子序列的元素个数相对较少,然后对各个子序列分别进行直接插入排序,待整个待排序列“基本有序”后,最后在对所有元素进行一次直接插入排序。因此,我们要采用跳跃分割的策略:将相距某个“增量”的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。希尔排序是对直接插入排序算法的优化和升级。
该时间复杂度为O(n)
简单通俗的来讲就是在直接插入排序上多加了一个分段(对比一下直接插入排序的代码就知道了)
- 归并排序(MERGE-SORT)
动态演示
归并操作,也叫归并算法,指的是将两个已经排序的序列合并成一个序列的操作。归并排序算法依赖归并操作。简单的说,就是将一个序列不断的进行二分(当然也可以三分、多分)分裂,然后递归下去,再合并。
还是不清楚的话我们就用一张图来解释一下吧
该算法的时间复杂度为O(nlogn),空间复杂度为O(n)。
C语言 实现该算法 代码如下:
- 计数排序(Counting Sort)
动态演示
算法过程:
- 根据待排序集合中最大元素和最小元素的差值范围,申请额外空间;
- 遍历待排序集合,将每一个元素出现的次数记录到元素值对应的额外空间内;
- 对额外空间内数据进行计算,得出每一个元素的正确位置;
- 将待排序集合每一个元素移动到计算得出的正确位置上。
计数排序的时间复杂度为O(n+k)
C语言 实现该算法 代码如下:
- 基数排序(radix sort)
动态演示
就让我们举个栗子吧
设有一个初始序列为: R {50, 123, 543, 187, 49, 30, 0, 2, 11, 100}。
我们知道,任何一个阿拉伯数,它的各个位数上的基数都是以0~9来表示的。所以我们不妨把0~9视为10个桶。
我们先根据序列的个位数的数字来进行分类,将其分到指定的桶中。例如:R[0] = 50,个位数上是0,将这个数存入编号为0的桶中。分类后,我们在从各个桶中,将这些数按照从编号0到编号9的顺序依次将所有数取出来。这时,得到的序列就是个位数上呈递增趋势的序列。 按照个位数排序: {50, 30, 0, 100, 11, 2, 123, 543, 187, 49}。
接下来,可以对十位数、百位数也按照这种方法进行排序,最后就能得到排序完成的序列。
该算法的时间复杂度为O(d(n+k))
我们看这个算法的时间复杂度我们应该就可以猜出来O(d(n+k)),这个算法适用于位数不多,待排序列最大位数不是特别大,每一位数的范围不大的情况下(当然对于数字排序,每一位的范围都是[0,9])。
C语言 实现该算法 代码如下:
- 桶排序(Bucket sort)
动态演示
算法思想:
- 根据待排序集合中最大元素和最小元素的差值范围和映射规则,确定申请的桶个数;
- 遍历待排序集合,将每一个元素移动到对应的桶中;
- 对每一个桶中元素进行排序,并移动到已排序集合中。
时间复杂度:O(n+k),k为 n*logn-n*logm
举一个栗子吧
假设一组长度为20的数组
【63,157,189,51,101,47,141,121,157,156,194,117,98,139,67,133,181,13,28,109】
现在需要按5个分桶,进行桶排序,实现步骤如下:
1、找到数组中的最大值194和最小值13,然后根据桶数为5,计算出每个桶中的数据范围为
(194-13+1)/5=36.4
2、遍历原始数据,(以第一个数据63为例)先找到该数据对应的桶序列Math.floor(63 - 13) / 36.4) =1,然后将该数据放入序列为1的桶中(从0开始算)
3、当向同一个序列的桶中第二次插入数据时,判断桶中已存在的数字与新插入的数字的大小,按从左到右,从小打大的顺序插入。如第一个桶已经有了63,再插入51,67后,桶中的排序为(51,63,67) 一般通过链表来存放桶中数据,但js中可以使用数组来模拟
4、全部数据装桶完毕后,按序列,从小到大合并所有非空的桶(如0,1,2,3,4桶)
C语言 实现该算法 代码如下: