快排是对冒泡排序的一种改进,其基本思想是基于分治的:在待排序表L[1…n]中任取一个元素pivot作为基准,通过一趟排序将待排序表划分为独立的两部分L[1…k-1]和L[k+1…n],使得L[1…k-1]中所有元素都小于pivot,L[k+1…n]中所有元素都大于等于pivot,则pivot放在了其最终位置L[k]上,这个过程称作一趟快速排序。而后分别递归地对两个子重复上述过程,直至每个部分内只有一个元素或空为止,即所有元素都放在了最终的位置上。

       首先假设划分算法已知,记为Partition(),返回的是上述中的k,注意到L(k)已经在最终的位置,所以可以先对表进行划分,而后对两个表调用同样的排序操作。因此可以递归调用快速排序算法进行排序,具体的程序结构如下:

void QuickSort(ElemType A[] ,int low,int high){
    if(low<high){
          //Partition()就是划分操作        int pivotpos = Partition(A,low,high);
        QuickSort(A,low,pivotpos-1);
        QuickSort(A,pivotpos+1,high);
    }
}

排序算法的关键在于划分操作,同时快速排序算法的性能也主要取决于划分操作的好坏。

下面以严版教材为例:

假设每次总以当前表中第一个元素作为枢轴值(基准)对表进行划分,则必须将表中比枢轴值大的元素向右移动,比枢轴值小的元素像左移动,使得一趟Partition操作之后,表中的元素被枢轴值一分为二。

int Partition(ElemType A[],int low,int high){
    //严版教材的划分算法 ,一趟排序过程
    ElemType pivot = A[low];    //将当前表中第一个元素设为枢轴值,对表进行划分
    while(low<high){    //循环跳出条件
        while(low<high && A[high]>=pivot) --high;
        A[low] = A[high];    //将比枢轴值小的元素移动到左端
        while(low<high &&A[low]<pivot) ++low;
        A[high]=A[low];    //将比枢轴值大的元素移动到右端
    }
    A[low] = pivot;    //枢轴值放到最终位置
    return low;    //返回存放枢轴值的最终位置
}

快速排序的性能分析:

        空间效率:由于快速排序是递归的,需要借助一个递归的工作栈来保存每一层递归调用的必要信息,其容量应与递归调用的最大深度一致。最好情况下为log2(n+1);最坏情况下,因为要奥进行n-1次递归调用,所以栈的深度为O(n);平均情况下,栈的深度为O(log2n)。因而空间复杂度在最坏的情况下为O(n),平均情况下为O(log2n)。

        时间效率:快速排序的运行时间与划分是否对称有关,而后者又与具体使用的划分算法有关。快速排序的最坏情况发生在两个区域分别包含n-1个元素和0个元素时,这种最大程度的不对称性若发生在每一次递归上,即对应于初始排序表基本有序或者基本逆序时,就得到最坏情况下的时间复杂度O(𝑛2)。

       有很多方法可以提高算法的效率。一种方法是当递归过程中划分得到的子序列的规模较小时,不要再继续递归调用快速排序,可以直接采用直接插入排序算法进行后续的排序工作。另一种方法就是尽量选取一个可以 将数据中分的枢轴元素。如从序列的头尾及中间选取三个元素,再取这三个元素的中间值作为最终的枢轴元素;或者随即将从当前表中选取枢轴元素,这样做使得最坏情况在实际排序中几乎不会发生。

        在最理想状态下,也即Partition可能做到最平衡的划分中得到两个子问题的大小都不可能大于n/2,在这种情况下,快速排序的运行速度将大大提升。此时,时间复杂度为O(nlog2𝑛)。好在快速排序平均情况下运行时间与其最佳情况下的运行时间很接近,而不是接近其最坏情况下的运行时间。快速排序是所有内部排序算法中平均性能最优的排序算法。

        稳定性:在划分算法中,若右端区间存在两个关键字相同,且均小于基准值的记录,则在交换到左端区间后,他们的位置会发生相对变化,即快速排序算法是一个不稳定的排序方法。

快排的Java代码实现

public int[] MySort (int[] arr) {
        // write code here
        if(arr.length == 0 || arr.length == 1){
            return arr;
        }
        int left = 0;
        int right = arr.length-1;
        quickSort(arr,left,right);
        return arr;
    }
   
    public void quickSort(int[] arr,int left,int right){
        if(left<right){
            int a = findA(arr,left,right);
            quickSort(arr,left,a-1);
            quickSort(arr,a+1,right);
        }
    }
   
    public int findA(int[] arr,int left,int right){
        int temp = arr[left];
        while(left<right){
            while(left<right && arr[right] >= temp){
                right--;
            }
            arr[left] = arr[right];
            while(left < right && arr[left] <= temp){
                left++;
            }
            arr[right] = arr[left];
        }
        arr[right]=temp;
        return right;
    }