算法基础4—快速排序

本文详细介绍了快速排序算法,包括其分治思想、实现步骤、伪代码及JavaScript实现,并分析了不同情况下的时间复杂度。

快速排序也是在面试中常被问的排序算法之一,它与归并算法一样,也使用了分治的思想。快速排序的三步分治过程:

  1. 分解:将一个待排序数组A[p,…,r]划分为两个字数组(可能为空)A[p…q-1], A[q+1…r],其中A[p…q-1]中的每一个元素都小于A[q], A[q+1…r]中的每一个元素都大于A[q]。计算下标q也是划分过程中的一部分。
  2. 解决: 通过递归调用快速排序,对子数组A[p…q-1]和A[q+1…r]进行排序。
  3. 合并:因为子数组都是原址排序的,所以不需要合并操作,数组A[p…r]已经有序。
    下面先给出快速排序的伪代码:
quickSort(A, p, r)
 1.if p<r
 4.  q = partition(A,p,r)
 5.  quickSort(A, p, q-1)
 6.  quickSort(A, q+1, r)

为了排序一个数组的所有元素,初始调用是quickSort(A, 1,A.length)。
首先介绍一个快速排序的最关键的一步就是数组的划分:
即partition部分,它实现了对数组A[p…r]的原址重排。
番外:<原址在百度百科中给出的定义是在排序算法中,如果输入数组中仅有常数个元素需要在排序过程中存储在数组之外,则称排序算法是原址的。为什么归并排序不是原址排序,而堆排序是原址的呢?要怎么理解这句话呢?
因为归并排序还要另外一个数组做merge。那个是O(n)空间。in-place归并是可以写出来的,就是超级麻烦。快排是in-place的,因为它就是在自己数组里折腾。堆排也是,它建立了一个堆的结构,每次deletemax少一个元素,但它还是在自己数组里折腾的,只是这个数组前面若干个元素维护了堆结构,后面若干个元素是由小到大排好顺序的,堆永远不会访问后面排好顺序的元素。>
继续给出partition的伪代码:

partition(A, p, r)
 7. x = A[r]            //选定的基准值
 8. i = p-1             //类似一个指针,指定要交换的元素
 9. for j = p to r        //也类似一个指针,指定后面满足交换条件的元素
 10.     if A[j] <= x
 11.        i ++       
 12.        exchange A[i] with A[j]
 13. exchange A[i+1] with A[r]
 14. return i+1

在子数组A[p…r]上,partition维护了4个区域,如下图所示,A[p…i]区间内的所有值都是小于等于x,A[i..j]区间内的所有值都是大于x,A[r]=x。字数组A[j…r-1]中的值可能属于任何一种情况。


当A[j..r]中的元素小于等于x时,执行如算法中的操作,i++,并且交换A[i]和A[j]。元素大于x时,没有明显操作,即加入到A[i..j]中。
js代码如下:


var partition = function(a,l,r){
    var x = a[r];
    var i = l-1;
    for(var j = l; j < r; j++){
        if(a[j] <= x){
            i++;
            var temp = a[i];
            a[i] = a[j];
            a[j] = temp;
            console.log(i+' '+j);
        }
    }
    var ans = a[i+1];
    a[i+1] = a[r];
    a[r] = ans;
    return i+1;
}
var quickSort=function(a,l,r){
    if(l < r){
        var  p = partition(a,l,r);
        //console.log(p);
        quickSort(a,l,p-1);
        quickSort(a,p+1,r);
    }
}
function main(){
    var a = [72,6,57,88,85,42,83,48,60];
    var l = 0;
    var r = a.length-1;
    quickSort(a,l,r);
    return a;
}
console.log(main());

时间复杂度分析

还有一个过程,就是算法的时间复杂度分析,虽然我们常说快速排序的时间复杂度是O(nlgn),却很少具体的分析一下什么情况下是O(nlgn),这个是快排最好的时间复杂度吗?
我们都知道快排的运行时间依赖于划分是否平衡,而平衡与否又依赖于用于划分的元素。如果划分是平衡的,那么快排算法性能与归并排序一样。如果划分不平衡,那么快排的性能就接近于插入排序了。
下面从三个不同的划分来分析一下快排的时间复杂性。

  1. 最坏情况划分

    当划分的两个字问题分别包含了n-1个元素和0个元素。我们假设每次递归调用都出现了这种不平衡的划分。划分操作的时间复杂度是O(n)。由于对大小为0的数组进行递归会直接返回,因此T(0) = O(1),于是算法运行的时间递归式可以表示成
    T(n) = T(n-1)+O(n)
    利用带入法可以得到T(n) = O(n)+O(n-1)+O(n-2)+…+O(1)+T(0)
    即: T(n) = O(n^2)
    可以看到,最坏情况下快速排序运行时间并不比插入排序更好。此外,当数组已经排好序时,时间复杂度依然为O(n^2),而插入排序的时间复杂度为O(n)。

  2. 最好情况划分

    在可能的最平衡的划分中,partition得到的两个子问题的规模都不大于n/2。这是因为其中一个子问题的规模为[n/2],而另一个子问题的规模为[n/2]-1。在这种情况下快速排序的性能非常好。此时,算法运行的时间递归式为:
    T(n) = 2T(n/2)+O(n)
    上述递归式的解为T(n) = O(nlgn)。
    事实上,任何一种常数比例的划分都会产生深度为O(lgn) 的递归树,其中每一层的时间代价都是O(n),因此,只要是常数比例的划分,算法的运行时间都是O(nlgn)。

  3. 平均情况

    在平均情况下,partition所产生的划分同时混合有“好”和“差”的划分。此时,在递归树中,好和差的划分是随机分布的。
    如下图a所示,显示了连续两层上的划分,根节点处划分的代价为n,划分产生两个子数组,大小分别为n-1和0。在下一层上,大小为n-1的子数组按最好情况划分。这一组合的划分代价为O(n)+O(n-1)= O(n)。该代价并不比(b)中所示的划分情况差。因此,当好的划分和差的划分交替出现时,快速排序的时间复杂度与全是好的划分时一样,依然是O(nlgn)。区别只是O符号中隐含的常数因子要略大一些。
    这里写图片描述

内容概要:本文介绍了ENVI Deep Learning V1.0的操作教程,重点讲解了如何利用ENVI软件进行深度学习模型的训练与应用,以实现遥感图像中特定目标(如集装箱)的自动提取。教程涵盖了从数据准备、标签图像创建、模型初始化与训练,到执行分类及结果优化的完整流程,并介绍了精度评价与通过ENVI Modeler实现一键化建模的方法。系统基于TensorFlow框架,采用ENVINet5(U-Net变体)架构,支持通过点、线、面ROI或分类图生成标签数据,适用于多/高光谱影像的单一类别特征提取。; 适合人群:具备遥感图像处理基础,熟悉ENVI软件操作,从事地理信息、测绘、环境监测等相关领域的技术人员或研究人员,尤其是希望将深度学习技术应用于遥感目标识别的初学者与实践者。; 使用场景及目标:①在遥感影像中自动识别和提取特定地物目标(如车辆、建筑、道路、集装箱等);②掌握ENVI环境下深度学习模型的训练流程与关键参数设置(如Patch Size、Epochs、Class Weight等);③通过模型调优与结果反馈提升分类精度,实现高效自动化信息提取。; 阅读建议:建议结合实际遥感项目边学边练,重点关注标签数据制作、模型参数配置与结果后处理环节,充分利用ENVI Modeler进行自动化建模与参数优化,同时注意软硬件环境(特别是NVIDIA GPU)的配置要求以保障训练效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值