第二部分 排序和顺序统计量

第二部分 排序和顺序统计量

一些概念

排序问题

  • 输入:一个 n 个数的序列<a1,a2,...,an>
  • 输出:输入序列的一个排列(重排) <a1,a2,...,an> <script id="MathJax-Element-3" type="math/tex"> </script>,使得 a1a2...an

数据结构

在实际中待排序的数很少是单独的数值,它们通常是记录的一部分。每个记录包含一个关键字,这也是排序问题中要重排的值;记录的其他部分我们称为卫星数据,它和关键字是一同存取的。

学习排序的目的

排序是算法研究中最基础的问题。

  • 有些应用需要对信息进行排序,如准备报表时,银行按照编号对支票排序;
  • 很多算法将排序作为关键子程序,如Photoshop里面的图层,有顶层,底层等按照上下关系的排序图层;
  • 排序中有很多有用的技术,排序可以作为掌握这些技术的一个应用实例;
  • 排序问题的下界可作为其他问题下界证明的参照;
  • 在实现排序算法时出现的工程问题,能让我们意识到很多问题,需要从算法层面,而非“代码调优”层面去解决。

排序算法

原址

如果输入数组中仅有常数个元素需要在排序过程中存储在元素之外,则称该排序算法为原址的。

常用排序算法对比

