排序问题的解决方案是算法问题当中最多的,常见的有插入排序,选择排序,冒泡排序,归并排序,快速排序,堆排序等,下面将对不同的排序算法进行分析。假设均为实现从小到大排序,计算空间复杂度时,不考虑原本存储元素的空间,只考虑实现算法需要的额外空间。
插入排序的基本思想是:从待排序的元素中选出一个,插入已排好序的元素队列中。重复操作直到所有元素全部被处理完。插入过程可以有两种情况:(1).若已排好序的元素存放在数组当中,要插入一个新的元素,要将已排好的元素队列中比该新元素大的整体向后移动一位,留出一个空间存放该元素。这种情况下,需要额外的空间存放排好序的队列,因此空间复杂度为O(n)。插入元素,首先需要确定插入点,然后移动插入点后元素,因此时间复杂度为O(n2)。(2).若已排好序的元素存放在链表当中,对于每个元素都需要开辟额外空间存储,因此空间复杂度为O(n)。插入元素,只要确定插入点即可,针对链表的插入是常数级操作,因此时间复杂度为O(n)。整体来说,该算法是最容易理解,也是最容易实现的。
选择排序在插入排序算法上进行了改进。从待排元素中选择最小的一个,和这个待排元素队列最前面的一个进行交换,这样该元素就比它后面的所有元素都小,待排元素就变成了该元素后面的所有元素。重复操作直到待排元素只剩下一个。该算法中,外层循环每次减小待排元素数量,内层循环寻找待排元素中最小的一个。直到遍历所有待排元素之后才能确定哪个最小,确定后才能交换。因此,该算法的时间复杂度为O(n2),由于不需要额外空间存放待排元素或已排好序的元素,因此其空间复杂度为常数级O(1)。
冒泡排序对选择排序进行改进。将待排元素队列看作是竖形排列,从下到上进行处理:两两相邻元素进行比较,如果下面的比上面的小则两者交换,直到到达顶端。这样顶端的元素就比其下面的所有元素都小,待排元素就变成了该顶端元素下面的所有元素。多次循环处理直到待排元素数只剩下一个。该算法中,外层循环每次减小待排元素数量,内层循环从底向上两两元素比较交换,根据比较结果交换。因此,该算法的时间复杂度为O(n2),同样不需要额外空间存放待排元素或已排好序的元素,因此其空间复杂度为常数级O(1)。
归并排序基本思想是:将待排元素前后分成两部分,分别进行排序,使这两部分内部是有序的,然后对这两部分进行合并。因此归并排序用到了递归调用,对待排元素队列对半分并对这两部分分别调用归并排序,因此递归的结束条件是待排元素只剩下一个。合并过程需要做的是,将两段元素队列分别拷贝进入临时存储空间,后按元素顺序合并存入原来的空间当中。根据算法,递归调用的层数为log2n,而每层都对所有的元素进行了归并的操作,操作量为O(n),因此时间复杂度为O(nlog2n)。同时由于归并过程开辟了新的空间对元素队列进行临时存储,所以空间复杂度为O(n)。
快速排序的基本思想是:以待排元素队列的第一个作为标杆元素,将所有待排元素分为前后两部分,前面部分的元素都比标杆元素小,后面部分的元素都比标杆元素大,标杆元素则放置于这两部分之间。然后对前后两部分元素分别再进行快速排序,即递归调用,调用结束条件是待排元素只剩下一个。理想情况下,每次待排元素被分成的前后两部分大小是一致的,因此递归调用层数为log2n。每层都完成了对元素队列的对半分操作,操作量为O(n),因此理想情况下时间复杂度是O(nlog2n)。极端情况下,元素本身是有序的,对半分的结果就是一半没有任何元素,这种情况下,递归调用层数为n,整体时间复杂度是O(n2)。由于对半分的过程中没有开辟额外空间,因此其空间复杂度为常数级O(1)。
堆排序采用了完全二叉树的思想,将待排序元素队列从逻辑上看做一个完全二叉树,队列从1开始编号,后依次从上到下,从左到右对应完全二叉树的每一个节点。这样得到的二叉树子节点的编号等于父节点编号的两倍或两倍加1。在此基础上构建大根堆,即对于所有节点,它要比它的两个子节点小,要比它的父节点大。建堆完成后可将堆顶元素,也就是根,与堆中最后一个节点交换元素值。堆大小减1,再次调整堆为大根堆后再做元素交换,堆大小再次减1。如此直到堆大小变为1后即完成了堆排序。构建堆的完全二叉树层数为log2n,排序过程中的调整最大深度也为log2n,需要对所有元素进行调整,所以时间复杂度为O(nlog2n),因此时间对整个过程中没有开辟额外空间,因此其空间复杂度为常数级O(1)。
对以上六种排序算法进行过程描述后可以看出,思路简单的算法时间和空间复杂度相对高,实现相对困难的算法在时间和空间复杂度都要好。在实际应用过程中,要根据实际情况选择相应的算法。如原始数据已经基本有序,就不适合选择快速排序算法,否则易出现时间复杂度为O(n2)的极端情况。
在测试中,将冒泡排序,归并排序,快速排序,堆排序以C语言代码方式实现,所测试数据通过C语言随机函数产生,规模为20000个,存放在文件中。排序程序从文件读入数据排序后再次写入到文件。通过用CENA程序评判软件运行上面四个算法程序的内存使用情况,执行所用时间情况如下:
冒泡排序:
归并排序:
快速排序:
堆排序:
可知,在时间复杂度方面,冒泡排序最差,快速排序和堆排序基本相同,而归并排序因为加入了内存申请和释放的过程,因此实验结果与理论分析存在一定的误差。空间复杂度上,归并排序所占内存明显大于其他三者,是因为归并排序在运算时大量借助临时内存完成排序。(该测评软件显示的内存使用情况包括程序运行的所有开销)
由于实验条件所限,只进行了20000个数据规模下的排序开销测试。在实际应用中,特别是大数据时代,数据规模以GB计算,不同算法在时空复杂度上的体现将会更加明显。