快速排序的详细分析、代码实现以及如何优化(Java)

19 篇文章 0 订阅
2 篇文章 0 订阅
一、原理
  1. 从区间中取一个数据作为基准值,按照基准值将区间划分为左右两部分,其中左半部分的数据 < 基准值,右半部分的数据>基准值;
  2. 按照快排的思想排左半部分;
  3. 按快排的思想排右半部分;

在这里插入图片描述
类似于二叉树前序遍历的框架:

 public static void quickSort(int[] arr,int left, int right){
        if(right-left > 1){
            //按基准值对[left,right)区间进行分割
            int key = partion2(arr,left,right);

            //递归基准值左半侧和右半侧
            quickSort(arr,left,key);
            quickSort(arr,key+1,right);
        }
    }
二、如何进行划分?

下面我会讲三种方法来进行划分。

  • 交换的方法(每个划分都会用到,所以写在最前)
  public static  void swap(int[]arr,int left, int right){
        int temp = arr[left];
        arr[left] = arr[right];
        arr[right] = temp;
    }
  1. 方法一:进行数据划分
    在这里插入图片描述
    标注:key=arr[length-1]

1、设置两个索引 begin 和 end;

2、 begin 从前往后找,找比 key 大的数,找到后停止;

3、end 从后往前找,找比 key 小的数,找到后停止;

4、begin 位置上的元素与 end 位置上的元素进行交换;

5、最后再将 key 与 begin 位置数据进行交换(如果所指元素位置就是 key , 就不需要交换了)

 public static int partion(int[]arr,int left, int right){
            int begin = left;
            int end = right-1;
            int mid = getIndexOfMiddle(arr,left,right);//优化(后面会讲到)
            swap(arr,mid,right);
            int key = arr[end];

            while (begin<end){
                //begin
                while (begin<end && arr[begin]<=key){
                    begin++;
                }

                //end
                while (begin<end && arr[end]>=key){
                    end--;
                }

                if(begin<end){
                    swap(arr,begin,end);
                }
            }

        if(begin!=right-1){
            swap(arr,begin,key);
        }
       return begin;
    }
  1. 方法二:“挖坑法”
    在这里插入图片描述
    标注:key=arr[length-1]

1、设置两个索引 begin 和 end;

2、 begin 从前往后找,找比 key 大的数,找到后停止;【8的位置】

3、begin 去填坑 【8 → 5】,end向前走一步;

4、begin的位置则为新的坑【8】;

5、end 从后往前走,找到比 key 小的元素,找到后填坑;

6、找到的位置又为新的坑,再从 begin 开始向后找,以此类推;

7、用key填最后一个坑。

 public static int partion2(int[]arr,int left, int right){
        int begin = left;
        int end = right-1;
        int mid = getIndexOfMiddle(arr,left,right);//优化(后面会讲到)
        swap(arr,mid,right);
        int key = arr[end];

        while (begin<end){

            //begin从前往后找,找大于Key的
            while (begin<end && arr[begin] <= key){
               begin++;
            }

            //找到大于key的,用该元素填end位置的坑
            if(begin<end){
                arr[end] = arr[begin];
                end--;
            }

            //end从后往前找,找比Key小的
            while (begin<end && arr[end] >= key){
                end--;
            }

            //找到了,用该元素去填begin位置的坑
            if(begin<end){
                arr[begin] = arr[end];
                begin++;
            }
        }

        //用key填最后一个坑
             arr[begin] = key;
             return begin;
    }
  1. 方法三:前后索引
    在这里插入图片描述
    标注:key=arr[length-1]

此方法结合代码来分析,首先先看代码,如下所示

   public static int partion3(int[]arr,int left, int right){
        int cur = left;
        int pre = cur-1;
        int mid = getIndexOfMiddle(arr,left,right);//优化(后面会讲到)
        swap(arr,mid,right);
        int key = arr[right-1];

        while (cur<right){
            if(arr[cur] < key && pre++!=cur){
                swap(arr,cur,pre);
            }
            cur++;
        }

        if(pre++!=cur){
            swap(arr,pre,right-1);
        }
        return pre;
    }

1、定义两个索引:cur 和 pre ;

2、从3开始,arr[cur] < key (3 < 5满足),但是pre++!=cur(不满足) ,pre(pre++)、 cur都在3的位置上;不进入if语句。

3、cur++: cur 到8的位置上,再次进行while循环;

4、arr[cur] < key (8 不小于 5 ----不满足),直接cur++(cur到2的位置上);

5、此时 arr[cur] < key (2 < 5满足),pre++!=cur(pre在3的位置上)此条件也满足,进行交换后cur++(此时cur在6的位置上);

