算法分析与设计学习总结_2

算法分析与设计

四、动态规划

(1)动态规划

​ ① 基本思想:n将原问题分解为若干个子问题,先求子问题的解,然后从这些子问题的解得到原问题的解。这些子问题的解往往不是相互独立的

​ ② 基本要素:最优子结构和重叠子问题。

​ ③ 最优子结构性质:最优解的子结构也是最优的。问题的最优解是由其子问题的最优解所构成的。(最优子结构性质使我们能够以自底向上的方式递归地从子结构的最优解构造出问题的最优解)

​ ④ 重叠子问题:子问题之间并非相互独立的,而是彼此有重叠的。(因为子问题重叠,所以存在着重复计算。这样就可以用填表保存子问题解的方法来提高效率。)

​ ⑤ 动态规划实现通常是自底向上的递归形式,但也可以采用自上而下的形式(备忘录方法)。

(2)消除重复计算问题

c++ 复制代码
Void MatrixChain(int p, int n, int **m, int **s)
 { for (int i = 1; i <= n; i++) m[i][i] = 0; 
   //将对角线元素赋值为零,即单个矩阵计算量为0
   for (int r = 2; r <= n; r++) 
       for (int i = 1; i <= n -- r +1; i++) {
          int j = i + r -- 1;
(5)     m[i][j] = m[i+1][j] + p[i--1]*p[i]*p[j];
          //计算A[i, j] = A[i: i] A[i+1: j]
          s[i][j] = i;  //记下断点i
(7)     for (int k = i + 1; k < j; k++) {
             int t = m[i][k] + m[k+1][j] + p[i--1]*p[k]*p[j]; 
             //对i<k<j, 逐个计算A[i, j] = A[i: k] A[k+1: j]
             if (t < m[i][j]) {m[i][j] = t; s[i][j] = k;}
             //记下较小的m[i][j]及相应的断点k 
}}}

(3)动态规划的备忘录方法

(4)多边形游戏求解

(5)动态规划求序列相似性

五、回溯法

5.1搜索

(1)三种搜索算法比较:

(三者对 待搜索表 的控制方式不同)

​ ① 广度优先搜索的优点是一定能找到解;缺点是空间复杂性和时间复杂性都大。(队列)

​ ② 深度优先搜索的优点是空间复杂性和时间复杂性较小;缺点是不一定能找到解。(栈)

​ ③ 启发式搜索是最好优先的搜索,优点是一般来说能较快地找到解,即其时间复杂性更小;缺点是需要设计一个评价函数,并且评价函数的优劣决定了启发式搜索的优劣。(元素按照某方式排序)

5.2 回溯法

(1)回溯法

​ ① 深度优先搜索就是回溯法(栈)。可以递归实现,也可以迭代实现。

​ ② n回溯法通常求解三类问题:

​ 求排列时间复杂性为O(f(n)n!);

​ 求子集:时间复杂性为O(f(n)2n);

​ 求路径:时间复杂性为O(f(n)kn)。

​ 这里f(n)为处理一个结点的时间复杂性。

​ ② 递归回溯法的一般形式

c++ 复制代码
Try(s){
 做挑选候选者的准备;
 while (未成功且还有候选者) {
   挑选下一个候选者next;  
   if  (next可接受) {
       记录next;
        if (满足成功条件) {成功并输出结果}
        else Try(s+1);
        if (不成功) 删去next的记录; }}
 return 成功与否}

​ ③ 迭代回溯的一般形式

c++ 复制代码
Backtrack(Tree T) {
  unfinish = true;  L.Push(T.root);  
  while (unfinish || L≠Φ) {
    a = L.Pop( ); 
      //若栈顶元素是末尾标记,说明这个路径已经到头了,需要回溯一步。
    if (a is the last mark) backastep( );
   else  if (a is good) {record(a);
             //若a是目标节点,则结束搜索并输出求解的路径。
            if (a is goal) {unfinish = false; output( );}
             //若a不是目标节点且a有后即,则将a的后继压入栈中。
            else if (a has sons) L.Push-Sons(a)  
      //这里的L.Push-Sons(a)要先压入末尾标记,然后再压入后继结点。
                   else move-off(a);}}}
//若a不是目标节点又没有后继,则将a从解的路径中删除。

​ ④ 为了便于判断最后一个儿子,需要设计一个末尾标记,每次压栈时,先压入末尾标记。

(2)数的全排列问题

​ ① 递归回溯解决

​ ② 迭代回溯解决

c++ 复制代码
Backtrack(Tree T) {
  unfinish = true; i = 1; L.Push(1, 2, ..., n,-1);
  while (unfinish || L≠Φ) {
    a = L.Pop( ); 
    if (a = = -1) {i-- --; a = rec[i]; rec[i]=0;used[a]=0;}
    else  if (used[a]==0) 
            {rec[i]=a; used[a]=1;
              if (i = = n) {unfinish = false; output(rec);}
              else {i++;  L.Push(1, 2, ..., n, -1);} 
}}}

​ ③ 时间复杂性:O(n!)

(3)n皇后问题

(4)TSP问题

(5)0-1 背包问题

六、分支限界法

(1)分支限界法

​ ① 分支限界法就是最佳优先(包括广度优先在内)的搜索法。

​ ② 策略:采用有序的队列,即每次优先搜索评价函数值最小的结点,从而希望较快地找到最佳的路径或排列。此方法又称 界限剪支法。

​ ③ 分支限界法的两个要点:评价函数的构造(应能正确有效地压缩状态空间)和搜索路径的构造。

​ ④ 优点:与其它算法相比,时间复杂性无本质区别。但好的评价函数可有小的常数,提高了效率。

(2)评价函数的构造

(3)搜索路径的构造

(4) 单源最短路径问题