六、快速排序(Quick Sort)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_21918021/article/details/89095551

六、快速排序(Quick Sort)

快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

算法描述

快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:

  • 从数列中挑出一个元素,称为 “基准”(pivot);
  • 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
  • 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

算法分析

最佳情况:T(n) = O(nlogn) 最差情况:T(n) = O(n2) 平均情况:T(n) = O(nlogn)

动图展示

在这里插入图片描述

代码实现

1、普通快速排序

缺点:

  • 在近乎有序的数组下,该快排比归并要慢很多。
  • 因为每次排序后,左右两个子递归规模相差悬殊,构成的二叉树平衡因子没有归并好。
  • 在完全有序时,退化为O(n^2)
	public static void quickSort(int[] arr, int n) {
        __quickSort(arr,0,n-1);
        System.out.println(Arrays.toString(arr));
    }

    private static void __quickSort(int[] arr, int left, int right) {
        //首先处理递归到底的情况,左边大于右边
        //可以优化,如果对于小数组时,可以用插入排序完成
        if(left>=right)
            return;
        int p= partition(arr,left,right);

        //对arr[l,,p-1]和arr[p+1,,,r]递归,每次比基准值小的在左,大的在右
        __quickSort(arr, left, p-1);
        __quickSort(arr, p+1, right);
    }

    //每次以第一个元素季即arr[l]为基准,比基准值小的在右,大的在左,
    //返回基准值最终在数组中的位置
    private static int partition(int[] arr, int left, int right) {
        // 设定基准值(pivot)
        int pivot=arr[left];
        int j=left;
        for(int i=left+1;i<=right;i++) {
            //每次循环将小于v的往前换
            if(arr[i]<pivot) {
                swap(arr,i,j+1);
                j++; //arr[j+1]始终是>pivot的,arr[j]是最后一个<=pivot的

            }
        }
        //再将基准值移动到中间
        swap(arr,left,j);
        //最终j所指的位置就是中间值
        return j;
    }
    //可以用数组来作为引用
    private static void swap(int[] arr, int i, int j) {
        if(i!=j) {
            int temp =arr[j];
            arr[j]=arr[i];
            arr[i]=temp;
        }
    }

2、快排优化方法一【随机基准】

优化:

  • 将第一个元素为基准值改为随机一个元素为基准值。
  • 降低退化为O(n^2)的概率。
  • 数学期望值为O(nlogn)
	public static void quickSort2(int[]arr,int n) {
        __quickSort2(arr,0,n-1);
        System.out.println(Arrays.toString(arr));
    }

    private static void __quickSort2(int[] arr, int left, int right) {
        if(left>=right)
            return;
        int p= partition(arr,left,right);
        __quickSort2(arr, left, p-1);
        __quickSort2(arr, p+1, right);
    }

    private static int partition(int[] arr, int left, int right) {
        //double类型,产生一个l到r的随机数作为数组基准值下标
        //将随机数与数组第一个数交换,即让随机数作为基准值
        //减小近乎有序的概率
        swap(arr, left, (int)Math.random()*(right-left+1)+left);
        int pivot=arr[left];
        int j=left;
        for(int i=left+1;i<=right;i++) {
            if(arr[i]<pivot) {
                swap(arr,i,j+1);
                j++;
            }
        }
        swap(arr,left,j);
        return j;
    }

    private static void swap(int[] arr, int i, int j) {
        if(i!=j) {
            int temp =arr[j];
            arr[j]=arr[i];
            arr[i]=temp;
        }
    }

3、快排优化方法二【两路快排】

对于方一方二中如果存在大量重复元素,当基准值为重复元素时。等于base的这些元素会聚集到右侧(或者稍微改改大小关系就会聚集到左侧)。 总之就会聚集到一边。这样在数组中重复数字很多的时候, 就又会导致两边子递归规模差距悬殊的情况,很有可能退化为O(n^2)
这时想把等于base的那些数分派到base两边,而不是让他们聚集到一起。

优化:

  • 可以将swap去掉
	public static void quickSort3(int[]arr,int n) {
        __quickSort3(arr,0,n-1);
        System.out.println(Arrays.toString(arr));
    }

    private static void __quickSort3(int[] arr, int l, int r) {
        if(l>=r)
            return;
        int p= partition(arr,l,r);
        __quickSort3(arr, l, p-1);
        __quickSort3(arr, p+1, r);

    }

    private static int partition(int[] arr, int left, int right) {
        swap(arr,left,(int)Math.random()*(right-left+1)+left);
        //满足arr[l+1,i)<=v,arr(j,r]>=v
        int pivot=arr[left];
        int i=left+1,j=right;
        while(true) {
            //从左到右扫描,扫描出第一个比base大的元素,然后i停在那里
            while(arr[i]<pivot && i<right)//arr[i]不能=pivot,会导致v聚集在一边
                i++;
            //从右到左扫描,扫描出第一个比base小的元素,然后j停在那里
            while(arr[j]>pivot && j>=left)
                j--;
            if(i>=j)
                break;
            swap(arr, i, j);
            i++;
            j--;
        }
        //将基准值交换到合适位置
        swap(arr, left, j);
        return j;
    }
    private static void swap(int[] arr, int i, int j) {
        if(i!=j) {
            int temp=arr[i];
            arr[i]=arr[j];
            arr[j]=temp;
        }
    }

4、快排优化方法三【三路快排】

优化:

  • 当大量数据,且重复数多时,用三路快排。将整个数组分为小于v,等于v,大于v三部分。
	public static void quickSort4(int[]arr,int n) {
        __quickSort4(arr,0,n-1);
        System.out.println(Arrays.toString(arr));
    }


    private static void __quickSort4(int[] arr, int left, int right) {
        if(left>=right)
            return;
        //因为==pivot的部分是一个数组而非单独的一个数,所以直接处理,不调用函数
        swap(arr,left,(int)Math.random()*(right-left+1)+left);
        int pivot=arr[left];
        //变量定义要保证初始空间为空
        int i=left+1;//arr[lt+1,i)==v
        int lt=left;//arr[l+1,lt]<v
        int gt=right+1;//arr[gt,r]>v
        while (i<gt) {
            if(arr[i]==pivot) {
                i++;
            }else if (arr[i]<pivot) {
                swap(arr, i, lt+1);//画示意图,arr[lt+1]==pivot
                i++;
                lt++;
            }else {
                swap(arr, i, gt-1);//arr[gt-1]为未处理的数据
                gt--;
            }
        }
        swap(arr, left, lt);
        __quickSort4(arr, left, lt-1);
        __quickSort4(arr, gt, right);
    }
    private static void swap(int[] arr, int i, int j) {
        if(i!=j) {
            int temp=arr[i];
            arr[i]=arr[j];
            arr[j]=temp;
        }
    }

猜你喜欢

转载自blog.csdn.net/qq_21918021/article/details/89095551