快速排序的几个优化

快排代码的第一句即是选取基准点,此后数据的移动根据这个基准点的大小进行调整,若是基准点选取的很差,将会致使快排的效率低下,通过测试,普通的快排算法针对(1)近乎有序的数列;(2)含有大量重复数据的数列;这两种状况时效率将会变得很是低,针对这些状况,通过适当的优化可使快排达到很高的效率。算法

1.三数取中法和随机交换法

快排将选取的基准点通过调整放到合适的位置,以后将这个基准点左右两边的区间分别递归的进行快排,若是基准点的数据比较小,将会致使调整后基准点处于靠近两侧的位置,那么两边的区间长度将会严重失去平衡数组


对于一个近乎有序的数列,当直接使用第一个元素做为基准点的时候,将会致使下图的状况函数


若是数列很是接近有序测试


能够看到此时快排的递归过程生成的递归树的平衡性很是差,不能保证高度是logn,进而致使了效率变低,针对这种状况,只需改变选取基准点的方式就能够提升算法的效率。优化

三数取中法:指的是选取基准点以前咱们能够拿出数列中间位置元素的值,将它和首尾的元素进行比较,以后将这三个数中的中间大的数交换到数列首位的位置,以后将这个数做为基准点,尽可能减少以后的分区后左右两边的区间长度之差。ui

随机交换法:指的是选取基准点以前设计随机种子,经过随机函数获得一个在数列长度内的数,将这个随机数做为索引所指的数和第一个元素进行交换,以后将首位元素做为基准点。即随机选一个数放到首位的地方。这样一来,第一次就将最小的数交换到首位的几率是很是小的,第二次将次小的数交换到首位的几率依然很是的小。设计

2.针对数列含有大量重复元素的状况

好比随机生成一个含有15万个数据的数组,范围是从0~10,那么数组中将含有很是多的重复数据,对这个数组使用上述的快排排序时,时间几乎又是回到了O(n^2)的级别,缘由以下3d


在以前的分区算法中,因为含有大量的重复数据,将会致使分出来的左右区间如图所示,两个区间的长度比例彻底失衡,致使了算法的时间复杂度上升,针对这种状况,须要对分区操做作适当的修改,思路是将小于基准点的数所有放到左边,大于基准点的数所有放到右边,code

具体操做:从右向左扫描时,若是元素值大于基准点,则继续,不然中止,如图j所指的位置,从左向右扫面时,当元素值小于基准点,则继续,不然中止,如图i所指的位置blog

通过上述扫描后,i和j停在了相应的位置,此时要作的是,交换i和j分别所指的元素便可,这样一来,左边的所有都是小于等于基准点的,右边的所有都是大于等于基准点的,这样作的好处是,对于等于基准点的元素,将他们分别放到了左右的两个区间,而不是像以前那样彻底放在同一边,致使区间长度不平衡。所以,经过将等于基准点的元素分配到不一样区间,保证了左右区间长度尽量平衡,提高了算法的效率


代码

template<typename T>
int partion2(T k[], int l, int r) {
    swap(k[l], k[rand() % (r - l + 1) + l]);
    T temp = k[l];
    int i = l + 1, j = r;
    while (true) {
        while (i <= r && k[i] < temp) i++;
        while (j >= l && k[j] > temp) j--;
        if (i > j)
            break;
        swap(k[i], k[j]);
        i++;
        j--;
    }
    swap(k[l], k[j]);

    return j;
}

template<typename T>
void QSort2(T k[], int l, int r) {
    if (l < r) {
        int p = partion2(k, l, r);
        QSort2(k, l, p - 1);
        QSort2(k, p + 1, r);
    }
}

template<typename T>
void QuickSort2(T k[], int len) {
    srand(time(NULL));
    QSort2(k, 0, len - 1);
}

通过测试,对于含有大量重复数据的数组,算法的效率基本回到了O(nlogn)的级别。

3.3路法

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++


代码

template<typename T>
void QSort3Ways(T k[], int l, int r) {
    if (r - l <= 15) {
        InsertSort(k, l, r);
        return;
    }

    swap(k[l], k[rand() % (r - l + 1) + l]);
    T temp = k[l];
    int lt = l;                 // k[l+1...lt] < v
    int gt = r + 1;             // k[gt...r] > v
    int i = l + 1;              // k[lt+1...i) = v
    while (i < gt) {
        if (k[i] < temp) {
            swap(k[i], k[lt + 1]);
            i++;
            lt++;
        } else if (k[i] > temp) {
            swap(k[i], k[gt - 1]);
            gt--;
        } else {
            i++;
        }
    }
    swap(k[l], k[lt]);
    QSort3Ways(k, l, lt - 1);
    QSort3Ways(k, gt, r);

}

template<typename T>
void QuickSort3Ways(T k[], int len) {
    srand(time(NULL));
    QSort3Ways(k, 0, len - 1);
}