当前位置: 首页 > news >正文

《算法导论》笔记——归并排序及循环不变式证明

归并排序

算法原理:

现在还是沿用牌堆的比喻:

一共两个操作:

1.分解:

现在我们将牌分为两堆,对其中的每一堆都进行 ‘分为两堆’ 这个操作,直到单个堆的元素只有一个

此时每个牌堆都是有序的

2.合并

我们对每两个临近的牌堆(记为A、B)进行:

当前每个牌堆仅一张牌,我们比较A和B的大小,将其中较小的一张牌放在另一张的上面



以此类推,对34、56、78…号牌堆进行这个操作

这样,我们获得了若干个单个都有序的牌堆

接下来,我们继续取临近的两个牌堆(记为A、B),进行:

取出两堆顶上的一张,比较大小,将其中较小的放入手中,重复这个操作(如果此时手中有牌,那么放于上一张的下面),直到至少一堆已空

那么这时,一共会有3种情况:

1.两堆均空

2.A空

3.B空

对于1,我们继续合并34、56、78…号牌堆,重复这个操作

对于2、3,显然,此时剩余的所有元素一定比单独的这一堆中最大的都大,读者可以自己尝试证明一下(提示:反证法)

那么我们可以将剩余的所有元素都直接按序排在单独这一堆后

(严格证明见下文)

我们继续重复合并操作,直到只剩一堆,那么此时的牌堆就是有序的,排序完成

具体算法实现

我们规定,a为待排序数组,共有n个元素(0 to n-1)

l为目前区间的左端点,r为右端点,mid为中间点

其中mid = l+(r-l)/2 (至于为什么这么做,而不是简单的(l+r)/2,请看下文)

开始时,l=0,r=n−1,mid=l+(r−l)/2l = 0,r = n-1,mid = l+(r-l)/2l=0r=n1mid=l+(rl)/2
我们执行两个操作:

1.分解:令a分为[l,mid]、[mid+1,r]两段,对子段继续按此分解,直到l=r(即单一元素)

2.合并:

我们每次合并[l,mid]、[mid+1,r]两段,保证每段内部都是有序的
也就是说,我们需要一个合并函数,类似:merge(a,l,r,mid)

将[l,mid]记为L,[mid+1,r]记为R,再定义一个辅助数组tmp,用来保存合并完的数组,之后拷贝到数组a

令k为当前tmp的下标,开始时k=0

令i,j为当前L、R的下标,开始时为0

然后:如果L[i]>=R[j],那么tmp[k] = L[i],i–;

否则tmp[k] = R[j],j–;

重复直到至少其中一堆的top < 0(即堆中没有任何数);

随后,我们将剩余的所有元素拷贝至tmp的末尾

等到合并完的区间就等于a时,算法结束, a数组有序;

循环不变式证明

P.S. 这里的证明方式更通俗,《算法导论》给出了另外一种证明

由于分解这一步仅仅相当于分解问题为多个子问题,因此,我们不对这个阶段证明循环不变式

那么,我们列出合并阶段的循环不变式:

记住:在过程中,我们力图维护的,就是其循环不变式

那么,我们其实一直在维护的是tmp数组的有序性

所以,循环不变式为:

tmp[0…k]始终保存L[0…i]、R[0…j]中前k小的数

前提条件:子序列LR均有序

我们开始证明:

1.初始化: 此时tmp中什么也没有,因此符合循环不变式

2.保持:在算法过程中,我们有两个阶段:

  • 一、在两个子序列均非空时,我们每次取L[i]、R[j]中较小的那一个
  • 二、在至少一个为空时,我们将剩余的所有元素直接移动到tmp末尾



    我们挨个证明,
  • 一、若取了L[i]、R[j]L[i]、R[j]L[i]R[j]中较小的那一个导致tmp无序,那么说明有一个数M>min(L[i]、R[j])M>min(L[i]、R[j])M>min(L[i]R[j])且在tmp中,而M一定来自L、R其中一个数组;


    若M来自于min(L[i]、R[j])min(L[i]、R[j])min(L[i]R[j])所在的数组,那么说明此数组无序,于前提不符,所以不存在这样的M,即tmp有序;


    若M来自于max(L[i]、R[j])max(L[i]、R[j])max(L[i]R[j])所在的数组,那么说明,在选择M的那一轮循环中,M为较小的,因此另一堆剩余的所有元素一定都大于M,M这一堆剩余的所有元素也一定都大于M

    所以之后不会存在一个比M小的数min(L[i]、R[j])min(L[i]、R[j])min(L[i]R[j])了,与前提不符,所以不存在这样的M;

  • 二、若这个操作导致tmp无序,那么在剩余这一堆中一定存在一个数Q,使得Q<tmp[k]Q<tmp[k]Q<tmp[k],那么,tmp[k]只可能来自L、R

    如果和Q同堆,那么由于Q<tmp[k]Q<tmp[k]Q<tmp[k],说明此堆无序,所以不存在Q
    若不同堆,那么说明这一堆剩余所有元素均大于tmp[k],则剩余的堆应该是这一堆,与假设不符;

