快速排序的简单分析

快速排序有很多版本,这里给出一种基础版本和一种典型加强版本,即三数取中版本.

基础版本


public class QuickSort {
    public static void quickSort(int[] a) {
        quickSort(a,0,a.length-1)
    }

    private static void quickSort(int[] a, int left, int right) {
        if (left >= right) {
            return;
        }

        int CUTOFF = partition(a, left, right,right);
        quickSort(a, left, CUTOFF - 1);
        quickSort(a, CUTOFF + 1, right);
    }
    private static int partition(int[] a, int left,int right,int pivot){
        int leftPtr=left-1;
        int rightPtr=right;
        int pivotVal = a[pivot];
        while (true) {
            while(a[++leftPtr]<pivotVal){}
            while(rightPtr>0&&a[--rightPtr]>pivotVal){}

            if (leftPtr >= rightPtr) {
                break;
            } else {
                exchange(a,leftPtr, rightPtr);
            }
        }
        exchange(a,leftPtr,right);
        return leftPtr;

    }

    private static void exchange(int[] a ,int x, int y) {
        int tmp = a[x];
        a[x] = a[y];
        a[y]=tmp;
    }
}

三数取中


public class QuickSort_median3 {
    public static void quickSort(int[] a) {
        quickSort(a, 0, a.length - 1);
    }

    private static void quickSort(int[] a, int left, int right) {

        int size=right-left+1;
        if (size <= 3) {
            manualSort(a,left, right);
            return;
        }
        int median=median3(a,left,right);
        int CUTOFF = partition(a, left, right, median);
        quickSort(a, left, CUTOFF - 1);
        quickSort(a, CUTOFF + 1, right);
    }

    private static void manualSort(int[] a,int left, int right) {
        int size=right-left+1;
        if (size <= 1) {
            return;
        }
        if (size == 2) {
            if (a[left] > a[right]) {
                exchange(a, left, right);
            }
        } else {
            if (a[left] > a[right - 1]) {
                exchange(a, left, right - 1);
            }
            if ((a[left] > right)) {
                exchange(a, left, right);
            }
            if (a[right - 1] > right) {
                exchange(a,a[right - 1], a[right]);
            }
        }
    }

    private static int partition(int[] a, int left, int right, int pivot) {
        int leftPtr=left;
        int rightPtr=right-1;
        int pivotVal = a[pivot];
        while (true) {
            while(a[++leftPtr]<pivotVal){}
            while(a[--rightPtr]>pivotVal){}
            if (leftPtr >= rightPtr) {
                break;
            } else {
                exchange(a,leftPtr,rightPtr);
            }

        }
        exchange(a,leftPtr,right-1);
        return leftPtr;
    }


    private static int median3(int[] a,int left, int right){
        int center = (left + right) / 2;
        if (a[left] > a[center]) {
            exchange(a, left, center);
        }
        if (a[left] > a[right]) {
            exchange(a, left, right);
        }
        if (a[center] > a[right]) {
            exchange(a, center, right);
        }
        exchange(a,center,right-1);
        return right-1;
    }
    private static void exchange(int[] a ,int x, int y) {
        int tmp = a[x];
        a[x] = a[y];
        a[y]=tmp;
    }

}

快速排序的难点不在于理解,而在于代码实现.
核心步骤是:
1. 指定某个值,
2. 把数组中所有小于这个数的数放在左边
3. 把数组中所有大于这个数的数放在右边
4. 对于左边的数组重复步骤12345
5. 对于右边的数组重复步骤12345

而在代码中有很多细节需要注意.

基础版快排

首先要有一个驱动程序,穿入一左一右两个指针

    public static void quickSort(int[] a) {
        quickSort(a,0,a.length-1);//快排驱动,传入位置.
    }

直接选取最右的位置的值作为枢纽,写出快排框架:

    private static void quickSort(int[] a, int left, int right) {
        if (left >= right) {
            return;
        }

        int CUTOFF = partition(a, left, right,right);//以最右位为枢纽.
        quickSort(a, left, CUTOFF - 1);
        quickSort(a, CUTOFF + 1, right);
    }

