-
Notifications
You must be signed in to change notification settings - Fork 0
Home
编程基础算法分为五大类:
- 贪婪算法
- 分治算法
- 动态规划算法
- 回溯法
- 分支界定
下面从动态规划开始,逐个讲解基础算法的概念以及用法。
对于一些能够将问题降解成若干子问题的问题,都可以使用动态规划算法加以解决。 动态规划的概念:一般基于一个递推公式以及一个或多个初始状态,一个问题可以分解为多个子问题,一个子问题可以由前一个问题的解推出。由于省去了很多重复计算,动态规划的时间复杂度是多项式时间。
动态规划和递归本质上都需要若干初始状态和状态转移方程,这也是动态规划算法中最重要的两个概念。具体的解释如下: 状态:根据子问题定义状态 状态转移方程:其实是描述状态是如何转移的,包括当前状态和前一个状态。 因此,学习动态规划算法最重要的就是找出状态定义和状态转移方程
在实际使用中,首先确定问题能否使用动态规划算法求解,主要思路便是判断该问题能否划分为多个本质上为同一类问题的子问题,也就是说,一个大问题划分为多个规模较小的问题后,其本质还是同一个问题,只是规模变小。而判断是否为同一类问题的方法则是判断一个子问题是否能有它前一个子问题推导的出,如果能,则是,反之,则不是。 状态的定义和状态转移方程的确定是能够灵活使用动态规划算法的关键,状态的定义一般依赖于子问题的定义,子问题的求解结果一般可以作为一个状态,当前状态的确定由上一个状态推导而出。以最长非降子序列为例,讲述状态确定和状态转移过程:
LIS问题描述:a[1..n]为原始序列,d[k]表示长度为k的不下降子序列末尾元素的最小值,len表示当前已知的最长子序列的长度。 对于这问题,长度为n的原始序列的LIS问题可以降解为长度小于n的LIS问题,这样一步步降解最终确定出初始状态长度为1的序列的LIS的长度为1,那么状态则是第i个元素之前的子序列的LIS,记为d[i]。那么d[j]到d[i+1](j<i)的推导就是状态转移,此时的需要注意的是转移套路并不是只与i的前一个元素比较,而是与i之前的所有元素j比较。两种可能情况,如果a[i]>a[j],同时d[i]比d[j]加1要小,此时进行状态转移,d[i]=d[j]+1。以此类推,最终得到LIS的长度。
分治算法的基本思想是,将问题分解为多个规模较小且问题形式相同的子问题,一步步的分解下去,直到问题小到一定规模能很简单的求解,然后将各个子问题的解反向合并为原问题的解。能够套用分治算法的问题,一般能够将问题分解为形式相同的子问题,同时子问题之间没有包含关系,相互独立的。比较经典的分治算法的应用案例主要有:归并排序、二分查找、快速排序等。
把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:1. 使用约束函数,剪去不满足约束条件的路径;2.使用限界函数,剪去不能得到最优解的路径。问题的关键在于如何定义问题的解空间,转化成树(即解空间树)。解空间树分为两种:子集树和排列树。两种在算法结构和思路上大体相同。