算法分析与设计

本文介绍了算法的关键特征,如有限性、确定性和可行性,以及时间复杂度和几种常见的算法策略如分治法(如二分搜索、快速排序等)、贪心法(如活动安排问题)、动态规划(如背包问题的求解)和回溯法(如0-1背包问题)。此外,还对比了分治、递归、动态规划和贪心的区别,以及分支限界法的应用实例。

算法是什么

算法是求解问题的一系列计算步骤,用来将输入数据转换成输出结果。

算法的目标

正确性、可使用性、可读性、健壮性、高效率与低存储量要求。

算法的基本特征

有限性:一个算法必须总是(对任何合法的输入值)在执行有限步之后结束

确定性:算法中的每一条指令必须有确切的含义,不产生二义性

可行性:算法中的每一条运算都必须是足够基本的,原则上能够精确的执行

输入性:一个算法有零个或多个输入

输出性:一个算法有一个或多个输出

时间复杂度

算法的时间复杂度又分为最好时间复杂度,最坏时间复杂度、平均时间复杂度。其中默认为最坏时间复杂度。

渐进记号

渐近上界记号(O),渐近下界记号(Ω),渐进精确界符号(Θ)。

分治法

【概述】对于一个规模为n的问题,若该问题可以容易的解决(比如n的规模较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解决这些子问题,然后将各子问题的解合并得到原问题的解。

【步骤】将大问题分解为若干个子问题、求解子问题、合并子问题的结果得到最终解。

【理解】分而治之,逐个击破。

应用:二分搜索(时间复杂度O(log n)

      快速排序(时间复杂度O(nlog n),步骤:分解,递归求解,合并;快速排序的运行时间与划分是否对称有关)

       合并排序(时间复杂度 O(nlog n);基本思想:将待排元素分成大小一致相同的两个子集合,分别对两个子集合进行排序,最终将排序好的子集合合并成所要求的排序好的集合)

期盘覆盖;大整数乘法;Strassen矩阵乘法;最接近点对问题(O(nlog n));

蛮力法

【概述】蛮力法是一种简单、暴力、直接解决问题的方法,通常直接基于问题的描述和所涉及的概念定义,依赖计算机的计算速度直接求解。

【步骤】搜索所有的解空间、搜索所有的路径、直接计算、模拟和仿真。

【理解】有的暴力枚举即可,有的直接模拟即可。

动态规划法

【概述】动态规划是一种解决多阶段决策问题的优化方法,把多阶段过程转化为一系列单阶段问题,利用各阶段之间的关系,逐个求解。能用动态规划法的问题一般具有3个性质:最优子结构,无后效性,有重叠子问题。

【步骤】分析最优解的性质,递归的定义最优解;以自底向上或自顶向下的记忆化方式计算出最优值;根据计算最优值时得到的信息,构造问题的最优解。

找出最优解性质,并刻画其结构特性;递归地定义最优值;自底向上求最优值;构造最优解;

【基本要素】最优子结构性质:问题的最优解包含子问题的最优解

子问题重叠性质:在递归法自顶向下求解问题时,每次产生的子问题并不总是新的问题,有些子问题被反复计算多次。

【理解】自底向上的构建过程。

动态规划、递归、备忘录的区别

备忘录的递归方式是自顶向下的,动态规划的递归方式是自底向上的

备忘录与直接递归的控制结构相同,区别在于备忘录给子问题的解建立了备忘录,以备查看。

【算法应用】

矩阵连乘;最大字段和;最大公共子序列;0-1背包

贪心法

【概述】贪心法的基本思路是在对问题求解时总是做出在当前看来最好的选择,也就是说,通过局部最优达到整体最优。通常,这需要证明。

即使贪心算法不能得到整体最优解,但最终结果却是最优解的很好的近似解

【基本要素】贪心选择性质:所求问题的整体最优解可以通过一系列局部最优解的选择即贪心选择来达到。

最优子结构性质:一个问题的最优解包括子问题的最优解。

【步骤】步骤很简单,就是每一步都做出当前状态下最好的选择。

【理解】通过局部最优达到整体最优。

【算法应用】

活动安排问题:结束时间早的优先

背包问题

为什么0-1背包用贪心算法不能求得最优解?

主要原因在于无法保证最终能将背包装满,部分闲置的背包空间使每公斤背包空间的价值降低了。事实上,在考虑0-1背包问题时,应比较选择该物品和不选择该物品所导致的最终方案,然后再作出最好选择。由此就导出许多互相重叠的子问题。这正是该问题可用动态规划算法求解的另一重要特征。

最优装载问题;哈夫曼编码问题;计算平均码长

单源最短路径问题(Dijkstra:设置顶点集合S,并不断地做贪心选择来扩充这个集合)

最小生成树问题;Prim和Kruskal算法;多机调度问题

分治、贪心和动态规划

动态规划以自底向上的方式求解,贪心选择以自顶向下方式求解

贪心和动态规划都有最优子结构性质

背包问题可以用贪心算法,而0-1背包问题不能用贪心算法求解

回溯法

【概述】回溯法实际上是一个类似穷举的搜索尝试过程,主要就是在搜索尝试过程中寻找问题的解,当发现已不满足解条件时,就回退,尝试别的路径。

基本思想:以深度优先方式系统搜索问题解的算法

回溯法是一个既带有系统性又带有跳跃性的搜索算法。

特征:在搜索过程中动态产生问题的解空间。问题的解空间树是虚拟的,并不需要算法运行时构造一个真正的树结构,在任何时刻,算法只保存从根结点到当前扩展结点的路径。

【一些相关概念】

扩展结点:一个正在产生儿子的结点

活结点:一个自身已生成但儿子结点还没有全部生成的结点。

死结点:不能再向纵深方向移动的扩展结点。

避免无效搜索策略——剪枝函数:

约束函数:用约束函数在扩展结点处剪去不满足约束的子树

限界函数:用限界函数剪去得不到最优解的子树

【步骤】确定解空间树,确定结点的扩展搜索规则,以深度优先方式搜索解空间树,并在搜索过程中采用剪枝来避免无效搜索。

解题步骤:针对所给问题,定义问题解空间;确定解空间结构;以深度优先搜索解空间,并在搜索过程中剪枝

【理解】试探所有路径,不符合时则回退到刚才的状态。

【算法框架】

递归回溯和迭代回溯

子集树:当从n个元素的集合中找出满足某种性质的子集时,相应的解空间叫子集树,2^n个叶节点,总节点2^(n+1)-1。遍历时间O(2^n)

排列树:确定n个元素满足某种性质的排列时,相应的解空间树称为排列树,n!个叶节点。

遍历时间O(n!)

【算法应用】

装载问题;

解空间树:子集树;右儿子剪枝:当前载重量+剩余集装箱重量<=当前最优载重量;

0-1背包问题;

解空间树:子集树

右儿子剪枝:当前结点处上界Bound

最大团问题;

解空间树:子集树

右儿子剪枝:确认有足够多的可选择顶点使得算法有可能在右子树中找到更大的团。

n后问题

解空间树:完全n叉树

图的m着色问题

解空间树:完全m叉树(m为颜色数)

旅行售货员问题

解空间树:排列树

递归遍历

分支限界法

【概述】分支限界法类似于回溯法,也是一种在解空间树上搜索解的算法。但在一般情况下,二者的求解目标不同。回溯法求解目标是找出解空间树中满足约束条件的所有解,分支限界法的求解目标是找出满足约束条件的一个解。

基本思想:以广度优先或最小耗费(最大效益)优先的方式搜索解空间树,搜索过程中,每一个活结点只有一次机会称为当前扩展结点。活结点一旦成为扩展结点,就一次性产生所有儿子结点。

【步骤】将根结点加入或活结点队列;取出头结点,作为当前扩展结点;对于当前扩展结点,从左到右生成它的所有孩子结点,用约束条件检查,满足后加入活结点队列;重复上述步骤直至找到一个解或者活结点队列为空。

【分支限界法算法框架】

队列式分支限界法:

将活结点表组织成一个队列,按队列先进先出的原则选取下一个结点为当前扩展结点。不搜索已不可行结点为根的子树,因为搜索时结点的处理是跳跃式的,所以无法递归。

优先队列式分支限界法:

将活结点组织成一个优先队列,按优先队列式中规定的结点优先级选取优先级最高的下一个结点成为当前扩展结点。

【理解】就是个带减枝的BFS。

【算法应用】

单源最短路径

  1. 最小堆
  2. 优先级是结点所对应的当前路长
  3. 约束条件:一种是下界不小于当前最短路径则剪去以该节点为根的子树

另一种是利用结点间的控制关系进行剪枝。两条路径到达同一结点,剪去较长路径所对应的树中的结点为根的子树。

装载问题

队列式

右儿子剪枝:当前结点对应重量+剩余集装箱重量<=当前最优解

搜索到第一个叶结点之前,总有当前最优解为0,上式总成立,右子树测试不起作用。为确保右子树成功剪枝,应该在算法每一次进入左子树的时侯就更新当前最优值。

优先队列式

优先级:从根结点到结点x的路径所相应的载重量再加上剩余集装箱重量之和。

最小堆

最优解的构造:需要保存已构造出的部分解空间树

0-1背包问题

最大堆

优先级:已装袋的物品价值加上剩下的最大单位重量价值的物品装满剩余容量的价值和。

上界:背包问题的最优解

最大团问题

最大堆

优先级:

un(见上界)

上界:

用变量cn表示与该结点相应的团的顶点数;level表示结点在子集空间树中所处的层次;cn +n-level+1作为顶点数上界un的值。

算法的终止条件:遇到子集树中的一个叶结点(即n+1层结点)成为当前扩展结点。

旅行售货员问题

最小堆

优先级:堆中每个结点的子树费用的下界

下界:

以剩余的所有顶点最小出边费用和作为依据。计算图中每个顶点的最小费用出边并用minout记录。如果所给的有向图中某个顶点没有出边,则该图不可能有回路,算法即告结束。

分治VS递归

分治是一种算法,即将大问题分解为若干个子问题、求解子问题、合并子问题,通俗的说就是分而治之,逐个击破。

递归是一种代码的编写技巧,通俗的说递归就是自己调用自己,而且递归代码的执行与计算机的栈结构相契合。

二者是两个层面的概念,如果非要有联系,则可以说分治往往通过递归实现。

动态规划VS贪心

贪心是一种特殊的动态规划,二者的最大差别在于是否记录子问题的结果。

我们不妨从两种算法的相同点说起。动态规划和贪心都是在做一系列的决策,并在结束时得到最优决策,换句话说,它们都是一个不断求解子问题的过程。

为什么说贪心是一种特殊的动态规划呢?这是因为,贪心时当前子问题的最优解一定包含上一个子问题的最优解,因此对前的子问题的最优解不做保留;动态规划时每一步的最优解一定包含之前子问题的最优解,但不一定是上一个,因此需要对之前所有子问题的最优解进行记录。

上面对于“是否对之前子问题最优解进行记录”的叙述有些晦涩,因此我举一个通俗的例子。我买了一杯18元奶茶,假如我手里有不限数量的面值为10元、5元、1元的零钱,那么我该如何付钱呢?使用贪心算法,10+5+1+1+1=18;但是,假如我手里有不限数量的面值为8元、5元、3元的零钱,是不是就无法直接贪心了呢?贪心时并没有进行子问题最优解的记录,因此当我们付了8+8后,并不知道刚刚付了8+8,问题就进入死胡同了,如果是动态规划,因为记录了之前子问题的最优解,所以可以回退并得到8+5,并继续进行。

动态规划因为需要记录子问题的结果,因此具有更高的复杂度。上面的问题其实是一个完全背包问题,贪心O(n),动态规划O(n²),正好印证了这个论述。

动态规划VS分治

动态规划和分治的共同点在于,它们都是将一个大问题分解为了若干个子问题,然后求解子问题。而它们的区别,在于如何由子问题结果构造出最终结果。

从表面上看,分治使用的是递归,是一个自顶向下的过程;动态规划使用的是迭代。是一个自底向上的过程。

如果想更深入的思考二者的区别,比较二者各自的优劣,可以接着看下面的论述——自顶向下的分治如何转化为自底向上的动态规划。它们之间有一个中间过程,即记忆化搜索;记忆化搜索是一个使用了记忆化数组、防止重复计算子问题的分治;动态规划是通过巧妙的填表顺序、避免了栈的使用的记忆化搜索。(看图戳这里)

分治法适合于子问题相互独立的情况,动态规划适用于子问题相互重叠的情况;在出现重复计算子问题的情况下,动态规划性能显著优于分治。

与分治法不同,适合于动态规划求解的问题,经分解得到的子问题往往是不独立的。

回溯VS分支限界

从解空间的搜索方式看,回溯法使用DFS,分支限界法使用BFS。

从存储结点的数据结构上看,回溯法使用栈,分支限界法使用队列或优先队列。

从结点存储特性上看,对于回溯法,活结点的所有子结点被遍历后才能出栈,对于分支限界法,每个结点只有一次成为活结点的机会。

从应用场景上看,回溯法通常用于找出满足条件的所有解,分支限界法通常用于找出满足条件的一个解或者特定意义的最优解。

回溯法与分支限界法的异同:

求解目标不同

回溯法更适合找出解空间树中满足约束条件的所有解;

分支限界法更适合找出满足约束条件的一个解,或是在满足约束条件的解中找出在某种意义下的最优解。

对解空间搜索方式不同:

回溯法以深度优先方式搜索解空间树,每个活结点有多个机会成为扩展结点

分支限界法以广度优先或最小耗费优先方式搜索解空间树,每个活结点只有一次机会成为扩展结点,一旦成为扩展结点就一次性产生所有儿子结点。

相同点:都是在问题的解空间上搜索问题解的算法

实例链接:

(4条消息) 【期末知识点整理】算法设计与分析_算法设计与分析知识点_samarua的博客-CSDN博客

0-1背包问题

蛮力法

思路:使用求幂集的方法求出所有物品组合方式,对于每一种组合,计算总重量sumw和总价值sumv,在sumw小于等于限重W的前提下,用sumv尝试更新所维护的最大价值maxv。

评价:需要求出整个幂集,所以时间复杂度为O(2^n)。

回溯法

思路:对于当前的物品i,有选与不选两种情况,分别用0/1表示,便得到了一棵解空间树,然后DFS搜索该解空间树。设dfs(i, sumw, sumv, choose[]),其中i表示当前为第i个物品,sumw表示当前总重量,sumv表示当前总价值,choose[i]=true表示选,choose[i]=false表示不选。如果选,则下一层为dfs(i+1, sumw+w[i], sumv+v[i], choose[]),如果不选,则下一层为dfs(i+1, sumw, sumv, choose[])。

评价:虽然我们可以通过减枝优化来缩小解空间(如果选择了当前物品且重量已经超过了W,则没有必要继续扩展该路径;如果没选当前物品且即使选了后面的所有物品重量依旧无法达到W,则没有必要继续扩展该路径),但本质还是求出整个幂集,时间复杂度依旧是O(2^n)。

动态规划法

思路:dp[i][j]表示将前i件物品装进限重为j的背包可以获得的最大价值,明确了含义,其实很容易写出状态转移方程dp[i][j] = max(dp[i−1][j], dp[i−1][j−w[i]]+v[i])。

评价:动态规划法相比于回溯法,使用了dp数组记录了子问题的解,从而避免了重复子问题的计算,算法效率大大提高。时间复杂度为O(nW),其中n为物品的数量,W为背包的限重。其实,我们还可以通过滚动数组进行降维优化,但这只是在不影响其他状态转移的前提下进行状态值的覆盖,从而复用数组空间,时间复杂度依旧是O(nW)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值