剑指offer(Java实现)——快速排序

1、基本原理


1.1 快排基本思想:

Step 1: 对输入的数组进行shuffle,防止对输入产生依赖性,以保证随机性

Step 2:对数组进行切分,对于某个j,

  • a[j]已经排定;
  • a[lo]到a[j-1]中的所有元素都  不大于 a[j];
  • a[j+1]到a[hi]中的所有元素都 不小于 a[j]。

Step 3:然后再分别对左子数组和右子数组分别递归排序


1.2 切分算法伪代码:

private static int partition(Comparable[] a, int lo, int hi) {
        // 将数组切分成a[lo .. i-1]、a[i]、a[i+1 .. hi]三个部分
        int i = lo,j = hi + 1;      // 左右扫描指针
        Comparable v = a[lo];       // 切分元素
        while(true){
            // 扫描左右,检查扫描是否结束并交换元素
            while(less(a[++i], v)) if(i == hi) break; // 还需要检查指针i是否越界,less是a[++i] < v,即遇到 大于等于 切分元素值的元素时停下
            while(less(v, a[--j])) if(j == lo) break; // 同样需要检查指针j是否越界
            if(i >= j) break;       // 循环终止条件
            exch(a, i, j);          // 交换下标分别i、j的元素位置
        }
        exch(a, lo, j);     // 将v = a[j]放入正确的位置,注意是与j交换而不是i
        return j;           //a[lo .. j-1] <= a[j] <= a[j+1 .. hi] 达成
    }

1.3 快排伪代码:

    public static void sort(Comparable[] a) {
        shuffle(a);
        sort(a, 0, a.length - 1);
    }
    public static void sort(Comparable[] a, int lo, int hi){
        if(lo >= hi) return ;
        int j = partition(a, lo, hi);   //切分
        sort(a, lo, j - 1);         // 对左半部分a[lo .. j-1]排序
        sort(a, j + 1, hi);         //对右半部分a[j+1 .. hi]排序
    }

2、算法分析

算法的注意事项:

  1. 原地切分。若使用辅助数组,可以很容易实现切分。但是切分后的数组复制回去 和 数组额外空间开销 都会使我们得不偿失,因此直接对原数组进行切分是更好的选择。
  2. 别越界。如果切分元素v是最小或最大的那个元素,则会越界,详看切分的伪代码。
  3. 保证随机性。两种策略

          a.对初始数组进行shuffle;

          b.选择切分元素时,从数组中随机选取一个。

  4. 终止循环。快排的切分内的循环需要注意,正确地检查数组越界 和 考虑数组中可能存在元素值相同的情况。
  5. 处理切分元素值有重复的情况。左侧扫描 最好是遇到 大于等于 切分元素值的元素时停下,右侧扫描 最好是遇到 小于等于 切分元素值的元素时停下,虽然会造成一些没必要的等值元素交换,但是可以避免一些典型情况下运行时间变成平方级别。
    典型情况:假设遇到和切分元素相同值的元素时继续扫描而不是停下来,那么可证明:处理只有若干种元素值的数组时的运行时间是平方级别的。
  6. 终止递归。快排的递归终止条件

算法复杂度分析:

  • 时间复杂度

最好、平均:O(nlogn)

最坏:O(n^2),与划分算法有关

  • 空间复杂度

由于使用了递归,空间复杂度为O(logn)

3、Java实现

                     

/**
 * 是一个整型数组的快速排序的简单实现,未优化的地方有:
 * 1、未对输入数组shuffle,且选取数组第一个作为主元,因此没有消除输入的依赖性
 * 2、左侧扫描 是遇到 大于等于 切分元素值的元素停下,这样避免“处理只有若干种元素值的数组时的运行时间是平方级别的”,但是会
 * 造成不必要的相同元素值进行交换,如:所有元素值都是5
 */
public class QuickSort{

    /**
     * 此函数用于交换数组任意两个元素的位置
     * @param arr
     * @param i
     * @param j
     */
    private static void swap(int[] arr, int i, int j) {
        //健壮性判断
        if(arr == null || arr.length <= 0) {
            System.out.println("数组为空");
            return;
        }
        //交换下标分别为i和j的元素值
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

    /**
     * 此函数是快排中的划分算法,实现了一趟快速排序,以第一个元素为主元,
     * 本函数运行结束后使得主元左侧元素均小于主元,主元右侧元素大于主元。
     * @param arr   待排序的数组
     * @param start
     * @param end
     * @return 返回一趟排序后主元的下标
     */
    private static int partition(int[] arr, int start, int end) {
        int i = start,j = end + 1;
        //选择第一个元素为主元
        int key = arr[start];

        while(true) {
            // 扫描左右,检查扫描是否结束并交换元素
            while(arr[++i] < key) if(i == end) break;      // 还需要检查指针i是否越界,且遇到 大于等于 切分元素值的元素时停下
            while(key < arr[--j]) if(j == start) break;    // 同样需要检查指针j是否越界
            if(i >= j) break;               // 循环终止条件
            swap(arr, i, j);                // 交换下标分别i、j的元素位置
        }
        swap(arr, start, j);            // 将v = a[j]放入正确的位置,注意是与j交换而不是i
        System.out.println("某一趟排序结果:"+printArray(arr));
        return j;                       // a[lo .. j-1] <= a[j] <= a[j+1 .. hi] 达成

    }

    /**
     * 快速排序的递归函数
     * @param arr       待排序的数组
     * @param start     数组起始下标
     * @param end       数组结束下标
     */
    private static void QuickSort(int[] arr, int start, int end) {
        if(start >= end) return;
        int j = partition(arr, start, end);        // 切分
        QuickSort(arr, start, j - 1);        // 对左半部分a[start .. j-1]排序
        QuickSort(arr, j + 1, end);         // 对右半部分a[j+1 .. end]排序
    }

    /**
     * 此函数为快排的入口函数
     * @param arr
     */
    public static void QuickSort(int[] arr) {
        // 健壮性判断
        if(arr == null || arr.length <= 0) {
            System.out.println("数组为空");
            return;
        }
        // 通过递归进行快排
        QuickSort(arr, 0, arr.length -  1);
    }

    public static String printArray(int[] arr) {
        // 健壮性判断
        if(arr == null) {
            System.out.println("数组为空");
            return null;
        }

        StringBuffer sb = new StringBuffer();
        for(int i = 0; i < arr.length; i++){
            sb.append(arr[i] + " ");
        }
        return sb.toString();
    }
    public static void main(String[] args) {
        //测试案例
        int[] arr = {2,12,34,34,56,623,21};
        System.out.println("before sort:" +  printArray(arr));
        QuickSort(arr);
        System.out.println("after sort:" +  printArray(arr));

    }

}



猜你喜欢

转载自blog.csdn.net/u013885699/article/details/80239399