《算法设计与分析》学习笔记

本文详细介绍了算法的基本概念,包括算法的定义、复杂度分析中的渐近记号,如O、Ω、Θ、o、ω。接着讨论了分治策略及其应用,如递归树、主方法。此外,文章涵盖了动态规划的步骤和最优子结构,以及贪心算法、回溯法和堆排序。在图论部分,讲解了图的性质、最小生成树的Kruskal和Prim算法,以及最大流最小割定理。最后,文章提及了P问题和NP问题以及停机问题的不可解性。
摘要由CSDN通过智能技术生成

目录

算法基本概念

算法的定义

算法复杂度分析

渐近记号

①渐近上界记号O

②渐近下界记号Ω

③渐近紧确界记号 Θ

④非渐近紧确上界记号o

⑤非渐近紧确下界记号ω

渐进记号极限定义

分治

分治步骤

递归树

​编辑代入法

主方法

改变变量

二叉树

建堆

堆排序

回溯法

0-1背包问题

动态规划

动态规划步骤

最优子结构

重叠子问题

最优性原理

证明:0-1背包问题Knap(1,n,c)满足最优性原理

证明:最长路径问题不满足最优性原理。

贪心

活动选择问题

哈夫曼编码

摊还分析

聚合法/合计法

核算法/记账法

势能法

图论

拓扑排序

并查集

Kruskal算法

prim算法

流网络

最大流最小割定理

Ford-Fulkerson方法

P问题和NP问题

停机问题


算法基本概念

程序=数据结构+算法

算法的定义

算法是若干指令的有穷序列,满足性质:

①输入:有外部提供的量作为算法的输入。

②输出:算法产生至少一个量作为输出。

③确定性:组成算法的每条指令是清晰,无歧义的。

④有限性:算法中每条指令的执行次数是有限的,执行每条指令的时间也是有限的。

⑤可行性: 算法是能够有效解决问题的。

问题求解过程

算法复杂度分析

一个算法的运行时间是指在特定输入时所执行的基本操作数或步数。

插入排序例子

假定每次执行第i行所花的时间是常量ci;对 j = 2, 3, … n, 假设tj表示对那个值 j 执行while循环测试的次数。

当一个forwhile循环按通常的方式(由于循环头中的测试)退出时,执行测试的次数比执行循环体的次数多1。

则插入排序的运行时间为所有times与对应cost之积的和,即取决于不确定的tj。

最好情况下tj的值为1,最坏情况下tj的值为j,平均情况下tj的值为j/2。

渐近记号

①渐近上界记号O

渐近地给出一个函数在常量因子内的上界:

O(g(n)) = { f(n) : 存在正常量cn0,使得对所有nn0,有0 ≤ f(n) ≤ cg(n)}

O可用于标识最坏情况运行时间

②渐近下界记号Ω

渐近地给出一个函数在常量因子内的下界:

Ω(g(n)) = { f(n) :存在正常量 c n0,使得对所有nn0,有 0 ≤ cg(n) ≤ f(n) for all nn0 }

Ω可用于标识最佳情况运行时间

③渐近紧确界记号 Θ

渐近地给出了一个函数的上界和下界:Q(g(n)) = { f(n) : 存在正常量c1, c2和n0,使得对所有nn0,有0 ≤ c1 g(n) ≤ f(n) ≤ c2 g(n)}

形式化证明n2/2 - 3n = Q(n2)

即确定正常数c1, c2和n0,使得对所有nn0,有:

n2除不等式得:

右边不等式在n ≥ 1,c2 ≥ 1/2时成立,左边不等式在n ³ 7,c1≤1/14时成立,选c1 = 1/14,c2 = 1/2,n0=7时上式即可成立。

④非渐近紧确上界记号o

o(g(n)) = { f(n) | 对于任何正常量c > 0,存在常量n0  > 0使得对所有n ³ n0,有0 ≤ f(n) < cg(n) }

⑤非渐近紧确下界记号ω

ω(g(n)) = { f(n) | 对于任何正常量c > 0,存在常量n0 >0使得对所有n ³ n0,有0 ≤ cg(n) < f(n) }

f(n)∈ω(g(n))  当且仅当  g(n)∈o(f(n))

渐进记号极限定义

 

分治

分治步骤

将一个问题分解为与原问题相似但规模更小的若干子问题,递归地解这些子问题,然后将这些子问题的解结合起来构成原问题的解。这种方法在每层递归上均包括三个步骤:

①Divide(分解):将问题划分为若干个子问题

②Conquer(求解):递归地求解子问题;若子问题规模足够小,则直接解决之

③Combine(组合):将子问题的解结合成原问题的解

 n!的递归式是什么 ?

递归树

代入法

T(n) = T(n/2) + n²

假设T(n)∈O(n²),证明T(n)≤cn²:

主方法

主方法可解如下形式的递归式

T(n) = aT(n/b) + f(n)

主定理

 

 关键是比较 f(n) 和 nlogba,看谁大:

①f(n)小,case1成立

②差不多大,case2成立

③f(n)大,case3成立

case1例子:

 case2例子:

 case3例子:

 有些情况主方法不一定适用。