Q.E.DQ.E.DQ.E.D

形式化的循环不变式证明:

形式化定义:
L[1..m]L[1..m]L[1..m]R[1..p]R[1..p]R[1..p] 为有序数组,合并时不变式:
temp[1..k]temp[1..k]temp[1..k] 包含 L[1..i]L[1..i]L[1..i]R[1..j]R[1..j]R[1..j] 中前 kkk 小元素且有序

数学归纳证明:

初始化:k=0,i=1,j=1k=0, i=1, j=1k=0,i=1,j=1,空数组满足条件
保持:
L[i]≤R[j]L[i] \leq R[j]L[i]R[j],则 L[i]L[i]L[i] 是剩余元素最小者
由归纳假设 temp[1..k]temp[1..k]temp[1..k] 有序,追加 L[i]L[i]L[i] 仍有序
终止:i>mi>mi>mj>pj>pj>p 时,剩余元素直接追加仍有序

为什么是mid = l+(r-l)/2

我们先尝试展开:
mid=l+(r−l)/2=l+0.5r−0.5l=0.5r+0.5l=1/2(r+l)mid = l+(r-l)/2 = l+0.5r-0.5l = 0.5r+0.5l = 1/2(r+l)mid=l+(rl)/2=l+0.5r0.5l=0.5r+0.5l=1/2(r+l);

与直接mid=(l+r)/2mid = (l+r)/2mid=(l+r)/2本质上是一致的

那么为什么还要变形呢

想这样一个问题:倘若r、l均取int类型的最大值的一半+1,mid=(l+r)/2mid = (l+r)/2mid=(l+r)/2会发生什么呢

显然,l+rl+rl+r超过了int类型的最大值,会导致溢出

综上,我们选择mid=l+(r−l)/2mid = l+(r-l)/2mid=l+(rl)/2

关于位运算的一些奇技淫巧

我们注意到:在mid=l+(r−l)/2mid = l+(r-l)/2mid=l+(rl)/2中需要计算一个/2/2/2的操作

那么,有请位运算——快速乘除法

原理 :在进行乘除运算时,如果乘数或除数是 2 的幂次方,可以利用位移运算来替代乘除法操作

左移一位相当于乘以 2,右移一位相当于除以 2

例如,num << n 等价于 num * 2^n,num >> n 等价于 num / 2^n

所以,我们可以改为:mid = (l+(r-l))>>1 (位运算的优先级更低,需要单独加括号)

Upt 2025.8.6

GhostSilver

http://www.dtcms.com/a/318339.html

相关文章:

  • [AI 生成] 大数据数仓面试题
  • 无人机共轴双桨动力测试-如何确认桨叶最优间距(效率/噪音/重量/尺寸)
  • 无人机航拍数据集|第3期 无人机军事目标目标检测YOLO数据集3556张yolov11/yolov8/yolov5可训练
  • 2025年高防IP隐身术:四层架构拆解源站IP“消失之谜”
  • 笔试——Day30
  • 吴声 2025 年度演讲:“场景革命十年”的多面审视,理念重复之嫌!
  • 笔记html模板
  • OpenHarmony源码解析之init进程
  • 题解:CF1453D Checkpoints
  • 看不见的伪造痕迹:AI时代的鉴伪攻防战
  • 微信小程序多媒体功能实现
  • n8n循环处理完全指南
  • 工业自动化中Modbus RTU转Profibus DP网关的应用与温度监控实现
  • 昇思+昇腾开发板:DeepSeek-R1-Distill-Qwen-1.5B 模型推理部署与 JIT 优化实践
  • spring i18n 词条英文下格式化占位符不生效 问题排查处理
  • Kafka 重平衡(Rebalance)机制
  • UniStorm 5.3.0 + Unity2022 + URP配置说明
  • 机器学习概述
  • 微调训练时,ignore_empty_think是怎么保护模型的思考能力?
  • 自然语言处理的相关概念与问题
  • Redis面试精讲 Day 12:Redis Sentinel哨兵机制详解
  • 非机动车识别mAP↑28%!陌讯多模态融合算法在智慧交通的实战解析
  • PyTorch生成式人工智能——Hugging Face环境配置与应用详解
  • leetcode 3479. 水果成篮 III 中等
  • 74.5%登顶SWE-bench:Claude Opus 4.1如何重塑AI编程格局
  • AdGuard 安卓修改版:全方位广告拦截与隐私保护专家
  • 将英文PDF文件完整地翻译成中文的4类方式
  • 【机器学习篇】02day.python机器学习篇Scikit-learn基础操作
  • Kafka ISR机制和Raft区别:副本数优化的秘密
  • 浅谈对linux进程池的理解