快速排序

快速排序核心思想

核心:在于选主元pivot和partition过程 以及 二分的思想

一般的快速排序是选取第一个元素作为主元,然后通过一趟排序将要排序的数据进行分组,比主元小的分到左边,比主元大的分到右边。
然后再分别对左边和右边的部分进行递归。

快速排序三个步骤

  1. 选择基准:在待排序列中,按照某种方式挑出一个元素,作为pivot
  2. 划分操作:将数组中比主元小的交换到左边,比主元大的交换到右边
  3. 递归对两个序列进行快速排序,直到序列为空或者只有一个元素

Partition

public int partition(int arr[],int start,int end){

        int index=start;//这里不能随便写!
        int pivot=arr[index];
        //使用两个指针,i和j一个从开开始扫描,一个从尾部开始扫描
        int i=start,j=end;
        //partition整体的复杂度为O(N)
        while(i!=j){// 此处仅仅是比较i是否等于j,不算时间复杂度
            //比较一半
            while(arr[j]>pivot&&i<j){
                j--;
            }
            //比较另一半
            while(arr[i]<=pivot&&i<j){
                i++;
            }
            //交换当前的i和j
            swap(arr,i,j);
        }

        if(i==j){ //i已经遇到j的
            swap(arr,index,i);
        }
        return i;
    }

选主元三种方法

第一种方法就是直接选第一个序列第一个数,begin下标的元素作为主元。
这种方法比较糟糕,因为如果数组已经有序,
每次划分只能让待排序列的序列减一。此时为最坏情况,沦为冒泡排序O(N^2)

第二种方法是随机基准元,加入随机性,这在大数据的排序中比较有效,
即我们的划分期望是比较好的,不会有特别差的情况出现。
所以随机快速排序对于大多数情况达到O(NlogN)的时间复杂度。
但是当数组全部元素都是相等的,
这时就是最坏情况。

第三种
选取中位数作为我们的主元,这是我们的最佳划分。
但是我们只能去估计,一般可以采用Median3
的方法,选择头元素和尾元素,以及一个位于中心的元素来估计中位数。
Median3消除了预排序输入的不好情况。

举例:待排序序列为:8 1 4 9 6 3 5 2 7 0

左边为:8,右边为0,中间为6

    public int median3(int[] arr,int low,int high){
        //获取序列的中间元素下标,比较三个元素的值,返回中间值的下标
        int m=(low+high)/2;
        //保证左边部分较小
        //正常 1 2 3 不用比较
        //考虑  2 1 3
        // 3 2 1
        // 2 3 1 
        // 3 1 2
        /// 1 3 2
        if(arr[low]>arr[m])
            swap(arr,low,m);
        //保证左边小,此时最左边的一定是最小的元素
        if(arr[low]>arr[high]){
            swap(arr,low,high);
        }
        //如果中间比左边小,保证左边部分较小
        if(arr[m]>arr[high]){
            swap(arr,m,high);
        }
        //将中位数换到high-1去
        //我们其实不用考虑左右两端的数,因为它们肯定已经是有序的了
        //只需要考虑A[low+1] ..A[hight-2]
        swap(arr,m,high-1); //hide pivot 
        return arr[high-1];  //high-1; 
    }

本质就是一个 将3个无序的数排序的算法(非递减)。
现在我们的m是最右边的数的前一个,即我们现在只是对low+1到 high-2进行考虑,因为pivot是high-1,pivot会在patition过程中被

快排实现

public void Sort(int[] A,int p,int r){
        int len=A.length;
        int i,j,pivot;
        pivot=median3(A,p,r);
        if(p+3<=r){ //median
            i=p;j=r-1;
            while(A[++i]<pivot){}
            while(A[--j]>pivot){}
            for(;;){
                if(i<j){
                    swap(A,i,j);
                }else{
                    break;
                }
            }
            //划分完之后,交换一下A[r-1]与A[i],因为A[i]位置的值由于median3的交换实际上是A[high-1]的值,也就是说它肯定是比pivot大的,而A[r-1]是pivot的值,所以它应该回归到中位位置! 
            swap(A,i,r-1);
            Sort(A,p,i-1);
            Sort(A,i+1,r);  
        }else{
            //1~3长度的数组采用插入排序
            InsertionSort(A);
        }
    }

性能分析

对比冒泡快排之所以快?

因为相比冒泡排序,每次交换都是跳跃式的,每次排序的时候设置一个基准点。每次交换的时候不会像冒泡一样每次只是在相邻数之间交换,它交换的距离大得多。

时间复杂度

最好情况: O(NlogN)
最坏情况: O(N^2)

空间复杂度

O(logN), 这时递归造成的空间复杂度,因为递归树的深度是logN。

稳定性:不稳定
因为关键字的比较和交换是跳跃进行的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值