数据结构与算法简记--动态规划实战

动态规划实战


 新的搜索问题:搜索引擎纠错功能实现?

  • 量化字符串相似度--》编辑距离:将一个字符串转化成另一个字符串,需要的最少编辑操作次数(比如增加一个字符、删除一个字符、替换一个字符)
  • 莱文斯坦距离(Levenshtein distance):允许增加、删除、替换字符这三个编辑操作
  • 最长公共子串长度(Longest common substring length):允许增加、删除字符这两个编辑操作。
  • 举例:两个字符串 mitcmu 和 mtacnu 的莱文斯坦距离是 3,最长公共子串长度是 4。

编程计算莱文斯坦距离

  • 问题分析:这个问题是求把一个字符串变成另一个字符串,需要的最少编辑次数。整个求解过程,涉及多个决策阶段,我们需要依次考察一个字符串中的每个字符,跟另一个字符串中的字符是否匹配,匹配的话如何处理,不匹配的话又如何处理。所以,这个问题符合多阶段决策最优解模型。
  • 多阶段求最优解,按前面的理论,先套用回溯算法,再画递归树,分析重复子问题,再看是否可列状态转换方程,再填二维状态表,最后翻译为代码实现。
  • 回溯是一个递归处理的过程:
    • 如果 a[i] 与 b[j] 匹配,我们递归考察 a[i+1] 和 b[j+1]。
    • 如果 a[i] 与 b[j] 不匹配,那我们有多种处理方式可选:
      • 可以删除 a[i],然后递归考察 a[i+1] 和 b[j];
      • 可以删除 b[j],然后递归考察 a[i] 和 b[j+1];
      • 可以在 a[i] 前面添加一个跟 b[j] 相同的字符,然后递归考察 a[i] 和 b[j+1];
      • 可以在 b[j] 前面添加一个跟 a[i] 相同的字符,然后递归考察 a[i+1] 和 b[j];
      • 可以将 a[i] 替换成 b[j],或者将 b[j] 替换成 a[i],然后递归考察 a[i+1] 和 b[j+1]。
    • 翻译为代码:
    • private char[] a = "mitcmu".toCharArray();
      private char[] b = "mtacnu".toCharArray();
      private int n = 6;
      private int m = 6;
      private int minDist = Integer.MAX_VALUE; // 存储结果
      // 调用方式 lwstBT(0, 0, 0);
      public lwstBT(int i, int j, int edist) {
        if (i == n || j == m) {
          if (i < n) edist += (n-i);
          if (j < m) edist += (m - j);
          if (edist < minDist) minDist = edist;
          return;
        }
        if (a[i] == b[j]) { // 两个字符匹配
          lwstBT(i+1, j+1, edist);
        } else { // 两个字符不匹配
          lwstBT(i + 1, j, edist + 1); // 删除a[i]或者b[j]前添加一个字符
          lwstBT(i, j + 1, edist + 1); // 删除b[j]或者a[i]前添加一个字符
          lwstBT(i + 1, j + 1, edist + 1); // 将a[i]和b[j]替换为相同字符
        }
      }
    • 画递归树判断重复子问题:
      • 在递归树中,每个节点代表一个状态,状态包含三个变量 (i, j, edist),其中,edist 表示处理到 a[i] 和 b[j] 时,已经执行的编辑操作的次数。 

      • (i, j) 两个变量重复的节点很多,比如 (3, 2) 和 (2, 3)。对于 (i, j) 相同的节点,我们只需要保留 edist 最小的,继续递归处理就可以了,剩下的节点都可以舍弃。
      • 所以,状态就从 (i, j, edist) 变成了 (i, j, min_edist),其中 min_edist 表示处理到 a[i] 和 b[j],已经执行的最少编辑次数。
    • 状态转移方程
      • 本例中,状态 (i, j) 可能从 (i-1, j),(i, j-1),(i-1, j-1) 三个状态中的任意一个转移过来
      • 列出方程
      • 如果:a[i]!=b[j],那么:min_edist(i, j)就等于:
        min(min_edist(i-1,j)+1, min_edist(i,j-1)+1, min_edist(i-1,j-1)+1)
        
        如果:a[i]==b[j],那么:min_edist(i, j)就等于:
        min(min_edist(i-1,j)+1, min_edist(i,j-1)+1,min_edist(i-1,j-1))
        
        其中,min表示求三数中的最小值。     
    • 状态表
    • 翻译为代码实现
      • public int lwstDP(char[] a, int n, char[] b, int m) {
          int[][] minDist = new int[n][m];
          for (int j = 0; j < m; ++j) { // 初始化第0行:a[0..0]与b[0..j]的编辑距离
            if (a[0] == b[j]) minDist[0][j] = j;
            else if (j != 0) minDist[0][j] = minDist[0][j-1]+1;
            else minDist[0][j] = 1;
          }
          for (int i = 0; i < n; ++i) { // 初始化第0列:a[0..i]与b[0..0]的编辑距离
            if (a[i] == b[0]) minDist[i][0] = i;
            else if (i != 0) minDist[i][0] = minDist[i-1][0]+1;
            else minDist[i][0] = 1;
          }
          for (int i = 1; i < n; ++i) { // 按行填表
            for (int j = 1; j < m; ++j) {
              if (a[i] == b[j]) minDist[i][j] = min(
                  minDist[i-1][j]+1, minDist[i][j-1]+1, minDist[i-1][j-1]);
              else minDist[i][j] = min(
                  minDist[i-1][j]+1, minDist[i][j-1]+1, minDist[i-1][j-1]+1);
            }
          }
          return minDist[n-1][m-1];
        }
        
        private int min(int x, int y, int z) {
          int minv = Integer.MAX_VALUE;
          if (x < minv) minv = x;
          if (y < minv) minv = y;
          if (z < minv) minv = z;
          return minv;
        }