改变变量

二叉树

有序树  是一棵有根结点的树,其中每个结点的孩子结点都是有序的 (第一、二个孩子结点等等)。

二叉树  是一棵有根结点的有序树,其中每个结点最多有两个孩子结点,并且左孩子结点和右孩子结点可区分 (也就是说他们有不同属性)。

结点 的深度 是从这个结点到根结点的简单路径上边的数目

树 的深度 是树中所有结点最大的深度

结点 的高度 是从该结点到一个叶子结点的最长简单路径的边数

树  的高度 是树的根结点的高度= 树的深度。

叶子结点:没有孩子的结点,也称外部结点。

内部结点:非叶子结点。

结点的度:结点孩子的数目。

完全二叉树 是一棵所有叶子结点在同一深度,而且每个非叶节点都有两个孩子结点的二叉树。

近似完全二叉树  深度为 d,只考虑深度为 d – 1 的部分是完全二叉树,深度为 d 的结点都在靠左部分。

建堆

从n/2向下取整开始调整堆

建堆的代价为O(n)。

堆排序

在数组上建一个最大堆。

从根结点开始 (它的值最大), 算法将最大值放到数组中正确的地方,例如将它与数组中最后一个元素交换位置。

“去掉” 数组中最后一个元素 (已经在正确的位置), 在新的根结点上调用 Max-Heapify,新的根结点满足最大堆性质。

重复“去掉” 操作直到只剩一个结点 (也就是最小值), 得到已经排序的数组。

回溯法

0-1背包问题

动态规划

动态规划步骤

动态规划的思想实质是分治思想解决冗余

求解步骤

①找出最优解的性质,并刻画其结构特征;

②递归地定义最优值(写出动态规划方程);

③以自底向上的方式计算出最优值;

④根据计算最优值时记录的信息,构造最优解。

注:

-步骤①~③是动态规划算法的基本步骤。如果只需要求出最优值的情形,步骤④可以省略

-若需要求出问题的一个最优解,则必须执行步骤④,步骤③中记录的信息是构造最优解的基础。

动态规划的有效性依赖于问题具有两个重要性质

最优子结构

问题的最优解是由其子问题的最优解来构造,则称该问题具有最优子结构性质。

重叠子问题

在用递归算法自顶向下解问题时,每次产生的子问题并不总是新问题,有些子问题被重复计算多次。动态规划算法利用子问题的重叠性质,对相同子问题只求解一次,将其解保存在一个表格中,以后该子问题的解直接查表。

最优性原理

求解问题的一个最优策略的子策略序列总是最优的,则称该问题满足最优性原理。

证明:0-1背包问题Knap(1,n,c)满足最优性原理

证明:最长路径问题不满足最优性原理。

贪心

活动选择问题

 

 

哈夫曼编码

 

 

 

 

摊还分析

聚合法/合计法

栈操作分析

核算法/记账法

栈操作

 

势能法

栈操作

 

 

图论

入度:有向图中连向该节点边的条数。

出度:有向图中从该节点连出的边的条数。

度:节点出度与入度之和,即连接该节点边的条数。

简单图:没有多重边,没有自环。

简单路径:对于一条由连续边与节点组成的路径,没有经过重复的节点。

连通图:对于一个无向图,任意两个节点之间都存在一条路径连接。

强连通图:对于一个有向图,任意2个节点之间都存在一条有向路径连接。

稀疏图:|E|≈|V|

稠密图:|E|≈|V|²

完全图:对于一个有向或者无向图,任意两个节点之间都有边邻接(对于有向图需要两个方向 的边)。

拓扑排序

并查集

 

Kruskal算法

克鲁斯卡尔算法的思想是逐步选择边来构建最小生成树。具体步骤如下:

  1. 将图中的所有边按照权值从小到大进行排序。
  2. 从权值最小的边开始,依次考虑每条边:
    • 如果该边的两个顶点不在同一个连通分量中,则选择该边加入最小生成树,并合并这两个顶点所在的连通分量。
    • 如果该边的两个顶点已经在同一个连通分量中,则舍弃该边,以避免形成环路。
  3. 重复步骤2,直到最小生成树中包含图中的所有顶点为止。

通过这种方式,克鲁斯卡尔算法能够找到一个连通图的最小生成树,并且保证总权值最小。算法的关键在于选择边的过程中保证不会形成环路,以确保最终生成的树是连通的。

prim算法

Prim算法的思想如下:

  1. 选择一个起始顶点作为初始集合,可以是任意一个顶点。
  2. 将该起始顶点加入到最小生成树的顶点集合中。
  3. 从已加入最小生成树的顶点集合中,选择一个顶点u,将与顶点u相连且权值最小的边(u, v)加入到候选边集合。
  4. 从候选边集合中选择权值最小的边(u, v),将顶点v加入到最小生成树的顶点集合中,同时将边(u, v)加入到最小生成树的边集合中。
  5. 重复步骤3和步骤4,直到最小生成树包含图中的所有顶点为止。

