快排代码的第一句便是选取基准点,此后数据的移动根据这个基准点的大小进行调整,如果基准点选取的不好,将会导致快排的效率低下,经过测试,普通的快排算法针对(1)近乎有序的数列;(2)含有大量重复数据的数列;这两种情况时效率将会变得非常低,针对这些情况,经过适当的优化可以使快排达到很高的效率。
快排将选取的基准点经过调整放到合适的位置,之后将这个基准点左右两边的区间分别递归的进行快排,如果基准点的数据比较小,将会导致调整后基准点处于靠近两侧的位置,那么两边的区间长度将会严重失去平衡
对于一个近乎有序的数列,当直接使用第一个元素作为基准点的时候,将会导致下图的情况
如果数列非常接近有序
可以看到此时快排的递归过程生成的递归树的平衡性非常差,不能保证高度是logn,进而导致了效率变低,针对这种情况,只需改变选取基准点的方式就可以提高算法的效率。
三数取中法:指的是选取基准点之前我们可以拿出数列中间位置元素的值,将它和首尾的元素进行比较,之后将这三个数中的中间大的数交换到数列首位的位置,之后将这个数作为基准点,尽量减小之后的分区后左右两边的区间长度之差。
随机交换法:指的是选取基准点之前设计随机种子,通过随机函数得到一个在数列长度内的数,将这个随机数作为索引所指的数和第一个元素进行交换,之后将首位元素作为基准点。即随机选一个数放到首位的地方。这样一来,第一次就将最小的数交换到首位的概率是非常小的,第二次将次小的数交换到首位的概率依然非常的小。
比如随机生成一个含有15万个数据的数组,范围是从0~10,那么数组中将含有非常多的重复数据,对这个数组使用上述的快排排序时,时间几乎又是回到了O(n^2)的级别,原因如下
具体操作:从右向左扫描时,如果元素值大于基准点,则继续,否则停止,如图j所指的位置,从左向右扫面时,当元素值小于基准点,则继续,否则停止,如图i所指的位置
经过上述扫描后,i和j停在了相应的位置,此时要做的是,交换i和j分别所指的元素即可,这样一来,左边的全部都是小于等于基准点的,右边的全部都是大于等于基准点的,这样做的好处是,对于等于基准点的元素,将他们分别放到了左右的两个区间,而不是像之前那样完全放在同一边,导致区间长度不平衡。因此,通过将等于基准点的元素分配到不同区间,保证了左右区间长度尽可能平衡,提升了算法的效率
代码
经过测试,对于含有大量重复数据的数组,算法的效率基本回到了O(nlogn)的级别。
3路法同样是针对含有大量重复数列的优化,不同于之前的快排方法,3路法的思想是将数列分成3个区间,分别是小于、等于和大于基准点的区间,那么分区之后,对于等于基准点的区间内的元素,我们就不需要对其做任何处理了,只需要递归的处理小于和大于基准点的元素即可。
如图,需要多设置几个索引,橙色部分全部是小于v的数据,紫色为大于v的数据,i所指的为待处理的数据。
(1)当待处理的数据小于v时,只需将i所指的元素和等于v的区间的第一个元素交换即可,即将e换到lt之后的位置,lt++,i++
(2)i所指元素大于v时,将其换到大于基准点的区间的第一个位置即可,即交换i和gt-1的元素,gt--,i不变
(3)i所指元素等于v时,直接i++
代码