6、一直循环,直到跳出循环位置;

7、循环结束后,pre+1与key值进行交换即可。

注意: pre 与 cur 一直是一前一后的关系,一段时间后,二者之间有距离,且二者之间的元素都大于key值。

四、最优情况与最差情况
  • 最优:

如果每次获取到的基准值都能够将区间划分成左右两半部分,类似于一棵平衡二叉树:O(N1ogN);

  • 最差:

每次划分之后,数据都在基准值的一侧(每次拿到的基准值刚好是区间中的极值),类似于一棵单支树:O(N²);

我们要尽量避免最差情况情况出现,因此我们可以采取三数取中的方式进行优化,使得平均时间复杂度为O(N1ogN)

public  static  int getIndexOfMiddle(int[] arr,int left,int right){
        int mid = left+(right-left)>>1;

        if(arr[left] < arr[right-1]){ //a<c
            if(arr[mid] < arr[left]){ // b<a
                return left;
            }else if(arr[mid] > arr[right-1]){//b>c
                return right-1;
            }else{
                return mid;
            }
        }else{ //a>c
            if(arr[mid]> arr[left]){ //b>a
                return left;
            }else if(arr[mid]< arr[left]){
                return right-1;
            }else {
                return mid;
            }
        }
    }


采用三数取中优化之后,每次拿到极值的概率就降低,认为快排最终看的是平均复杂度:O(NlogN)

五、应用场景及优化

【应用场景】:数据量大此较随机(数据杂乱)

数据量大,将来递归深度可能比较深,每次递归都是一次函数调用,每次都需要再栈中压入一个栈帧;
栈帧:函数在运行期间要保存的中间结果-比如:函数中的局部变量参数返回值信息)

栈是有大小的,所以可能会导致栈溢出,优化递归过深可能会导致栈溢出的问题,所以我们采取插入排序优化(插入排序在这里就不写了):

public static void quickSort(int[] arr,int left, int right){
        if(right-left < 16){
           insertSort(arr,left,right);
        }else{
            //按基准值对[left,right)区间进行分割
            int key = partion2(arr,left,right);

            //递归基准值左半侧和右半侧
            quickSort(arr,left,key);
            quickSort(arr,key+1,right);
        }
    }

right-left < 16:没有让递归到区间只剩一个数据时退出,是因为递归到一定程度,区间中的数据实际慢慢的变少;

采取插入排序优化的这种方式只能将递归导致栈溢出的概率降低,不能杜绝;如果想要杜绝此问题,可采取循环的方式(可借助栈完成):
①、栈的特性:后进先出;
②、递归:先调用的后退出,后调用的先退出;

public static void quickSort2(int[] arr) {
        Stack<Integer> stack = new Stack<>();

        //相当于right left
        stack.push(arr.length);
        stack.push(0);

        while (!stack.empty()){
            int left = stack.pop();
            int right = stack.pop();

            if(right- left > 1){
                 int key =partion(arr,left,right);

            //[key+1,right)
               stack.push(right);
               stack.push(key+1);

            //[left,key)
                stack.push(key);
                stack.push(left);
         }
        }
    }

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是Java实现快速排序算法的代码,类名为QuickSort: ```java public class QuickSort { public static void main(String[] args) { int[] arr = { 10, 7, 8, 9, 1, 5 }; int n = arr.length; quickSort(arr, 0, n - 1); System.out.println("Sorted array: "); printArray(arr); } public static void quickSort(int[] arr, int low, int high) { if (low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi - 1); quickSort(arr, pi + 1, high); } } public static int partition(int[] arr, int low, int high) { int pivot = arr[high]; int i = (low - 1); for (int j = low; j <= high - 1; j++) { if (arr[j] < pivot) { i++; int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } int temp = arr[i + 1]; arr[i + 1] = arr[high]; arr[high] = temp; return (i + 1); } public static void printArray(int[] arr) { int n = arr.length; for (int i = 0; i < n; ++i) System.out.print(arr[i] + " "); System.out.println(); } } ``` 这段代码中,我们定义了一个QuickSort类,其中包含了三个方法:quickSort、partition和printArray。 quickSort方法是快速排序算法的主要部分,它通过递归的方式将数组划分为两个部分,并对每个部分进行排序。在每次递归时,我们选择数组中的一个元素作为基准元素,并将数组中小于该元素的元素放在基准元素的左侧,大于该元素的元素放在基准元素的右侧。 partition方法用于划分数组,它选择数组的最后一个元素作为基准元素,然后将数组中小于该元素的元素放在基准元素的左侧,大于该元素的元素放在基准元素的右侧。 printArray方法用于输出排序后的数组。 快速排序算法的时间复杂度为O(nlogn)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值