通过这种方式,Prim算法逐渐扩展最小生成树的顶点集合,保证每一步都选择了与已加入顶点集合具有最小权值的边。最终得到的最小生成树是以起始顶点为根节点的一棵树,并且总权值最小。

需要注意的是,Prim算法的实现通常需要使用优先队列(最小堆)来高效地选择权值最小的边。

流网络

流网络是一个有向图G=V,E),其中每条边(u,v)均有一非负容量c(u,v)0。如果(u,v)不是E中的边,则假定c(u,v)=0。流网络中有两个特别的顶点:源点s汇点t。假定每个顶点均处于从源点到汇点的某条路径上。

残留网络

增广路径

最小割

指将原有网络G(V, E)划分成两个不相交的集合(A, B),使得A中的所有节点都无法到达B中的所有节点,在满足这一条件的情况下,将划分这两个集合的所有边的容量之和称为最小割。

最大流最小割定理

最大流最小割定理的证明

Ford-Fulkerson方法

Ford-Fulkerson方法通过不断地在残留网络中搜索出增广路径,并根据增广路径更新剩余容量的方式来寻找最大流。每次增广的过程中,都会选择一条从源点到汇点的路径,然后将这条路径上的流量增加到当前的最大流中。随着可行流的不断增加,残留网络中的剩余容量也不断减少,直到找不到增广路径为止。

P问题和NP问题

NP问题是指“非确定性多项式时间可解问题”(Nondeterministic Polynomial time problem)的缩写。它是理论计算机科学中的一个重要概念,与问题的求解复杂性相关。

在计算机科学中,问题可以分为两类:P问题和NP问题。P问题是可以在多项式时间内解决的问题,也就是说存在一种算法,以输入规模的多项式函数形式运行时间来解决该问题。而NP问题则是指可以在多项式时间内验证解的问题,也就是说如果给定一个解,可以在多项式时间内验证这个解是否正确。

换句话说,对于一个给定的NP问题,如果我们有一个解,我们可以在多项式时间内验证这个解的正确性。然而,我们并不能在多项式时间内找到一个解。这是因为NP问题通常是非确定性多项式时间可解的,意味着我们可以猜测一个解并在多项式时间内验证它,但没有一种确定性的算法能够在多项式时间内找到一个解。

经典的NP问题包括旅行商问题(TSP)、背包问题(Knapsack Problem)、图着色问题(Graph Coloring Problem)等。这些问题在实际中非常重要,但目前还没有找到一种高效的确定性算法来解决它们。如果能够在多项式时间内找到NP问题的解,那么P问题和NP问题将等价,这是一个著名的数学难题,被称为P与NP问题的克里伯尔猜想。

需要注意的是,虽然NP问题的解不能在多项式时间内找到,但如果我们得到了一个解,我们可以在多项式时间内验证其正确性。因此,一些NP问题可以通过近似算法或优化策略来获得接近最优解的解决方案。

停机问题

停机问题(Halting problem)是指确定一个给定的计算机程序能否在有限步骤内停止运行的问题。简而言之,停机问题是要判断一个程序是否会在某个时刻停止执行,或者会一直进行下去。

停机问题被证明是不可解的,也就是说,不存在一种通用算法可以判断任意程序是否会停止。这个结论由图灵在1936年提出,并通过其著名的停机问题证明(Turing's halting problem proof)得到了证实。

停机问题的重要性在于它揭示了计算机理论中的局限性。尽管现代计算机具有强大的计算能力,但某些问题是无法通过计算机自动解决的。停机问题成为计算机科学中的一个经典例子,被广泛应用于理论计算机科学和计算机算法的研究中。

停机问题的证明是通过构造反证法来展示停机问题的不可解性。这个证明由图灵在1936年提出,被称为图灵停机问题证明(Turing's halting problem proof)。

证明的思路如下:

  1. 假设存在一个算法或程序H,可以判断任意给定程序是否会在有限步骤内停止。

  2. 假设程序H接收两个输入:P(要判断是否停机的程序)和I(程序P的输入)。

  3. 程序H首先尝试运行程序P并观察它的行为。如果程序P在有限步骤内停机,则程序H返回"停机"。否则,程序H进入一个无限循环。

  4. 接下来,构造一个新的程序D。程序D的功能是根据输入参数来模拟程序H的行为。即,当程序D接收到输入P和I时,它会调用程序H并将输入设置为P和I。如果程序H返回"停机",那么程序D会进入一个无限循环;如果程序H进入无限循环,那么程序D会停机。

  5. 现在,我们将程序D作为自己的输入参数传递给程序D。也就是说,我们运行程序D,并将D作为输入传递给D。

  6. 根据程序D的定义,它会模拟程序H的行为。如果程序H(此时是D自己)返回"停机",那么程序D会进入无限循环;如果程序H进入无限循环,那么程序D会停机。

  7. 这就导致了矛盾:根据程序D的行为,无论它是停机还是进入无限循环,都会与程序H的判断相矛盾。

由此可见,假设存在一个算法或程序H来解决停机问题是不成立的。因此,停机问题是不可解的。这个证明揭示了计算机无法判断任意程序是否会停机的困境,成为计算机科学中的经典结果之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MaolinYe(叶茂林)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值