- 算法
通俗讲,算法是指解决问题的一种方法或一个过程。
严格讲,算法是由若干条指令组成的有穷序列。
- 算法满足以下性质
- 输入:有零个或多个由外部提供的量作为算法的输入;
- 输出:算法产生至少一个量作为输出;
- 确定性:组成算法的每条指令是清晰的,无歧义的;
- 有限性:算法中每条指令的执行次数是有限的,执行每条指令的时间也是有限的。
- 算法复杂性分析
算法复杂性的高低体现在运行该算法所需要的计算机资源的多少上,所需资源越多,该算法的复杂性就越高。分为:时间复杂性和空间复杂性。
渐进时间复杂度:只关心时间复杂度函数的最高阶的阶数
- 递归和分治
1. 递归:直接或间接地调用自身的算法称为递归算法。用函数自身给出定义的函数称为递归函数。
2. 分治法的基本思想:将一个规模为n的问题分解为k个规模较小(一般分解为规模大小相等)的子问题,这些子问题互相独立且与原问题相同。递归地解这些子问题,然后将各子问题的解合并得到原问题的解。
3. 分治法的一般设计模式:
返回类型 分治法(P) { If(问题规模(P)<阈值)基本子算法(P); 分解(P)为更小规模的问题P1、P2、P3、…、Pk; for(int i=1;i<=k;i++) { 解Y[i]=分治法(P[i]); } return 求原问题P的解(Y[1],Y[2],Y[3],…,Y[k]); }
4.常见问题解析:
二分查找:点击打开链接
棋盘覆盖:点击打开链接
合并排序:点击打开链接
快速排序:点击打开链接
循环赛日程表:点击打开链接
- 动态规划
1. 动态规划的基本思想:将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划算法求解的问题,经分解得到的子问题往往不是互相独立的,即将先求解出来的子问题的解保存下来,待求解其它交叉子问题的时候直接使用而不是多次求解。
2. 动态规划算法适合于求解最优化的问题,通常设计步骤如下:
1. 找出最优解的性质,并刻画其结构特征;
2. 递归地定义最优值;
3. 以自底向上的方式计算出最优值;
4. 根据计算最优值时得到的信息,构造最优解。
5. 动态规划算法的基本要素:最优子结构性质;重叠子问题性质
6. 最优子结构性质:问题的最优解包含了其子问题的最优解,即通过求解子问题的最优解,最终可以得到问题的最优解。
7. 重叠子问题性质:使用递归算法自顶向下解此类问题时,每次产生的子问题并不宗师新的问题,有些子问题被反复计算多次。动态规划算法利用这种子问题重叠的性质,对每个子问题只求解一次,而后将解保存下来(表格中),当需要再次求解该子问题的时候,只需要查看结果。
8. 备忘录算法(动态规划算法的变形):不同于动态规划算法自底向上解决问题,备忘录算法的递归方式是自顶向下的。一般来讲,若一个问题的所有子问题都至少要解一次,用动态规划算法;若是子问题空间中的部分子问题不需要求解时,则用备忘录算法。
9.常见问题解析:
最长公共子序列点击打开链接
矩阵连乘问题点击打开链接
0-1背包问题点击打开链接
最优二叉搜索树(转)点击打开链接
- 贪心算法
1. 贪心算法的基本要素:最优子结构性质;贪心选择性质
2. 基本思想:每次做出在当前看来是最好的选择,即不从整体最优上加以考虑,它所做出的选择只是在某种意义上的局部最优选择。利用贪心算法最终得到的解可能是最优解,也可能是最优解的近似解。
3. 贪心选择性质:指所求问题的整体最优解可以通过一系列局部最优的选择即贪心选择来达到。贪心选择可以依赖于已经做过的选择,但不依赖于还未做出的选择,也不依赖于子问题的解。通常自顶向下方式进行,每次贪心选择都将所求问题简化成规模更小的子问题。
4.常见问题解析:
活动安排问题点击打开链接
最优装载问题点击打开链接
最小生成树问题点击打开链接
多机调度问题点击打开链接
- 回溯法
1. 回溯法解题的算法框架:递归回溯,迭代回溯,子集树算法框架,排列树算法框架
2. 回溯法以深度优先方式系统搜索问题解,理论上可以搜索到一个问题的所有解或任一解。
3. 回溯法在问题的解空间树中,按照深度优先策略,从根节点出发搜索解空间树:
算法搜索至解空间树的任一结点时,先判断该结点是否包含问题的解。
如果肯定不包含,则剪枝(跳过对以该结点为根的子树的搜索),逐层向其祖先节点回溯。
否则进入该子树继续按深度优先搜索。
4. 回溯法求问题的所有解时,要回溯到根,且根节点的所有子树都已被遍历才结束。
回溯法求问题的一个解时,只要搜索到问题的一个解就可以结束。
5. 算法框架:
首先应明确定义问题的解空间,且解空间中至少有一个问题的解(或最优解);
基本思想:按深度优先策略从开始节点(根结点)搜索解空间树,这个开始结点成为活结点,同时也成为当前的扩展结点。在当前扩展结点位置纵向移动至一个新结点,这个新结点就成为新的活结点,并成为当前扩展结点。如果当前的扩展结点不能再纵向移动,则当前结点成为死结点,此时应该回溯至最近的一个活结点处,并使这个活结点成为当前的扩展结点。递归搜索直到求出需要的解或解空间中没有活结点为止。
6. 通常解题步骤:
针对所给问题,定义问题的解空间;
确定易于搜索的解空间结构;
以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
7. 递归回溯:
void BackTrack(int t)//t为递归深度 { if(t>n)Output(x); //当t>n表示算法已经到达叶子结点,直接记录或输出可行解x else { for(int i=f(n,t);i<=g(n,t);i++)//f、g表示当前扩展节点下没有搜索过的子树 { x[t]=h(i); if(Constraint(t)&&Bound(t))BackTrack(t+1);//利用剪枝函数和限界函数减少无用的计算 } } }
8. 迭代回溯:
void IterativeBacktrack(void) { int t=1; while(t>0) { if(f(n,t)<=g(n,t)) { for(int i=f(n,t);i<=g(n,t);i++) { x[t]=h(i); if(Constraint(t)&&Bound(t)) { if(Solution(t))Output(x); else t++; } } } else t--; } }
9. 子集树和排列树:
当所给问题是从n个元素的集合S中找出满足某种性质的子集时,相应的解空间树称子集树,如n个物品的0-1背包问题所相应的解空间树就是一棵子集树,这类子集树常由2^n个叶结点,其结点总数为2^(n+1)-1,遍历子集树的任何算法均需要O(2^n)时间;
void BackTrack(int t)//t为递归深度 { if(t>n)Output(x); //当t>n表示算法已经到达叶子结点,直接记录或输出可行解x else { for(int i=0;i<=1;i++) { x[t]=; if(Constraint(t)&&Bound(t))BackTrack(t+1);//利用剪枝函数和限界函数减少无用的计算 } } }
当所给问题是确定n个元素满足某种性质的排列时,相应解空间树称为排列树,排列树通常由n!个叶子结点,因此遍历排列树需要O(n!)时间:
void Backrack(int t) { if(t>n)Output(x); else { for(int i=t;i<=n;i++) { Swap(x[t],x[i]); if(Contraint(t)&&Bound(t)Backtrack(t+1)); Swap(x[t],x[i]); } } }
10.常见问题解析
装载问题点击打开链接
n皇后问题点击打开链接
图的m着色问题点击打开链接
旅行售货员问题点击打开链接
- 分支限界法:
1. 类似回溯法,在问题的解空间上搜索问题的解。
2. 与回溯法的不同:
一般情况下与回溯法的求解目标不同。回溯法求解目标是找出解空间中满足约束条件的所有解,而分支限界法求解目标是找出满足约束条件的一个解,或是从满足约束条件的找出使某一目标函数值达到极大或极小的解(即某种意义上的最优解)。
对解空间的搜索方式不同。回溯法以深度优先的方式搜索解空间,而分支限界法则是以广度优先或以最小耗费优先的方式搜索解空间。
3. 基本思想:以广度优先或最小耗费(最大效益)优先的方式搜索问题的解空间树。从根结点开始将其设置为活结点和扩展结点,然后一次性产生所有的儿子结点,从这些儿子结点中剔除导致不可行解或非最优解的结点,将剩下的结点加入活结点表中,然后从活结点表中选择最有利的结点作为新的扩展结点,将之前的扩展结点从活结点表中删除。一直到找到所需解或活结点表为空为止。
4. 常见方式:
队列式(FIFO)分支限界法:将活结点组织成一个队列,按照先进先出的原则选择扩展结点。
优先队列式分支限界法:将活结点表组织成一个优先队列,并按照优先队列中规定的结点优先级选取优先级最高的下一个结点成为当前扩展结点。
5.常见问题解析
布线问题:点击打开链接
单源最短路径问题点击打开链接