双路快速排序法

来源:互联网 发布:极度干燥 知乎 编辑:程序博客网 时间:2024/05/23 20:19

快速排序法的优化——双路快速排序

上一节我们自己动手写的一个快速排序的算法,在随机数测试中表现得非常好,然而,我们在用高度有序的数组进行测试的时候,发现快速排序的效率变得异常的低下,比归并排序的效率低得多了,近似退回了O(n^2)的复杂度,这是为什么呢?首先让我们来分析一下归并排序的算法思想,归并排序之所以能够达到O(logn)的复杂度,多亏了递归,递归使得把数组不断的二分分小分到不能分为止,然后再从小的数组开始解决,一直返回回去直到把最后一个最大的数组完成,任务就完成了,因此归并的复杂度为O(nlogn);




而相对于快速排序来说,归并排序的分类依据是从中间二分之一开始不停的分下去,这样保证了分的每段数组都是均匀的,也就是说形成的递归二叉树相对来说比较的稳定,而对于快速排序的分类方法来说,快速排序是以数组中的第一位元素为参考为来把数组分类的,因此在特殊情况(数组高度有序)的情况下,快速排序的分类可能导致数组分的并不均匀,效率将会大大降低,达不到O(nlogn)的复杂度,分出来的数组可能一边多一边少,如下图所示:


当数组完全有序的时候(极端情况),快速排序的劣势就更加明显的体现出来了:



因此,我们需要对快速排序进行优化,使其在碰到高度有序的数组时也能够轻松应对,

首先,我们分析快速排序之所以会退化的原因是数组的分配方法导致数组分类不均,因此我们需要修改快速排序的数组分类方法,尽量减少这种情况的产生,因为我们每次都以数组的第一位作为标准,这就产生了局限,因此我们只要更改数组的参考位置,不要每次都设置为数组第一位就行了,我们可以用数组中的任意一个数来作为参考值,增加其随机性来应对高度有序的数组,我们可以用rand()方法生成一个随机数作为参考数位置的下标,在把arr【l】与其进行调换,避免数组分配不均的情况,我们只需要修改_partion函数的定义即可:

int __partion(T arr[],int l,int r)//分类子操作,最后返回V处于的位置下标{    srand(time(NULL));    swap(arr[l],arr[rand()%(r-l+1)+l]);    T v=arr[l];//记录参考值的大小用来作为分类的依据

然而,即使解决了这个问题使得快速排序法面对高度有序的数组的问题时,当面对存在大量重复数字的数组时,快速排序法也会显得力不从心,分配的数组将会有一边倒的趋势:



因此,我们要换一个思路来写partion函数,之前我们都是把大于小于数统统放到一边,这一次我们采用双路快速排序法来提高效率,使其不用靠i一个变量遍历完所有的数据,我们可以增加一个变量j从数组的尾端同时遍历数组:




增加一个变量j,当i从左往右遍历数组时,碰到不符合小于V的数时停止,然后j从数组的右边开始遍历,碰到属于大于V的数时停止,此时我们只需要交换一下i.j指向的数据就可以了,然后重复i扫描,j扫描,交换两个数的操作,即使i和j所指向的数据相等时(都等于V)也会进行交换一次,防止了大量等于V的数据全部推到一边去了。我们在看一下具体的代码实现:

template<typename T>int __partion(T arr[],int l,int r)//分类子操作,最后返回V处于的位置下标{    srand(time(NULL));//随机种子    swap(arr[l],arr[rand()%(r-l+1)+l]);//随机取参考值的大小    T v=arr[l];//记录参考值的大小用来作为分类的依据    int i=l+1,j=r;    while(true)     {        while(i<=r&&arr[i]<v) i++;//之所以不加=V条件的作用是让=V的数据也进行交换        while(j>=l+1&&arr[j]>v)j--;        if(i>j) break;//i.j全部寻找完毕        swap(arr[i],arr[j]);//交换值        i++;        j--;    }    swap(arr[l],arr[j]);//把参考值提到中间来    return j;}



原创粉丝点击