算法设计与分析7_快速排序 and 动态规划DP and 贪心
苏大(suda)——算法设计与分析
快排
/## 快排的时间确界分析
/## 快排的随机化版本
随机化算法(randomized algorithm),是一种算法,在算法中使用了随机函数,且随机函数的返回值直接或者间接的影响了算法的执行流程或执行结果。
降低最坏情况的概率
/## 快排平均情况下的时间复杂度
动态规划
动态规划主要用于优化问需求解,带求出问题的很优解
动态规划和分治的区别与联系:
◇相同点:都是通过将求解问题划分为若干小规模的子问题,进行求解, 再合并子问题的最优解,来解决整个问题的解
◇不同点:
1)分治法是将大问题划分为相似但小规模的子问题,递归地求解子问题, 然后将子问题的解合并,分治法是递归地求解了所有划分出的子问题, 包括了那些重复的子问题!因此仅适用于子问题相互独立的场景。
2)当分解问题非独立时,更应该采用动态规划:将求解过的子问题答案都 保存起来,从而保证每个分解出的子问题只会被求解一次。
当分解问题非独立时,即他们共享子子问题时, 可采用动态规划
因为此时分治法将重复地解这些共同的子子问题,形 成重复计算,而动态规划对每一子子问题只做一次计 算,然后将答案存储在一表中(这就是programming 含义,像节目单一样),故可避免重复计算(critical issue:一般方法是自底向上)
Note: Fibonacci数递归式求解
动态规划常用Bottom Up(分治常用是Top down)
考试重点 备忘录算法
最优子结构
最优子结构指的是,问题的最优解包含子问题的最优解。对于一个给定的问题,当该问题可以由其子问题的最优解获得时,则该问题具有“最优子结构”性质。
矩阵链乘问题P210
两个矩阵A和B只有相容(compatible),即A的列数等于B的行数时,才能相乘。如果A 是p×q的矩阵,B是qXr的矩阵,那么乘积C是pr的矩阵。计算所需时间由第8行的标 量乘法的次数决定,即pqr。
矩阵积的完全括号化
P215
考试待选P219
P220 证明时间开销
同时满足最优子结构和重叠子问题,意味着大概率就是动态规划的求解
P213需要掌握
需要掌握
最优化法则:最后化问题的任一示例的最优解都是尤其子实例最优解构成的。
一般来说 大多数请胯下最优化法则成立,极少数请情况下例外(最长路径问题)。
最长路径问题
最长路径问题不是最优子结构
矩阵链乘的备忘录实现
即memoization方法(记忆,备忘录)。
关键点:
if m[i,j]<INF return m[i,j]
如果每个子问题都需要求解-自底向上
如果不是每个子问题都求解-备忘录实现
LCS最长公共子序列问题
只需要求出一个最长公共子序列即可
1. 证明具有最优子结构
首先易证明穷举无效,m每个元素的子序列数
LCS最优子结构:
2. 子问题的递归解
3. 计算最优解
4. 构造一个LCS
例题p225
多边形的最佳三角剖分
默认是凸多边形
与矩阵连乘问题完全一致
01背包问题
近似算法通常与NP-hard问题相关
整型规划问题也一般是NPC问题,一般转换成线性规划问题
01背包是NPC问题
🌟🌟🌟!!!!!!!!!01背包的备忘录版本实现!!!!!!!!!!!!!!🌟🌟🌟
时间复杂度 n是商品数量,C是背包容量
其实是伪多项式时间,当C是 则复杂度是
01背包的备忘录版本🌟
什么是备忘录方法?它同动态规划法相比主要不同点是什么?请指出动态规划法和备忘录方法 各自的适用范围。
(注:平常说的“动态规划”是自 底向上的动态规划,“备忘录方法”是自顶向下的动态规划。)答:备忘录方法是动态规划算法的变形,它通过 分治思想对原问题进行分解,以存储子问题的解的方式解决冗余计算,并采用自顶向下的递归方式获取问题的最终解。
与动态规划算法的不同之处是动态规划算法的递归方式是自底向上递归求解,而备忘录方法的递 归方式是自顶向下递归求解。
当一个问题的所有子问题都至少要解一次时,使 用动态规划算法。
当子问题空间中的部分子问题不需要求解时,使 用备忘录方法。
使用备忘录方法解决0-1背包问题:
- 跟直接递归很相似,该算法能将递归遇到的子问题的解保存在一个表中,以便下一个递归遇到同样的子问题时快速求解。
- 为了区分一个子问题是否已经求解,可以通过查表的方式来判断,若子问题对应的表中元素的值为一个初始特殊值,说明该子问题还未求解;否则,表明该子问题曾经已求解过,直接获取子问题的解,不需要递归求解该值。
KEY:memo[n][c]就是在n个物品中选择不超过c容量的最大价值,即为背包问题的解。
因此只需要返回memo[n][c]就能代表他是正确解。
伪代码
// N=物品种类 W=背包重量 w[N+1]=物品重量 v[N+1]=物品价值 int memory[N+1][W+1] = 0; int knapsack_memory_dp(int N,int W){ if(memory[N][W]!=0)//使用备忘录 return memory[N][W]; if(N==0||W==0)//初始化 return 0; if(W>=w[N])//装物品 memory[N][W]=max(dp(N-1,W),dp(N-1,W-w[N])+v[N]); else//不装物品 memory[N][W]=dp(N-1,W); return memory[N][W]; } int main(){ cout<<dp(N,W)<<endl; return 0; }
点击查看 正式c代码
//!!!!!自行导包!!!! using namespace std; const int N=3; //物品种类 const int W=5; //背包重量 int w[N+1]={0,1,2,3}; //物品重量 int v[N+1]={0,6,12,10}; //物品价值 int memory[N+1][W+1]; int max(int a,int b) { return a>b?a:b; } int dp(int N,int W){ if(memory[N][W]!=0)//使用备忘录 return memory[N][W]; if(N==0||W==0)//初始化 return 0; if(W>=w[N])//装物品 memory[N][W]=max(dp(N-1,W),dp(N-1,W-w[N])+v[N]); else//不装物品 memory[N][W]=dp(N-1,W); return memory[N][W]; } int main(){ int ans; ans = dp(N,W); return 0; }
3.备忘录算法与动态规划算法的区别有:
(1)备忘录方法的递归方式是自顶向下,而动态规划算法则是自底向上递归的。
(2)备忘录方法的控制结构与直接递归方法的控制结构相同,而动态规划算法的控制结构与循环迭代的控制结构相同。
(3)备忘录方法为每个解过的子问题建立了备忘录以备需要时查看,避免相同子问题的重复求解;而动态规划算法则是建立一个表格,从最小的子问题开始向上求解较大的子问题,把每一个子问题的解全部填入表格中,直到表格中出现原问题的解为止。它们两者的共同特点是对子问题的求解都只解了一次,并记录了答案。
(4)当一个问题的所有子问题都至少要解一次时,用动态规划比用备忘录方法要好,此时,动态规划算法没有任何多余的计算。当子问题空间中的部分问题可不必求解时,用备忘录方法较有利 ,因为从其控制结构可以看出,该方法只解那些确实需要求解的子问题。
算法改进 - 跳跃点
优化上面背包容量太大导致的时间复杂度的问题,改进后复杂度上界是
贪心算法
🌟🌟考试关键点:
- Activity-selection problem 活动选择 chap 16.1
- Fractional Knapsack
对绝大多数优化算法 贪心算法都找不到最优解。大多数能在用贪心算法找到最优解的 都在数据结构学过。
大多数调度问题可以用贪心算法求解。
什么时候选择贪心算法?
- 问题符合 贪心选择最优性。具体来说就是 局部最优就是全局最优
- 此外还有 最优子结构
贪心算法一般总是存在相应的动态规划解,但是贪心算法的效率最高。
原因:
- 最输入做预处理
- 选择合适的数据结构(如 优先队列,大根堆)
提醒: 可以通过 拟阵 证明是否可以使用贪心算法。
不考:判定change-making problem的贪心算法是不是最优解,求解change-making problem的DP求解
普里姆算法(Prim)构建MST
为何这样的贪心选择可以保证最优?
活动选择问题🌟
问题描述:
假设有一组活动,每个活动都有一个开始时间和结束时间。如何选择最多的活动,使得这些活动之间互不冲突(即没有重叠时间)?
贪心策略:
对于活动选择问题,一个直观的贪心策略是:每次选择最早结束的活动。
🌟🌟🌟考试的时候只需要给出最终的形态即可,写迭代版本🌟
理解迭代版本
迭代版本的核心在于不断地比较当前活动与已选活动集合中最后一个活动的结束时间,从而决定是否将当前活动加入集合。这个过程会一直持续到所有活动都被考虑过。
迭代版本伪代码
//s 开始时间集合, f 结束时间集合, n 活动的数量 //假定所有活动已经按照 结束时间 从小到大排序 GREEDY-ACTIVITY-SELECTOR(s, f, n) A = {a_1} k = 1 for i = 2 to n if s[i] ≥ f[k] // is a_m in S_k A = A ∪ {a_i} // yes, so choose it k = i // and continue form there return A
例题(不考)
任务调度
只有一个机器。有若干个任务,每个任务有一个执行时间,如何求解最优解,为什么?(如何证明)
如果不对,那么说明 插入一个不是从小到大排序的,是最优解。那么只需要证明 交换 插入的任务 后,时间更小即可。
带有截止期调度安排
每个job需要1个单位时间,任务关联两个参数:ddl(截至启动时间),profit(收益),即在ddl前完成会获得profit,要使总profit最好,如何求解?
(不需要每个任务都需要安排)
解决思路:profit从大到小排序,对每个进行判断 是否可以加入 可行性序列,如果可以加入,则加入。
多级调度问题
如果我们不排序,直接按。。。。,则可以得到2xx近似算法。
本文作者:kingwzun
本文链接:https://www.cnblogs.com/kingwz/p/18573748
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步