算法设计导论 课堂笔记

上12周 考试和考试周一起考 18 19周

24节课

考察内容:

考虑正确性证明(常用循环不变性+归纳法证明)

循环不变式(Loop Invariant)是算法设计与分析中的一个核心概念,尤其在证明迭代算法 (如排序、查找、图遍历等)的正确性时起着至关重要的作用。它本质上是一种在循环执行过程中始终保持为真的逻辑断言或性质


一、循环不变式的定义

循环不变式是指:在循环的每次迭代开始前(有时也包括结束后),某个特定条件始终成立。这个条件描述了算法在该时刻所维护的关键状态或结构性质。

更形式化地说,设有一个循环结构,其循环变量为 jj ,循环不变式 I(j)I(j) 是一个关于程序状态(如数组内容、变量值等)的谓词,满足:

  • 在首次进入循环前, I(j)I(j) 成立;
  • 若某次迭代前 I(j)I(j) 成立,则执行完该次迭代后,下一次迭代开始前 I(j+1)I(j+1) 仍成立;
  • 当循环终止时, I(j)I(j) 与终止条件共同蕴含算法的最终目标已达成。

二、为什么循环不变式能用于证明算法正确性?

循环不变式之所以有效,是因为它的验证过程严格对应于数学归纳法的逻辑结构,并通过三个关键步骤确保算法从初始状态到终止状态的每一步都"走在正确的轨道上"。

1. 初始化(Initialization)

证明在第一次循环迭代之前,循环不变式成立。

这相当于数学归纳法中的基础情形(base case)。例如,在插入排序中,当 j=1j=1 时,子数组 A0..j−1=A0A0..j−1=A0 只包含一个元素,显然是有序的,因此不变式"A0..j-1 是非降序排列的"成立。

2. 保持性(Maintenance)

假设在第 kk 次迭代开始前 不变式成立,证明在第 k+1k+1 次迭代开始前它仍然成立。

这对应于数学归纳法的归纳步骤。例如,在插入排序中,若 A0..j−1A0..j−1 已有序,算法将 AjAj 插入到合适位置,使得 A0..jA0..j 依然有序。这一操作由内层 while 循环完成(向右移动大于 key 的元素),从而保证了不变式的延续。

3. 终止(Termination)

当循环结束时,结合循环终止条件和不变式,能够推出算法达到了预期目标。

这是循环不变式区别于纯数学归纳法的关键------归纳在此处停止,并用于推导最终结果。例如,插入排序的外层循环在 j=nj=n (数组长度)时终止,此时不变式表明 A0..n−1A0..n−1 整体有序,即整个数组已排序完成。

正如《算法导论》所强调:"第三条性质或许是最重要的,因为我们将使用循环不变式来证明正确性。"


三、循环不变式的实际意义

  1. 提供清晰的算法理解框架

    它迫使程序员明确回答:"我在循环中试图维护什么不变的性质?"这种思维习惯能显著减少逻辑错误。

  2. 支持形式化验证

    在高可靠系统(如航天、金融)中,可通过断言(assertion)在代码中嵌入不变式检查,实现运行时验证。例如

    复制代码
    1assert is_sorted(A, j)  # 每次循环前后检查 A[0:j] 是否有序
  3. 适用于多种算法范式

    • 排序:插入排序(前缀有序)、冒泡排序(后缀已排好最大元素);
    • 查找:线性查找(已检查部分不含目标值)、二分查找(目标若存在必在 l, r 区间内);
    • 链表处理:删除重复节点(当前指针前无重复)。

四、举例说明:插入排序的循环不变式

以升序插入排序为例:

复制代码
1def INSERTION_SORT(A):
2    j = 1
3    while j < len(A):
4        key = A[j]
5        i = j - 1
6        while i >= 0 and key < A[i]:
7            A[i+1] = A[i]
8            i -= 1
9        A[i+1] = key
10        j += 1
  • 循环不变式 :在每次外层循环开始时,子数组 A[0..j-1] 是原数组中前 j 个元素的有序排列
  • 初始化j=1 时,A[0..0] 单元素,自然有序。
  • 保持 :假设 A[0..j-1] 有序,将 A[j] 插入后,A[0..j] 仍有序。
  • 终止 :当 j = len(A) 时,A[0..len(A)-1] 全部有序,算法正确。

算法的效率------空间时间复杂度

独立的,于机器无关

关注相对的近似的时间

考虑在大规模输入下的增长趋势

常数证明法:

证明2^(2n) budengyu O(2^n)

证明:

定义证明法:(和常数证明法一致,就是定义证明法)

常数证明法:

极限证明法:

洛必达:0/0型或∞/∞

算法优化问题

迭代和递归哪个更快

一般迭代反而更快,因为递归要调用函数产生开销,但是迭代只是loop

★流程题 给定递归函数,求输出值,用树状图

★属性归纳法证明递归正确性

★递归的要求:截至条件 有progress 不能fn=fn

分治

要求每个子任务互相独立' mergesort quick sort 子问题不重叠

动态规划

由于斐波那契每次都会计算f0 f1,很大部分会重复计算,因此考虑设计一个表,存储各个子问题的答案,会从O(N*2)->O(n),空间复杂度是n