算法最坏情况运行时间平均情况/期望运行时间空间原址性
插入排序 Θ(n2) Θ(n2)
归并排序 Θ(nlgn) Θ(nlgn)
堆排序 O(nlgn)
快速排序 Θ(n2) Θ(nlgn) (期望)
计数排序 Θ(n+k) Θ(n+k)
基数排序 Θ(d(n+k) Θ(d(n+k)
桶排序 Θ(n2) Θ(n) (平均情况)

为什么快速排序是目前来看最好的选择?

  • 快速排序具有空间原址性且平均情况运行 时间较小;
  • 我们注意到堆排序最坏情况是 O(nlgn) 且具有空间原址性,按道理讲应该堆排序更好一点,这里我们需要注意的是快速排序虽然最坏情况为 Θ(n2) ,但在实际应用中却几乎不会出现,更常见的是平均情况;还有的就是堆排序相对于快速排序而言, Θ(nlgn) 中的隐藏常数因子要大得多。

顺序统计量

一个 n 个数的集合的第i个顺序统计量就是集合中第 i 小的数。用于选择算法

第六章 堆排序

堆排序

(二叉)堆**是一个数组,它可以被看成一个近似的完全二叉树。树上的每一个结点对应数组的一个元素。除了最底层外,该树是完全充满的,而且是从左往右填充。

堆中父结点,左孩子,右孩子下标关系如下所示

parent-child-ship

堆的两种形式:最大堆和最小堆。最大堆是指除了根以外的所有结点i都要满足 A[Parent(i)]A[i] ;最小堆则相反,除了根以外的所有结点 i 都要满足A[Parent(i)]A[i]。在堆排序中,使用最大堆。

如何描述堆排序?

  • 堆排序是指利用堆这种数据结构所设计的一种排序算法,它是一种具有空间原址性的选择排序,时间复杂度为 O(nlgn)
  • 堆这种数据结构,我们可以把它近似看成二叉树,它有两种类型:最大堆和最小堆;最大堆要求,除根结点以外的所有结点 i 都要满足:A[Parent(i)]A[i],相反,最小堆则要求除根结点以外的所有结点 i 都要满足:A[Parent(i)]A[i]
  • 堆排序的非降序排列采用的是最大堆。堆排序利用堆能够自我维护的特性,在不断取出堆根结点的同时,对堆剩余元素重新建立最大堆,直至堆中只剩下根结点。

堆排序的核心步骤

  • MAX-HEAPIFY:维持最大堆性质的关键,时间复杂度为 O(lgn)
  • BUILD-MAX-HEAP:从无序的数据中构建最大堆,时间复杂度为 Θ(n)
  • HEAPSORT:堆排序,原址排序。

堆排序伪代码

heap-sort

代码简单解析 MAXHEAPIFY 是维持最大堆的关键函数,当最大堆中结点 i 改变时,通过MAXHEAPIFY重新建立最大堆; BUILDMAXHEAP 表示从无序数组中建立最大堆; HEAPSORT 是堆排序的实现函数,每次取出堆的根结点,再对剩余结点重新建立最大堆。

时间复杂度分析 MAXHEAPIFY 时间复杂度为 Θ(lg(n)) MAXHEAPIFY 时间复杂度为 O(n) HEAPSORT 时间复杂度为 O(nlg(n))

优先队列

描述:优先队列是一种用来维护由一组元素构成的集合 S 的数据结构,其中每一个元素都有一个相关的值,称为关键字。一个最大优先队列支持以下操作:

  • INSERT(S,x):把 x 插入集合S中,操作等价于 S=S{x}

    • MAXIMUM(S) :返回 S 中具有最大关键字的元素;
    • EXTRACTMAX(S):去除并返回 S 中的最大元素;
    • INCRESEKEY(S,x,k):将元素 x 的关键字增加到k,当然这里假设 x 的关键字不大于k
    • 优先队列的应用:在共享计算机系统中进行作业调度。最大优先队列记录将要执行的各个作业以及它们之间的相对优先级,当一个作业完成或被中断时,调度器用 EXTRACTMAX 从所有等待的作业中,选出具有最高优先级的作业来执行。在任何时候,调度器都可以调用 INSERT 把一个新作业加入到队列中。

      优先队列相关伪代码

      priority-queue

      所有最大优先队列的操作时间复杂度均为 O(lg(n))

      第七章 快速排序

      一句话描述快速排序

      快 速排序是目前来说最常用也是效果也最好的排序方法。它具有空间原址性,期望时间复杂度为 Θ(nlg(n)) ,且 Θ(nlg(n)) 中隐藏的常数因子非常小。它的原理是从待排序的数组中选出一个元素作为主元,根据主元将待排序数组分为两个子数组(一个由比主元小的元素组成,另一个由比主元大的元素组成),之后根据分治策略,分别对两个子数组进行相同操作,直至子数组中只剩下一个元素(也就是基本情况)。快速排序时间复杂度依赖于主元的选取,在实际应用时,一般会采取随机算法选取主元,以确保处理数据避免出现最坏情况。

      思考:是否可采用第九章第3节的中位数方法,来确定主元?

      快速排序分析

      • 快速排序是目前实际应用中最好的选择,它具有空间原址性;
      • 它的最坏情况时间复杂度为 Θ(n2) ,但是它的期望时间复杂度为 Θ(nlg(n)) ,且 Θ(nlg(n)) 中的隐藏因子非常小。
      分治思想考虑快速排序
      • 分解:数组 A[p...r] 被划分为两个子数组 A[p...q1] A[q+1...r] ,使得 A[p...q1] 中的每一个元素都小于等于 A[q] ;而 A[q+1...r] 中的每一个元素都大于等于$A[q];
      • 解决:通过递归调用快速排序,对子数组 A[p...q1] A[q+1...r] 进行排序;
      • 合并:因为子数组都是原址排序的,所以不需要合并操作,数组 A[p...r] 已经有序。
      快速排序伪代码

      quick-sort

      循环不变性分析 PARTITION 内的迭代操作

      这里的循环不变式为:

      1. pki 时, A[k]A[r]
      2. i+1kj1 时, A[k]A[r]

        • 初始化:迭代开始时, i=p1,j=p ,可知 A[p...i],A[i+1...j1] 均为空,可知循环不变式成立;
        • 保持:第 j 次迭代之前循环不变式成立,即有A[k]A[r],k=p...r;A[k]A[r],k=r+1...j2;当第 j 次迭代后,假设A[j]A[r],则交换 A[i+1] A[j] ,且 i=i+1j=j+1 ,因为迭代之前有 A[i+1]A[r],A[j]A[r] ,所以迭代之后,交换 A[i+1],A[j] 且自增 i,j 后,则仍有循环不变式成立;反之 A[j]>A[r] ,除了 j 自增1之外无其他操作,所以仍有循环不变式成立;
        • 终止:当迭代结束时,j=r则有 A[p...i]A[r],A[i+1...r1]A[r] ,由 PARTITION 的目的可知,它是为了按照主元 A[r] A[p...r1] 分成满足循环不变式的两部分,并且算法最后两行将 A[r] A[i+1] 交换,可知数组被关键字 q=i+1 分成了两个子数组 A[p...q] A[q+1...r] 算法正确。
      快速排序的性能
      最好情况划分
      • 每次都是划分成两个规模相等的子问题;
      • T(n)=2T(n/2)+Θ(n) ,所以时间复杂度为 Θ(nlgn)
      最坏情况划分
      • 每次划分后的子问题都有一个为空;
      • T(n)=T(n1)+Θ(n) ,将每一层的代价叠加可知,时间复杂度为 Θ(n2)
      平均情况(期望)划分
      • 随机化版本,即在数组 A 中根据均匀分布选择主元
      • 考察之后可知,其平均情况时间复杂度为Θ(nlgn)

      第八章 线性时间排序

      本章前言

      对之前的排序进行总结:均属于比较排序

      • 比较排序:在排序的最终结果中,各元素的次序依赖于它们之间的比较;有插入排序,归并排序,堆排序,快速排序。

      本章介绍内容:三种线性时间复杂度的排序算法——计数排序,基数排序,桶排序

      比较排序的最坏情况下界 Ω(nlgn)

      计数排序
      • 限制:输入为一个小区间的整数,它假设 n 个输入元素中的每一个元素都在0 k 区间内的整数,当k=O(n),一般计数排序要求 k 不能太大;

      • 基本思想:对于每一个输入元素x,确定小于等于 x 的元素个数。利用这一信息,将x放在合适位置。

      • 计数排序伪代码

        counting-sort

      基数排序

      基数排序很简单,它是一种用在卡片排序机上的排序算法。它的限制是排序的数据必须是整数且基础排序算法必须是稳定的,通过从低到高分别对不同位进行排序,从而最后实现对整个数组的排序,一般把计数排序(稳定的)作为基数排序对每一位的数据进行排序的基础算法。

      radix-sort

      基数排序伪代码

      radix-sort-pesudo

      基数排序和快速排序的对比
      • 虽然基数排序时间复杂度为 Θ(n) ,比快速排序 Θ(nlgn) 看上去更好,但其中隐藏的常数因子却大得多;
      • 基数排序借助的稳定排序(通常为计数排序)是非空间原址的,而快速排序是空间原址的。
      桶排序

      桶排序的限制

      • 严条件:输入数据服从均匀分布 [a,b)
      • 宽条件:所有桶的大小的平方和与总元素数呈线性关系

      满足任何一个条件都可以。

      桶排序伪代码

      bucket-sort

      桶排序平均情况时间复杂度分析

      首先我们知道插入排序的时间代价为 O(n2) ,我们假设第 i 个桶里的元素个数为ni,所以我们知道第 1112 行代码的时间代价为 n1i=0O(n2i) ,所以桶排序的总时间代价为 T(n)=Θ(n)+n1i=0O(n2i) 。求其期望时间代价为 E[T(n)]=E[Θ(n)+T(n)]=Θ(n)+E[n1i=0O(n2i)]=Θ(n)+T(n)=Θ(n)+n1i=0O(E[n2i])

      接下来,我们说明 E[T(n)]=Θ(n)

      我们定义指示变量:对于所有 i=0...n1 j=1...n Xij=I{A[j] in Bucket[i]} ;因为输入数组 A 均匀分布,所以每个元素等概率落入每个桶中,故每个桶具有相同的期望值E[n2i] ni=nj=1Xij

      E[n2i]=E[(j=1nXij)2]=E[j=1nk=1nXijXik]=j=1nE[X2ij]+1jn1kn;kjE[XijXik]E[X2ij]=121n+02(11n)kjXijXikE[XijXik]=1n1n=1n2 E[n2i]=n1n+(n2n)1n2=21n

      所以 E[T(n)]=Θ(n)+n1i=0O(E[n2i])=Θ(n)+nO(21n)=Θ(n)

      分析完毕。

      第九章 中位数和顺序统计量

      一句话:这一章主要讨论的是选择问题,其中涉及到最大元素,最小元素,中位数以及一般地,第 i 大的元素的选择。诚然,根据之前的介绍,我们可以先根据排序算法对数组进行排序,然后根据下标进行访问,但这样所需的时间代价为O(nlgn),本章的目的是提出期望为线性时间的选择算法。

      概念

      顺序统计量:第 i 个顺序统计量就是该集合中第i小的元素。

      选择问题

      • 输入:一个包含 n 个(互异的)元素的集合和一个整数i 1in
      • 输出:元素 xA ,且 A 中恰好有i1个其他元素小于它。

      求最小值时间代价 Θ(n) 比较次数为 n ;如果同时求最大和最小值,其时间代价Θ(n),但是我们可以将比较次数由 2n 降到 3n/2

      期望为线性时间的选择算法

      RANDOMIZEDSORT 算法是以快速排序算法为模型的,但它与快速排序不同的是, RANDOMIZEDSORT 只需要处理一边,使得其期望时间代价降为 Θ(n) ,这里就不证明了,可自行参考算法导论第九章教材。

      结论:假设所有元素是互异的,在期望线性时间内,我们可以找到任一顺序统计量,特别是中位数。

      最坏情况为线性时间的选择算法

      由于快速排序在最坏情况时,其时间复杂度为 Θ(n2) ;为了避免出现最坏情况,一种办法是采用随机算法选取主元,当然我们可以采用另一种更好的选择主元的方式,使得算法即使在最坏情况下,时间代价也是 Θ(n)

      其选择主元的方式称为 SELECT 算法:

      1. 将输入数组的 n 个元素划分为n/5组,每组5个元素,且至多只有一组由剩下的 nmod5 个元素组成;

      2. 寻找这 n/5 组中每一组中位数:首先在组内进行插入排序,然后选择每组的中位数;

      3. 对2中找出的 n/5 个中位数,递归调用 SELECT 以找出中位数 x

      4. 利用修改过的PARTITION版本,按中位数的中位数 x 对输入数组进行划分。如果最后中位数x落在数组 A 的第k个位置,表明 x A中第 k 小的元素;

      5. 如果i=k,则返回 x ,如果i<k,则在低区(元素值均比 x 小)递归调用SELECT来查找第 i 小元素;否则在高区递归调用SELECT来查找第 ik 小的元素。

        select

      写出它的伪代码

      select01

      select02

      总结

      介绍了数组排序的各种算法:比较排序,线性时间复杂度排序;在最后一行介绍了选择算法,并且指出选择算法的时间复杂度也可以是线性的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值