排序算法的时间复杂度与逆序数关系

本文尝试用逆序数的关系解释部分快速的排序算法的时间复杂度能突破O(N^2)的本质。


二更完成,已添加用逆序数解释归并算法时间复杂度低于O(N^2)的本质。

爱每个点赞的天使们。



排序算法,即将一串数据按照指定顺序进行排序,最常见到的排序算法包括插入排序,选择排序,交换排序等,这其中又有很多细分算法。衡量算法优劣的一个重要量度就是时间复杂度,在希尔算法面世之前,人们一度认为O(N^2)就是排序算法的极限了,但很显然现在看来并不对。笔者在学习排序算法时也一直很困惑,前人发明这些算法的思路到底是怎样的,为什么希尔算法的时间复杂度就可以突破O(N^2),从而达到O(N*lgN)呢?


正如文章开头所讲,排序算法即将一串数据按一定顺序进行排序,对于每一对顺序与我们期望顺序相反的数,我们称之为逆序数,而排序算法的目的就是消除这些逆序数。可以证明,对于给定的一组随机数,逆序数的数量为O(N^2),这就导致了,如果以“交换相邻元”为方法的话,那么每次操作就仅仅只消除了一对逆序数,那么所耗费时间就为O(N^2)。这也就是插入算法、冒泡算法等的时间复杂度。

如果想打破O(N^2)的时间壁垒,最直接的想法就时要做到一次交换操作可以消灭多个逆序数。希尔算法就是其中的典型。


希尔算法采用了一种“步长”的方式来优化了直接插入算法,先通过交换相距较远的数据,使每次移动一个数据,可以消灭一个及以上的逆序数,所以时间复杂度突破了O(N^2)。也正是由于逆序数的消灭取决于步长,所以希尔算法的时间复杂度也与步长挂钩,但在使用SedgeWick增量{1,5,9,41....109....}(即9*4^i-9*2^i+1或4^i-3*2^i+1)  时,即使是最坏情况,其时间复杂度也仅为O(N^4/3),平均情况下估计为O(N^7/6),已经比O(N^2)快很多了。


类似的,小于O(N^2)的算法都有同样的性质,在交换一组数的情况下,消除多对逆序数。


快速排序,虽然每次也只确定一个数的位置,但在确定这个数位置的同时,将小于其的数置于小于区,大于其的数置于大于区,解决了小于区和大于区的逆序关系。每次解除逆序数关系的量随指数递增,所以时间复杂度也小于O(N^2)。但在最差情况下,也即是如果一次快速排序确定的数位于最左或最右端,则快速排序与冒泡排序无异,其一次也处理了一个逆序关系,此时时间复杂度也上升到O(N^2)。

这时可能会有同学问了,欸,博主博主,堆排序每次也只是确定一个数啊,为什么它的时间复杂度就下降到O(N*lgN)了呢。这位同学,要学会透过现象看本质。当进行第一次堆排序时,如建立一个大根堆,实际上就是在确定每个节点的数一定大于其双子节点,已经消除了节点与子节点的逆序关系,在下一次建大根堆的时候,只是为新的根节点找一个合适的位置,原本节点与子节点的正序关系并没有变,只是做了移动处理。所以第一次建堆后,每次建堆就更新节点,然后处理该节点与子节点的逆序关系,消除的逆序关系呈指数增加。其时间复杂度也小于O(N^2)。也正是由于这个性质,堆排序也相当稳定,不受数据好坏的影响,时间复杂度稳定在O(N*lgN)。

下面我们来讲讲归并算法,其实思想很简单。归并算法就是用递归思想将多个有序表合并成一个新的有序表。假设对两个长为n的表,合并成一个长为2n的表,需要注意的是,在排序前,这两个表已经是分别有序的。我们可以把这个步骤看成将两个有序表合并成一个局部有序的无序表,再将这个无序表排序。当我们将一个无序表用直接插入排序排列成有序表的时候,用我们上面的理论,就是移动一个数只消灭了一对逆序数,而归并算法就是不停的在重复这一个操作,按理说时间复杂度也应该和直接插入排序相同。但是这也是归并的思想所在,当你将这一个无序表排列成有序的时候,此有序表下标为1的元素就和其他表中下标为2到N的数大致有序了,也就是,以平均情况来看,当移动此元素到表首,消灭了此元素与本表中的逆序关系,同时也可能消除了次元素与其他表中下标2到n的元素逆序对,也就是移动一个元素消灭多对逆序数。也就突破了时间复杂度为O(N^2)的限制。


致此,我用逆序数理论分析了部分难以理解的算法时间复杂度低于O(N^2)的原因,现在看起来这个理论其实很基础,但是在希尔算法出现之前,人们一度奉排序算法最快为O(N^2)为真理,显然每个伟大的理论的进步都是艰难而曲折的。探索新的理论总是比站在一定高度去总结经验要来的困难得多,但也只有不断总结原理,才有开拓新理论的可能,与君共勉,

你可能感兴趣的:(c语言自学)