卜算法学习笔记-02-分而治之算法01

给定一个问题,如何求解?首先查看最简单的实例能否求解,假如可以求解的话,下一步就是思考能否将大的实例分解成小的实例,以及能否将小的实例组合成为大的实例,如果都可以的话就称实例能归约,这个问题具有递归结构,可以设计递归算法进行求解

排序问题:对数组的归约

排序问题:

  • 输入:一个包含 n 个元素的数组 A[0..n1],其中每个元素都是整数;
  • 调整元素顺序后的数组 A,使得对任意的两个下标 0i<jn1,有 A[i]A[j]

依据元素下标拆分数组:插入排序与归并排序

第一种拆分方案及插入排序算法

  • 算法分析
    我们只需执行一个简单操作即可将数组 A[0..n1] 分解成两部分:前 n1 个元素 A[0..n2],以及单独一个元素 A[n1]。前 n1 个元素组成一个小 的数组,是原给定实例的子实例。在将原给定实例拆分成子实例之后,我们假定子实例已被求解,对数组来说,所谓子实例的解就是已经排好序的小的数组 A[0..n2]。要想完成对整个数组 A[0..n1] 的排序,我们只需将最后一个元素 A[n1]和小数组 A[0..n2] 中的元素逐个比较,然后将 A[n1] 插入到合适的位置即可。连续应用递归调用,最终会到达基始情形:当 n=1 时,数组 A 只有一个元素,此时无需排序,直接返回即可。
    插入排序伪代码
  • 时间复杂度:

T(n)={1n=1T(n1)+O(n)otherwise

将上述递归式展开:

T(n)T(n1)+cnT(n2)+c(n1)+cnc++c(n1)+cn=O(n2)

算法低效的原因是:在运行过程中,问题规模是呈线性下降的,即每次递归操作都是将规模为 n 的问题分解成一个规模为 n1 的子问题。

第二种拆分方案及归并排序算法

  • 算法分析:
    将大的数组 A[0..n1]按下标拆分成规模相同的两半,即 A[0..n21]A[n2..n1];每一半依然是数组,形式相同,但是规模变小,因此是原给定实例的子实例。通过迭代执行分解操作,即可使得问题规模呈指数形式下降。在使用递归调用将小的数组排好序之后,我们只需依据这两个已排好序的小的数组,“归并”(Merge)出整个数组。这里的归并包括两层意思:合并、以及排序。
    归并排序伪代码
    归并过程:
    归并过程伪代码
    循环不变量,是指关于程序行为的一个断言;这个断言在循环起始时成立,并且每执行一轮循环时都保持成立,因此可以推论出当循环结束时,断言必定成立。
  • 时间复杂度分析

T(n)={1n=12T(n2)+O(n)otherwise=O(nlogn)

分而治之算法时间复杂度分析及Master定理

在分而治之算法中,一种常见的情况是将一个规模为 n 的实例归约成 a 个子实例,每个子实例规模都相同(设为 nb )。假如“组合”子实例解的时间开销是 O(nd),则我们可将时间复杂度 T(n) 的递归关系及基始情形表示如下:

T(n)={1n=1aT(nb)+O(nd)otherwise

对于子问题比较规整的情况,即每个子问题的规模都相同,T(n) 上界的显式表达式已被总结成 Master 定理:

T(n)=aT(nb)+O(nd)aT(nb)+cnda(aT(nb2)+c(nb)d)+cndcnd(1+abd+(abd)2++(abd)logbn)+alogbn={O(nlogba)d<logbaO(nlogbalogn)d=logbaO(nd)d>logba

依据元素的值拆分数组-快速排序

算法分析

依据元素的数值将大数组拆分成小数组,即选定一个元素作“中心元”(Pivot),比中心元数值小的元素组成一个小数组,比中心元数值大的那些元素组成另一个小数组。
快速排序伪代码

时间复杂度

称排序后的数组 AA~,因此数组 &A$ 的最小元是 A~[0], 最大元是 A~[n1],中位数是 A~[n2]。我们在选择中心元时可能面临如下两种情况:
(1) A~[n1]/ A~[0]: 这样只会生成一个子实例,规模减少了 1,呈线性降低。如果在每一次迭代都是如此选择的话, 运行过程就与 InsertionSort 算法相同,时间复杂度为:

T(n)=T(n1)+O(n)=O(n2)

(2) A~[n2]: 这样会生成两个子实例,每个子实例的规模都是原来的一半,呈指数下降。如果在每一次迭代都是如此选择的话,运行过程就与 MergeSort 算法相同,时间复杂度为:

T(n)=2T(n2)+O(n)=O(nlogn)

证明运行时间的期望值依然是 O(nlogn)
修正版快排伪代码
Modified-QuickSort 算法只做了一点修改:随机选择一个元素做中心元之后,先检验一下这个中心元是否足够好;如果足够好,则继续执行后续的比较和排序,否则重新选择一个元素做中心元。所谓的中心元足够好,是指它位于A ̃的中间区域,即A~[n4]A~[3n4],修改后的算法时间复杂度为:

T(n)T(n4)+T(3n4)+2n(T(n16)+T(3n16)+2n4)+(T(3n16)+T(9n16)+23n4)+2n=(T(n16)+T(3n16))+(T(3n16)+T(9n16))+2n+2n=O(nlog43n)

接下来我们分析 QuickSort 算法的时间复杂度。在做具体的分析之前,我们先陈述关于运行时间的 3 点事实:
(1) 运行时间由比较次数界定:我们的目标就是计算期望运行时间 E[X]
(2) 任意两个元素 A~[i]A~[j] 最多只会比较一次
(3) 两个元素A~[i]A~[j]发生比较的概率是2ji+1

Pr[A~[i]A~[j]]=1n+1n+n(ji+1)n×2ji+1=(ji+1n+n(ji+1)n)×2ji+1=2ji+1

由此计算时间复杂度为:

E[X]=E[i=0n1j=i+1n1Xij]=i=0n1j=i+1n1E[Xij]=i=0n1j=i+1n12ji+1=i=0n1k=1ni12k+1i=0n1k=1n12k+1=O(nlogn)

空间复杂度

需要创建两个辅助数组 SS+,这样一来,除了数组本身之外还要额外占用 n 个内存单元,导致当 n 比较大时,内存需求有时难以满足。
所以有了原位排序算法,为避免开辟辅助数组 SS+,Lomuto 算法直接将数组 A 的左半部分当做 S,存放比中心元小的元素;把数组 A 的右半部分当做 S+,存放比中心元大的元素
原位快排伪代码

posted @   eryo  阅读(49)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示