zoukankan      html  css  js  c++  java
  • 《排序算法系列5》快速排序

    1 思想

    快速排序(Quick Sort)使用分治法策略。 
    它的基本思想是:选择一个基准数,通过一趟排序将要排序的数据分割成独立的两部分;其中一部分的所有数据都比另外一部分的所有数据都要小。然后,再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

    快速排序流程:
    (1) 从数列中挑出一个基准值。
    (2) 将所有比基准值小的摆放在基准前面,所有比基准值大的摆在基准的后面(相同的数可以到任一边);在这个分区退出之后,该基准就处于数列的中间位置。
    (3) 递归地把"基准值前面的子数列"和"基准值后面的子数列"进行排序。

    2 思路

    1.  选中第1个数6为基准,然后遍历后面的数,将比6大的数放到6的右边,比6小的数放到6的左边,分成2组
    2.  将上面6左边的数当成一个数组,在选出一个基数为3,将比3大的数放到3的右边,比3小的数放到3的左边,在分成2组
    3.   然后在选出一个基础  依次类推

    快速排序动态演示

    4 代码实现(通过递归实现)

      //快速排序
        public static void main(String[] args) {
            int[] arr = {6,2,1,7,9,3,4,5,10,8};
            quickSort(arr, 0, arr.length-1);
            System.out.println(Arrays.toString(arr));
        }
        
        /**
           *   数组排序方法
         * @param arr 需要排序的方法
         * @param left 需要排序的左起点
         * @param right 需要排序的右起点
         * void
         */
        public static void quickSort(int[] arr, int left, int right) {
            if (left > right) {
                return;
            }
            // 定义两个指针 用于移动
            int start = left;// 起点下表
            int end = right;// 终点下标
            int base = arr[left];// 把第一个点作为基准点
            int temp;// 下面帮助切换的辅助节点
            while (start < end) {
                while (start < end && arr[end] >= base) {// 右指针先走,找到小于基准数的停止
                    end--;
                }
                while (start < end && arr[start] <= base) {// 左指针后走,找到大于基准数的停止
                    start++;
                }
                //当上面的循环结束时,此时证明找到了start和end的值 这是交换即可
                if (start < end) {
                    temp = arr[start];
                    arr[start] = arr[end];
                    arr[end] = temp;
                }
            }
            //如果上面循环结束,证明 start 和 end 已经碰到了一起 此时start = end
            //这时候将基准数和start和end碰到的位置交换
            arr[left] = arr[start];
            arr[start] = base;
            
            //使用递归
            //对基准数左边的数进行排序
            quickSort(arr, left, start - 1);
            //对基准数右边的数进行排序
            quickSort(arr, start + 1, right);
        }

    5 快速排序时间复杂度 

      时间复杂度

              快速排序涉及到递归调用,所以该算法的时间复杂度还需要从递归算法的复杂度开始说起;

             递归算法的时间复杂度公式:T[n] = aT[n/b] + f(n)  ;对于递归算法的时间复杂度这里就不展开来说了;
       最优情况下时间复杂度
              快速排序最优的情况就是每一次取到的元素都刚好平分整个数组(很显然我上面的不是);
              此时的时间复杂度公式则为:T[n] = 2T[n/2] + f(n);T[n/2]为平分后的子数组的时间复杂度,f[n] 为平分这个数组时所花的时间;
              下面来推算下,在最优的情况下快速排序时间复杂度的计算(用迭代法):
                                             T[n] =  2T[n/2] + n                                                                     ----------------第一次递归

                     令:n = n/2        =  2 { 2 T[n/4] + (n/2) }  + n                                               ----------------第二次递归

                                                =  2^2 T[ n/ (2^2) ] + 2n

                    令:n = n/(2^2)   =  2^2  {  2 T[n/ (2^3) ]  + n/(2^2)}  +  2n                         ----------------第三次递归  

                                                =  2^3 T[  n/ (2^3) ]  + 3n

                    ......................................................................................                        

                    令:n = n/(  2^(m-1) )    =  2^m T[1]  + mn                                                  ----------------第m次递归(m次后结束)

                   当最后平分的不能再平分时,也就是说把公式一直往下跌倒,到最后得到T[1]时,说明这个公式已经迭代完了(T[1]是常量了)。

                   得到:T[n/ (2^m) ]  =  T[1]    ===>>   n = 2^m   ====>> m = logn;

                   T[n] = 2^m T[1] + mn ;其中m = logn;

                   T[n] = 2^(logn) T[1] + nlogn  =  n T[1] + nlogn  =  n + nlogn  ;其中n为元素个数

                   又因为当n >=  2时:nlogn  >=  n  (也就是logn > 1),所以取后面的 nlogn;

                   综上所述:快速排序最优的情况下时间复杂度为:O( nlogn )

      最差情况下时间复杂度

               最差的情况就是每一次取到的元素就是数组中最小/最大的,这种情况其实就是冒泡排序了(每一次都排好一个元素的顺序) 

               这种情况时间复杂度就好计算了,就是冒泡排序的时间复杂度:T[n] = n * (n-1) = n^2 + n;

               综上所述:快速排序最差的情况下时间复杂度为:O( n^2 ) 

        平均时间复杂度

             快速排序的平均时间复杂度也是:O(nlogn)

      空间复杂度

              其实这个空间复杂度不太好计算,因为有的人使用的是非就地排序,那样就不好计算了(因为有的人用到了辅助数组,所以这就要计算到你的元素个数了);我就分析下就地快速排序的空间复杂度吧; 
              首先就地快速排序使用的空间是O(1)的,也就是个常数级;而真正消耗空间的就是递归调用了,因为每次递归就要保持一些数据; 
              最优的情况下空间复杂度为:O(logn)  ;每一次都平分数组的情况 
              最差的情况下空间复杂度为:O( n )      ;退化为冒泡排序的情况 

    6 快速排序速度测试

     1   //快速排序
     2     public static void main(String[] args) {
     3         speedTest(80000);
     4     }
     5     /**
     6        *  创建一个随机数组 然后调用排序方法  得到时间
     7      * @param number 创建的随机数组个数
     8      */
     9     public static void speedTest(int number) {
    10         int[] arr = new int[number];
    11         for (int i = 0; i < arr.length; i++) {
    12             arr[i] = (int) (Math.random() * 800000);
    13         }
    14 
    15         SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    16         Date date1 = new Date();
    17         String time1 = simpleDateFormat.format(date1);
    18         System.out.println("排序前的时间为:" + time1);
    19         
    20         //调用上面的快速排序方法
    21         quickSort(arr, 0, arr.length - 1);
    22 
    23         Date date2 = new Date();
    24         String time2 = simpleDateFormat.format(date2);
    25         System.out.println("排序后的时间为:" + time2);
    26     }

     速度测试结果

    8万个数据通过插入排序大约需要不到1秒

    80万个数据通过插入排序大约需要不到1秒

    800万个数据通过插入排序大约需要1秒

     8000万个数据通过插入排序大约需要13秒

     

  • 相关阅读:
    Java常见的10个异常
    HashMap 和 Hashtable 的 6 个区别
    JSP随记
    Java> ArrayList的扩容因子为什么是1.5?
    Java> 有趣的byte及位运算
    前导0计数Integer.numberOfLeadingZeros,后缀0计数Integer.numberOfTailingzeros
    结构型模式
    结构型模式
    结构型模式
    结构型模式
  • 原文地址:https://www.cnblogs.com/wangxiucai/p/12678454.html
Copyright © 2011-2022 走看看