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

一些概念

排序问题

  • 输入:一个n个数的序列<a1,a2,...,an>
  • 输出:输入序列的一个排列(重排)<a1,a2,...,an>,使得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小的数。用于选择算法

第六章 堆排序

堆排序

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

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

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

堆的两种形式:最大堆和最小堆。最大堆是指除了根以外的所有结点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:堆排序,原址排序。

堆排序伪代码

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

代码简单解析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把一个新作业加入到队列中。

优先队列相关伪代码

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

所有最大优先队列的操作时间复杂度均为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]已经有序。

快速排序伪代码

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

循环不变性分析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个输入元素中的每一个元素都在0k区间内的整数,当k=O(n),一般计数排序要求k不能太大;

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

  • 计数排序伪代码

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

基数排序

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

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

基数排序伪代码

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

基数排序和快速排序的对比

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

桶排序

桶排序的限制

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

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

桶排序伪代码

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

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

首先我们知道插入排序的时间代价为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...n1j=1...nXij=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个(互异的)元素的集合和一个整数i1in
  • 输出:元素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个位置,表明xA中第k小的元素;

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

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

写出它的伪代码

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

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

总结

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

相关文章: