《排序算法系列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秒

 

猜你喜欢

转载自www.cnblogs.com/wangxiucai/p/12678454.html