铮哥技巧

  • 拿到一个问题的时候,我们可以先不思考,计算机会如何实现这个问题,而是单纯考虑“人脑”会如何去解决这个问题
  • 人脑比较倾向于思考具象化的、摸得着看得见的东西,不适合思考过于抽象的问题。所以,我们需要把抽象问题具象化。
  • 那如何具象化呢?我们可以实例化几个测试数据,通过人脑去分析具体实例的解,然后总结规律,再尝试套用学过的算法,看是否能够解决。
  • 除此之外,我还有一个非常有效、但也算不上技巧的东西,我也反复强调过,那就是多练。实际上,等你做多了题目之后,自然就会有感觉,看到问题,立马就能想到能否用动态规划解决,然后直接就可以寻找最优子结构,写出动态规划方程,然后将状态转移方程翻译成代码。

编程计算最长公共子串长度

  • 同理,先分析:
    • 每个状态还是包括三个变量 (i, j, max_lcs),max_lcs 表示 a[0…i] 和 b[0…j] 的最长公共子串长度。
    • 回溯的处理思路。
      • 从 a[0] 和 b[0] 开始,依次考察两个字符串中的字符是否匹配。
      • 如果 a[i] 与 b[j] 互相匹配,
        • 将最大公共子串长度加一,并且继续考察 a[i+1] 和 b[j+1]。
      • 如果 a[i] 与 b[j] 不匹配,最长公共子串长度不变,这个时候,有两个不同的决策路线:
        • 删除 a[i],或者在 b[j] 前面加上一个字符 a[i],然后继续考察 a[i+1] 和 b[j];
        • 删除 b[j],或者在 a[i] 前面加上一个字符 b[j],然后继续考察 a[i] 和 b[j+1]。
      • 就是说求 a[0…i] 和 b[0…j] 的最长公共长度 max_lcs(i, j),只有可能通过下面三个状态转移过来:
        • (i-1, j-1, max_lcs),其中 max_lcs 表示 a[0…i-1] 和 b[0…j-1] 的最长公共子串长度;
        • (i-1, j, max_lcs),其中 max_lcs 表示 a[0…i-1] 和 b[0…j] 的最长公共子串长度;
        • (i, j-1, max_lcs),其中 max_lcs 表示 a[0…i] 和 b[0…j-1] 的最长公共子串长度。
  • 给出状态转移方程
    • 如果:a[i]==b[j],那么:max_lcs(i, j)就等于:
      max(max_lcs(i-1,j-1)+1, max_lcs(i-1, j), max_lcs(i, j-1));
      
      如果:a[i]!=b[j],那么:max_lcs(i, j)就等于:
      max(max_lcs(i-1,j-1), max_lcs(i-1, j), max_lcs(i, j-1));
      
      其中max表示求三数中的最大值。
  • 代码实现
    • public int lcs(char[] a, int n, char[] b, int m) {
        int[][] maxlcs = new int[n][m];
        for (int j = 0; j < m; ++j) {//初始化第0行:a[0..0]与b[0..j]的maxlcs
          if (a[0] == b[j]) maxlcs[0][j] = 1;
          else if (j != 0) maxlcs[0][j] = maxlcs[0][j-1];
          else maxlcs[0][j] = 0;
        }
        for (int i = 0; i < n; ++i) {//初始化第0列:a[0..i]与b[0..0]的maxlcs
          if (a[i] == b[0]) maxlcs[i][0] = 1;
          else if (i != 0) maxlcs[i][0] = maxlcs[i-1][0];
          else maxlcs[i][0] = 0;
        }
        for (int i = 1; i < n; ++i) { // 填表
          for (int j = 1; j < m; ++j) {
            if (a[i] == b[j]) maxlcs[i][j] = max(
                maxlcs[i-1][j], maxlcs[i][j-1], maxlcs[i-1][j-1]+1);
            else maxlcs[i][j] = max(
                maxlcs[i-1][j], maxlcs[i][j-1], maxlcs[i-1][j-1]);
          }
        }
        return maxlcs[n-1][m-1];
      }
      
      private int max(int x, int y, int z) {
        int maxv = Integer.MIN_VALUE;
        if (x > maxv) maxv = x;
        if (y > maxv) maxv = y;
        if (z > maxv) maxv = z;
        return maxv;
      }

搜索单词纠错分析:

  • 将搜索词分别与词库中的单词求编辑距离,将编辑距离最小的列出,提示给用户。
  • 优化:
    • 问题:单纯利用编辑距离来纠错,效果并不一定好;词库中的数据量可能很大,搜索引擎每天要支持海量的搜索,所以对纠错的性能要求很高
    • 效果优化:
      • 取出编辑距离最小的 TOP 10,再根据其他因素做决策哪个做为纠错单词。
      • 使用多种编辑距离计算方法,取TOP 10,取交集
      • 统计用户习惯信息:在个人常用词库中求编辑距离最小的单词。
    • 性能优化:
      • 多机部署并行计算,归并统计结果。
      • 词库大:分库分表

猜你喜欢

转载自www.cnblogs.com/wod-Y/p/12152756.html
今日推荐