快速排序算法

快速排序算法被称为20世纪十大算法之一,是最重要的算法之一,是一定要掌握和熟练的!
快速排序的基本思想是:(分治法)
1.先从数列中取出一个数作为基准数。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.再对左右区间重复第二步,直到各区间只有一个数。

案例分析

假如现在我们要对这个序列,{50,10,90,30,70,40,80,60,20}进行快速排序,序列的原始状态是这样的:
这里写图片描述

关键代码


方法qSort表现了我们的算法思想,首先通过partition算出中间值,然后再对两边的值进行递归排序。
这里写图片描述

partition方法,初始时pivotkey 为50,然后进行逻辑判断。
当high指针所指向的值大于pivotkey,则一直让high指针递减,直到high指向一个比pivotkey小的数时,然后让high指针和low指针的值交换,目的就是为了把较小的值移到左边。图中20会与50交换。
当low指针指向的值小于pivotkey时,则一直让low递增,直到low指向一个比pivotkey大的数时,然后让high指针和low指针的值交换,目的就是为了把较大的值移到右边。图中low指针会移到90,然后90和50交换。

完整代码

#include <stdio.h> #include <stdlib.h> 
 #define MAXSIZE 10 
 typedef struct { int r[MAXSIZE]; int length; 
}SqList; 
 
void swap(SqList *L, int i, int j){ int temp = L->r[i]; 
    L->r[i] = L->r[j]; 
    L->r[j] = temp; 
} 
 
void initSort(SqList *L){ 
    L->length = 9; 
    L->r[0] = 0; int a[9] = {50,10,90,30,70,40,80,60,20} ; for(int i = 1; i<MAXSIZE; i++){ 
        L->r[i] = a[i-1]; 
    } 
} 
 
//算出中间值关键代码  int partition(SqList *L, int low, int high){ int pivotkey = L->r[low]; while(low < high){ while(low<high&&L->r[high] >= pivotkey){ 
            high--; 
        } 
        swap(L, low, high); while(low<high&&L->r[low] <= pivotkey){ 
            low++; 
        } 
        swap(L, low, high); 
    } return low; 
} 
 
//对无序序列进行排序  void qSort(SqList *L, int low, int high){ int mid; if(low < high){ 
        mid = partition(L, low, high);//算出中间值  
        qSort(L, low, mid-1);//对左边的表递归排序  
        qSort(L, mid+1, high);//对右边的表递归排序  
    } 
} 
 
void quickSort(SqList *L){ 
    qSort(L, 1, L->length); 
} 
 
void searchSort(SqList *L){ for(int i = 1; i < MAXSIZE; i++){ printf("%d ", L->r[i]); 
    } 
} 
 
int main(){ 
    SqList *L = new SqList; 
    initSort(L); 
    searchSort(L); printf("\n"); 
    quickSort(L); 
    searchSort(L); return 0; 
}

结果如下:

这里写图片描述

算法时间复杂度分析

在最好的情况下,第一次partition会扫描n个关键字,做n次比较,因此此时的时间复杂度为n,记为T(n)。然后,获得的中间值将数组一分为二,那么各自需要T(n/2),此时的总时间为:2 T(n/2)+n
如下:
T(n) <= 2T(n / 2) + n
T(n) <= 2(2T(n / 4) + n/2) + n = 4T(n/4) + 2n
T(n) <= 4(2T(n / 8) + n/4) + 2n = 8T(n/8) + 3n
….
T(n) <= nT(1) + (logn) * n = O(nlogn)

所以,最优情况下,快速排序的算法时间复杂度为O(nlogn)

优化

1、 发现一个问题,partition函数中,中间值pivotkey的取值是一个很值得商榷的问题,我们一开始设它为L->r[low],只是刚好L->r[low]为50,因此pivotkey的值是比较接近中间值的。但是假如在有这样一个序列,{9,1,4,8,6,5,2,3,7},那么此时pivotkey的值为9,但是9是整个序列最大值,这样我们的无用比较会做多少次啊!
我们可以用三数取中法,取三个关键字排序,再将中间值作为序列的中间值,一般取左端,中间,右端三个数。

//算出中间值关键代码  int partition(SqList *L, int low, int high){ int pivotkey; 
 //增加了优化代码  int m = low + (high - low) / 2;//中间元素的下标  if(L->r[low] > L->r[high]){ 
        swap(L, low, high);//保证左端较小  
    } if(L->r[m] > L->r[high]){ 
        swap(L, m, high);//保证中间值较小 
    } if(L->r[m] > L->r[low]){ 
        swap(L, low, m);//保证low位置的值为low,mid,high中第二大的 
    }  
    pivotkey = L->r[low]; 
 while(low < high){ while(low<high&&L->r[high] >= pivotkey){ 
            high--; 
        } 
        swap(L, low, high); while(low<high&&L->r[low] <= pivotkey){ 
            low++; 
        } 
        swap(L, low, high); 
    } return low; 
}

2、pivotekey的交换次数不必要,因为他最终都会到达中间(low

    public static int partition(int low, int high){ int privoteKey = array[low]; int back = privoteKey; while(low < high){ while(low<high && array[high] >= privoteKey){ 
                high--; 
            } //直到发现了有半部分有小于privotekey的数 //采用替换 array[low] = array[high]; while(low<high && array[low] <= privoteKey){ 
                low++; 
            } array[high] = array[low]; 
        } array[low] = back; return low; 
    }

3、快排和插入排序结合使用
当数组非常小的时候,使用快速排序反而有些大材小用了,因此递归其实也会影响性能的,所以我们可以定义一个阀值,当数组长度低于这个阀值时,就选择插入排序
经验表明,最好的组合方式是当n(子数组的长度)小于等于16时就使用插入排序

//对无序序列进行排序  void qSort(SqList *L, int low, int high){ int mid; if(high - low > MAX_LENGTH_INSERT_SORT){ 
        mid = partition(L, low, high);//算出中间值  
        qSort(L, low, mid-1);//对左边的表递归排序  
        qSort(L, mid+1, high);//对右边的表递归排序  
    } else { 
        insertSort(L); 
    } 
}
发布评论

分享到:

IT虾米网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

经典排序算法:堆排序(Heap Sort)详解
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。