注意,此段程序为递归程序,所以参数必须为可变的.不能再内部指定left,right.
合格的递归应有恰当的跳出条件.左指针大于右指针的时候需要结束递归.
事实上partition方法承担了大部分任务.它负责内部的跳跃交换,最后返回归位后的枢纽位置.最后一个参数是初始枢纽,直接给为right.
partition(a, left, right,right)
左递归,右递归.

quickSort(a, left, CUTOFF - 1);
quickSort(a, CUTOFF + 1, right);

接下来是分割程序.分割承担了内部的跳跃任务,细节不太容易准确写对.

    private static int partition(int[] a, int left,int right,int pivot){
        int leftPtr=left-1;//由于下面的代码是先右移再判断,为了保证左边从第一个开始判断,故左指针提前向左多移动一位,
        int rightPtr=right;//虽然右指针是先左移再判断,但是由于最右是被选取的枢纽,所以无需参与.直接从right-1开始向左即可.
        int pivotVal = a[pivot];
        while (true) {
            while(a[++leftPtr]<pivotVal){}//左指针与枢纽比较,尽可能右移.由于是先右移再比较,所以最后落在大于等于枢纽值的位置上.一定不会右越界,因为最右是枢纽.
            while(rightPtr>0&&a[--rightPtr]>pivotVal){}//右指针与枢纽比较,尽可能左移.由于先左移后比较,故只要right>0即可保证right-1>=0,不会左越界.最后落在小于等于pivot的位置.

            if (leftPtr >= rightPtr) {
                break;
            } else {
                exchange(a,leftPtr, rightPtr);//交换左右.
            }
        }
        exchange(a,leftPtr,right);//枢纽归位.
        return leftPtr;

    }

三数取中版快排

三数分割法在选取枢纽的环节做了优化,使平均效率更高.虽然代码量有所增加,但是反而使分割环节的细节好记了一些.

在基础版中,选取枢纽的方法是直接任命right.而如果right并非约等于中值而是约等于最大值或最小值,将会有最差O(N^2)的时间复杂度.而三数分割法大大降低了这样的可能性.
三数取中过程:

    private static int median3(int[] a,int left, int right){
        int center = (left + right) / 2;//算出中间位置
        //将三个位置排序.此处正面影响了分割过程.使得两端的值无需参与比较,一定是左<=枢纽<=右.
        if (a[left] > a[center]) {
            exchange(a, left, center);
        }
        if (a[left] > a[right]) {
            exchange(a, left, right);
        }
        if (a[center] > a[right]) {
            exchange(a, center, right);
        }
        exchange(a,center,right-1);//最后把枢纽值置于倒数第二个位置.
        return right-1;//返回枢纽位置.
    }

分割过程:

    private static int partition(int[] a, int left, int right, int pivot) {
        int leftPtr=left;
        int rightPtr=right-1;
        int pivotVal = a[pivot];
        while (true) {
            while(a[++leftPtr]<pivotVal){}
            while(a[--rightPtr]>pivotVal){}
            if (leftPtr >= rightPtr) {
                break;
            } else {
                exchange(a,leftPtr,rightPtr);
            }

        }
        exchange(a,leftPtr,right-1);
        return leftPtr;
    }

与基础版类似,但是由于最左,最右,右倒2都已经被占据且排好序,故在指定比较指针的时候要稍稍改变.另外,由于左右两端都已经left<=pivotVal<=right,所以内部while循环时无需再判断边界,一定不会越界.

当数值小于等于3的时候,为了效率要求,我们单独为3个数写出排序.

    private static void manualSort(int[] a,int left, int right) {
        int size=right-left+1;
        if (size <= 1) {
            return;
        }
        if (size == 2) {
            if (a[left] > a[right]) {
                exchange(a, left, right);
            }
        } else {
            if (a[left] > a[right - 1]) {
                exchange(a, left, right - 1);
            }
            if ((a[left] > right)) {
                exchange(a, left, right);
            }
            if (a[right - 1] > right) {
                exchange(a,a[right - 1], a[right]);
            }
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值