• 【重温基础算法】内部排序之快速排序法


    内部排序之快速排序


    对冒泡排序的一种优化

    主要思想

    快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。

    通过一趟排序将待排记录分割成独立的两个部分,一部分的关键字比另一部分的关键字小,则可以分别对两部分记录继续进行排序。

    1. 选取任务选取的一个记录,作为枢轴或(支点)(pivot)
    2. 重新排列记录,所有关键字比pivot值小的摆放在pivot前面,所有关键字比pivot值大的摆在pivot的后面(相同的数可以到任一边)。在这个分区退出之后,该pivot就处于数列的中间位置。这个称为分区(partition)操作;
    3. 重复执行步骤2。把小于pivot关键字的子数列和大于pivot关键字的子数列排序。

    过程演示

    在这里插入图片描述

    JAVA代码

    package sort;
    
    public class QuickSort {
    
        private static int count = 1;
    
        public static void main(String[] args) {
            int[] o = {49, 38, 45, 27, 46, 13, 27, 8};
            System.out.print("排序前: ");
            for (int t : o) {
                System.out.print(t);
                System.out.print(" ");
            }
            System.out.println();
    
            // 算法部分
            int left = 0;
            int right = o.length - 1;
    
            quickSort(o, left, right);
    
            System.out.print("排序后: ");
            for (int t : o) {
                System.out.print(t);
                System.out.print(" ");
            }
            System.out.println();
    
        }
    
        private static void quickSort(int[] arr, int left, int right) {
            if (left < right) {
                int partitionIndex = partition(arr, left, right);
                quickSort(arr, left, partitionIndex - 1);
                quickSort(arr, partitionIndex + 1, right);
            } else {
                count++;
            }
        }
    
        private static int partition(int[] arr, int left, int right) {
            // left位置的记录就是pivot,index代表比pivot小的下标
            int index = left + 1;
            System.out.print("第" + count + "趟pivot=" + arr[index - 1] + ",排序后: ");
    
            for (int i = index; i <= right; i++) {
                if (arr[i] < arr[left]) {
                    swap(arr, i, index);
                    index++;
                }
            }
            swap(arr, left, index - 1);
            for (int t : arr) {
                System.out.print(t);
                System.out.print(" ");
            }
            System.out.println();
            count++;
            return index - 1;
        }
    
        private static void swap(int[] arr, int i, int j) {
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    执行结果

    排序前: 49 38 45 27 46 13 27 8 
    第1趟pivot=49,排序后: 8 38 45 27 46 13 27 49 
    第2趟pivot=8,排序后: 8 38 45 27 46 13 27 49 
    第4趟pivot=38,排序后: 8 27 27 13 38 45 46 49 
    第5趟pivot=27,排序后: 8 13 27 27 38 45 46 49 
    第8趟pivot=45,排序后: 8 13 27 27 38 45 46 49 
    排序后: 8 13 27 27 38 45 46 49 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    算法分析

    时间复杂度分析

    经过上述一趟快速排序,我们只确定了一个元素的最终位置,我们最终需要经过 n n n趟快速排序才能将一个含有 n n n个数据元素的序列排好序,下面我们来分析其时间复杂度.

    n n n 为待排序数组中的元素个数, T ( n ) T(n) T(n)为算法需要的时间复杂度则
    T ( n ) = { D ( 1 ) n ≤ 1 D ( n ) + T ( I 1 ) + T ( I 2 ) n > 1 T(n)= {D(1)n1D(n)+T(I1)+T(I2)n>1 T(n)={D(1)D(n)+T(I1)+T(I2)n1n>1
    其中 D ( n ) = n − 1 D(n)=n−1 D(n)=n1 ,是一趟快排需要的比较次数,一趟快排结束后将数组分成两部分 I 1 I_1 I1 I 2 I_2 I2

    最好时间复杂度

    最好的情况是,每一次划分都正好将数组分成长度相等的两半
    T ( n ) = { D ( 1 ) n ≤ 1 D ( n ) + T ( n 2 ) + T ( n 2 ) n > 1 T(n)= {D(1)n1D(n)+T(n2)+T(n2)n>1 T(n)={D(1)D(n)+T(2n)+T(2n)n1n>1

    ∴ T ( n ) = D ( n ) + 2 T ( n 2 ) = D ( n ) + 2 D ( n 2 ) + 2 T ( n 4 ) . . . = D ( n ) + 2 D ( n 2 ) + 4 D ( n 4 ) + . . . + 2 k D ( n 2 k ) = n − 1 + 2 ( n 2 − 1 ) + 4 ( n 4 − 1 ) + . . . + 2 k ( n 2 k − 1 ) = ( n − 1 ) + ( n − 2 ) + ( n − 4 ) + . . . + ( n − 2 k )    ⟹    ∑ k = 1 n n − 2 k = k n − 2 k + 1 ∵ k = l o g 2 n ∴ 原式 = n l o g 2 n − 2 n + 1    ⟹    O ( n l o g 2 n ) T(n)=D(n)+2T(2n)=D(n)+2D(2n)+2T(4n)...=D(n)+2D(2n)+4D(4n)+...+2kD(2kn)=n1+2(2n1)+4(4n1)+...+2k(2kn1)=(n1)+(n2)+(n4)+...+(n2k)k=1nn2k=kn2k+1k=log2n原式=nlog2n2n+1O(nlog2n)

    最坏时间复杂度

    最坏情况下,每一次划分都将数组分成了 0 0 0 n − 1 n-1 n1两部分
    T ( n ) = { D ( 1 ) n ≤ 1 D ( n ) + T ( n − 1 ) + T ( 0 ) n > 1 T(n)= \begin{cases} \vcenter D(1) &{n \leq 1} \\ D(n)+T(n-1)+T(0) &{n >1 } \end{cases} T(n)={D(1)D(n)+T(n1)+T(0)n1n>1

    ∴ T ( n ) = D ( n ) + 2 T ( n − 1 ) = D ( n ) + D ( n − 1 ) + T ( n − 2 ) . . . = D ( n ) + D ( n − 1 ) + D ( n − 2 ) + . . . + D ( 1 ) = ( n − 1 ) + ( n − 2 ) + ( n − 3 ) + . . . + 0    ⟹    ∑ k = 0 n k = n ( n − 1 ) 2 原式 = n ( n − 1 ) 2    ⟹    O ( n 2 ) \begin{aligned} \therefore T(n)&=D(n)+2T(n-1) \\ &=D(n)+D(n-1)+T(n-2) \\ &.\\ &.\\ &.\\ &=D(n)+D(n-1)+D(n-2)+...+D(1) \\ &=(n-1)+(n-2)+(n-3)+...+0 \\ &\implies \displaystyle \sum_{k=0}^{n}k=\frac{n(n-1)}{2} \\ 原式&=\frac{n(n-1)}{2} \implies \boxed{O(n^2)}\\ \end{aligned} T(n)原式=D(n)+2T(n1)=D(n)+D(n1)+T(n2)...=D(n)+D(n1)+D(n2)+...+D(1)=(n1)+(n2)+(n3)+...+0k=0nk=2n(n1)=2n(n1)O(n2)

    平均时间复杂度

    任意一种划分情况出现的概率都相等
    A l l = { I 1 = 0 I 2 = n − 1 I 1 = 1 I 2 = n − 2 . . . I 1 = n − 1 I 2 = 0 All= \begin{cases} I_1=0 & I_2=n-1 \\ I_1=1 & I_2=n-2 \\ . \\ . \\ . \\ I_1=n-1 & I_2=0 \\ \end{cases} All= I1=0I1=1...I1=n1I2=n1I2=n2I2=0

    ∴ T a v g ( n ) = D ( n ) + 1 n ∗ ∑ i = 0 n − 1 T a v g ( i ) + T a v g ( n − i ) = D ( n ) + 2 n ∗ ∑ i = 0 n − 1 T a v g ( i ) T a v g ( n − 1 ) = D ( n − 1 ) + 2 n − 1 ∗ ∑ i = 0 n − 2 T a v g ( i ) n T a v g ( n ) − ( n − 1 ) T a v g ( n − 1 ) = n D ( n ) + 2 ∑ i = 0 n − 1 T a v g ( i ) − ( n − 1 ) D ( n − 1 ) − 2 ∗ ∑ i = 0 n − 2 T a v g ( i ) = n D ( n ) − ( n − 1 ) D ( n − 1 ) + 2 T a v g ( n − 1 ) = n ( n − 1 ) − ( n − 1 ) ( n − 2 ) + 2 T a v g ( n − 1 ) = 2 ( n − 1 ) + 2 T a v g ( n − 1 ) 移项得 n T a v g ( n ) = 2 ( n + 1 ) + 2 T a v g ( n − 1 ) + ( n − 1 ) 2 T a v g ( n − 1 ) = 2 ( n + 1 ) + ( n + 1 ) 2 T a v g ( n − 1 ) 等式两边同时除以 n ( n + 1 ) 得 T a v g ( n ) n + 1 = T a v g ( n − 1 ) n + 2 ( n − 1 ) n ( n + 1 ) 令 B ( n ) = T a v g ( n ) n + 1 得 B ( n ) = B ( n − 1 ) + 2 ( n − 1 ) n ( n + 1 ) = B ( n − 2 ) + 2 ( n − 2 ) n ( n − 1 ) + 2 ( n − 1 ) n ( n + 1 ) . . . = B ( 1 ) + ∑ i = 1 n 2 ( i − 1 ) i ( i + 1 ) = B ( 1 ) + ∑ i = 1 n 2 ( i + 1 ) − 4 i ( i + 1 ) = ∑ i = 1 n [ 2 i − 4 i ( i + 1 ) ] ∵ ∑ i = 1 n 4 i ( i + 1 ) = 4 ∑ i = 1 n [ i + 1 − i i ( i + 1 ) ] = 4 ∑ i = 1 n [ 1 i − 1 i ( i + 1 ) ] = 4 [ 1 − 1 n + 1 ] = 4 n n + 1 ∵ ∑ i = 1 n 1 i ≈ 0.577216+ln(n) ∴ B ( n ) = 2 ∗ 0.577216 + 2 ∗ l n ( n ) + 4 n n + 1 代回 B ( n ) = T a v g ( n ) n + 1 T a v g ( n ) = 2 ( n + 1 ) l n ( n ) − 4 n + 2 ∗ 0.577216 ( n + 1 )    ⟹    O ( n l o g 2 n ) \begin{aligned} \therefore T_{avg}(n)&=D(n)+\frac{1}{n}*\displaystyle \sum_{i=0}^{n-1}T_{avg}(i)+T_{avg}(n-i) \\ &=D(n)+\frac{2}{n}*\displaystyle \sum_{i=0}^{n-1}T_{avg}(i) \\ T_{avg}(n-1)&=D(n-1)+\frac{2}{n-1}*\displaystyle \sum_{i=0}^{n-2}T_{avg}(i)\\ nT_{avg}(n)-(n-1)T_{avg}(n-1)&= nD(n)+2\displaystyle \sum_{i=0}^{n-1}T_{avg}(i)-(n-1)D(n-1)-2*\displaystyle \sum_{i=0}^{n-2}T_{avg}(i) \\ &=nD(n)-(n-1)D(n-1)+2T_{avg}(n-1) \\ &=n(n-1)-(n-1)(n-2)+2T_{avg}(n-1) \\ &=2(n-1)+2T_{avg}(n-1) \\ 移项得 \\ nT_{avg}(n) &= 2(n+1)+2T_{avg}(n-1)+(n-1)2T_{avg}(n-1) \\ &= 2(n+1)+(n+1)2T_{avg}(n-1) \\ 等式两边同时除以n(n+1)得 \\ \frac{T_{avg}(n)}{n+1}&=\frac{T_{avg}(n-1)}{n}+\frac{2(n-1)}{n(n+1)} \\ 令B(n)=\frac{T_{avg}(n)}{n+1} 得\\ B(n)&=B(n-1)+\frac{2(n-1)}{n(n+1)} \\ &=\boxed{B(n-2)+\frac{2(n-2)}{n(n-1)}}+\frac{2(n-1)}{n(n+1)} \\ .\\ .\\ .\\ &=B(1)+\displaystyle \sum_{i=1}^{n}\frac{2(i-1)}{i(i+1)} \\ &=B(1)+\displaystyle \sum_{i=1}^{n}\frac{2(i+1)-4}{i(i+1)} \\ &=\displaystyle \sum_{i=1}^{n}[\frac{2}{i}-\frac{4}{i(i+1)}] \\ \because \displaystyle \sum_{i=1}^{n}\frac{4}{i(i+1)}&= 4\displaystyle \sum_{i=1}^{n}[\frac{i+1-i}{i(i+1)}] \\ &= 4\displaystyle \sum_{i=1}^{n}[\frac{1}{i}-\frac{1}{i(i+1)}] \\ &= 4[1-\frac{1}{n+1}]=\boxed{4\frac{n}{n+1}}\\ \because \displaystyle \sum_{i=1}^{n}\frac{1}{i}&\approx \colorbox{aqua}{0.577216+ln(n)} \\ \therefore B(n)&=2*0.577216+2*ln(n)+4\frac{n}{n+1} \\ 代回B(n)=\frac{T_{avg}(n)}{n+1} \\ T_{avg}(n)&=2(n+1)ln(n)-4n+2*0.577216(n+1) \implies \boxed{O(nlog_2^n)} \\ \end{aligned} Tavg(n)Tavg(n1)nTavg(n)(n1)Tavg(n1)移项得nTavg(n)等式两边同时除以n(n+1)n+1Tavg(n)B(n)=n+1Tavg(n)B(n)...i=1ni(i+1)4i=1ni1B(n)代回B(n)=n+1Tavg(n)Tavg(n)=D(n)+n1i=0n1Tavg(i)+Tavg(ni)=D(n)+n2i=0n1Tavg(i)=D(n1)+n12i=0n2Tavg(i)=nD(n)+2i=0n1Tavg(i)(n1)D(n1)2i=0n2Tavg(i)=nD(n)(n1)D(n1)+2Tavg(n1)=n(n1)(n1)(n2)+2Tavg(n1)=2(n1)+2Tavg(n1)=2(n+1)+2Tavg(n1)+(n1)2Tavg(n1)=2(n+1)+(n+1)2Tavg(n1)=nTavg(n1)+n(n+1)2(n1)=B(n1)+n(n+1)2(n1)=B(n2)+n(n1)2(n2)+n(n+1)2(n1)=B(1)+i=1ni(i+1)2(i1)=B(1)+i=1ni(i+1)2(i+1)4=i=1n[i2i(i+1)4]=4i=1n[i(i+1)i+1i]=4i=1n[i1i(i+1)1]=4[1n+11]=4n+1n0.577216+ln(n)=20.577216+2ln(n)+4n+1n=2(n+1)ln(n)4n+20.577216(n+1)O(nlog2n)

    空间复杂度

    快速排序的空间复杂度取决于分划基准的选择,每次都选在中间, O ( l o g 2 n ) O(log_2^n) O(log2n)若基本有序,退化为冒泡,栈的深度 O ( n ) O(n) O(n)

    O ( l o g 2 n ) O(log_2^n) O(log2n)就是递归的深度,递归的时候使用的栈空间稍微优化一点的快排,比如取首尾中三个数据,取其中间的值作为划分标准,不会出现退化到冒泡的可能。

    计算参考:https://zhuanlan.zhihu.com/p/341201904

  • 相关阅读:
    一步一图带你深入理解 Linux 虚拟内存管理
    利用vue模拟element-ui的分页器效果
    成为Linux大神——必须要具备的基本技能!
    算法导论第一章——算法在计算中的应用
    Flask数据库_filter过滤器的使用
    说Redis是单线程其实不严谨以及一些思考
    聚类模型的算法性能评价
    第二证券|小鹏持续萎靡,理想蔚来逆势反弹破月销记录
    本地电脑搭建Plex私人影音云盘教程,内网穿透实现远程访问
    【Java基础篇 | 面向对象】—— 继承
  • 原文地址:https://blog.csdn.net/weixin_43820556/article/details/126876791