但这样还要递归调用,调用会花费视觉空间

因此使用自底向上的调用

什么样的问题适合DP解决呢?

满足:

解是由子问题的解组成的

由很多子问题是重复运输的

如何实现?

记住各个子问题的答案

时间复杂度=每个子问题的时间=subpro*time

分治和动态规划的区别

子问题不重叠------分治

子问题重叠------动态规划

如何减少计算的:保存结果、bonttom to top从低到上的计算

DP的步骤:

0/1背包问题

递归解决:

问题转换为,如果第i个物品不选,则从前i-1个物品中选w的物品;

如果选,则从前i-1个物品中选w-wi的物品;

递归做法:

此外为了记录旋量哪些内容,开辟一个bi,w记录选了哪些内容

装不下就保持上一个状态,如果装得下,就看选择原来的东西,还是换新的东西

先横着填,再往下填

但凡放不下(当前重量放不放的下自己),就使用其上面一个的值!!!

但凡放得下,就计算max【a,b+v】,即不选自己和选自己的最大值(上一个值,自己的值+选择了自己后还能装得下的最大值)

同时还需要填写表,表示是从哪里来的(记录选择了哪些物品),然后从最后一个往前找递推链到0,0即可

LCS问题 最长公共子序列

只要顺序一致即可,不要求连续。

长度为n的字符串,最多有2^n种子字符串

如果Xm和Yn是相等的,也就是两个字符串的末尾字符一致

LCS(Xm,Yn)=LCS(Xm-1,Yn-1)+1

如果Xm和Yn是不相等的,也就是两个字符串的末尾字符不一致,那么缩减后一定是要么Xm-1,Yn,要么压缩为Xm,Yn-1

得到递推公式:

依然使用表格法:

如果末尾字符相同,则从对角线来,如果不相同,则选择从上方或左侧来的最大值

先看相不相同,如果相同选择右上的值++,如果不同看上方和左方谁最大,一样默认选上方,也可以走左侧(else if ci-1,j>=ci,j-1)结果的长度是唯一的,但是结果序列不唯一。

最后的步数就是斜着的格子个数。

在只需要计算长度,不需要具体内容的情况下:

如果需要空间优化,还可以只记录本行和前一行,因为推导只需要这两行。

最优二叉树

引入n+1个叶子,n和1是为了约束找不到的场景

概率越高的应该放在越浅的位置

i 0 1 2 3 4 5
pᵢ 0.15 0.1 0.05 0.1 0.2
qᵢ 0.05 0.1 0.05 0.05 0.05 0.1

内部节点(关键字):

  • k₂: depth=1 → 1 × 0.1 = 0.1
  • k₁: depth=2 → 2 × 0.15 = 0.3
  • k₄: depth=2 → 2 × 0.1 = 0.2
  • k₃: depth=3 → 3 × 0.05 = 0.15
  • k₅: depth=3 → 3 × 0.2 = 0.6

→ 内部节点总和 = 0.1 + 0.3 + 0.2 + 0.15 + 0.6 = 1.35

外部节点(失败节点):

  • d₀: depth=3 → 3 × 0.05 = 0.15
  • d₁: depth=3 → 3 × 0.1 = 0.3
  • d₂: depth=4 → 4 × 0.05 = 0.2
  • d₃: depth=4 → 4 × 0.05 = 0.2
  • d₄: depth=4 → 4 × 0.05 = 0.2
  • d₅: depth=4 → 4 × 0.1 = 0.4

→ 外部节点总和 = 0.15 + 0.3 + 0.2 + 0.2 + 0.2 + 0.4 = 1.45

总期望代价:

E(T) = 1.35 + 1.45 = 2.8

ei,j------最优期望值

wi,j------区间总权重

物理意义清晰:每一层的代价 = 子树代价 + 本层所有节点概率和(因深度+1)

沿着对角线,从低往右上填

贪心算法

保证choise正确 可被拆分为最优子结构

字符编码

定长编码

可变长编码

☆哈夫曼树

相关推荐
luj_176811 小时前
星火科技助力边远地区防病攻坚
c语言·开发语言·c++·经验分享·算法
geats人山人海14 小时前
数据结构第六章c语言 树的存储下二叉树的概念和存储
c语言·数据结构·算法
漂流瓶jz15 小时前
UVA-12627 奇怪的气球膨胀 题解答案代码 算法竞赛入门经典第二版
算法·图论·递归·aoapc·算法竞赛入门经典·uva·12627
jarvisuni15 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
kobesdu15 小时前
流形上的优化:SO(3)与SE(3)的广义加减法在FAST-LIO中如何简化状态估计
人工智能·算法·fastlio
白狐_79816 小时前
408 数据结构算法题 01:线性表暴力求解保分指南
java·数据结构·算法
乐观勇敢坚强的老彭16 小时前
C++信奥:开关门、开关灯问题
开发语言·c++·算法
冻柠檬飞冰走茶16 小时前
PTA基础编程题目集 7-31 字符串循环左移(C语言实现)
c语言·开发语言·数据结构·算法
Hi李耶16 小时前
【LeetCode】4-寻找两个正序数组的中位数
算法·leetcode·职场和发展