zoukankan      html  css  js  c++  java
  • 动态规划(1)——最长子序列(LCS)问题

    最长子序列问题:从中找出最长的字符序列,比如: cnblogs和belong。这两个字符串的最长子序列就是blog。

    动态规划:通过分解大问题,不断的将大问题变成小问题,最终整合所有解,得出最优解(和递归有点相似,但是递归的时间复杂度太过大,通过动态规划的解决,可以将一部分的时间复杂度调整成空间复杂度)

    Xm = {x1,x2,x3...xm},Y= {y1,y2,y3,...yn},求X和Y的最长子序列。

    1,假设Z = {z1,z2,..., zk}是X和Y的最长子序列,那么可以看出(解1)

    • 如果xm = yn ,那么Zk-1 就是Xm-1和Yn-1的LCS(因为最后一个元素相等且已经规定Zk是Xm和Yn的LCS,所以Zk-1 自然就是Xm-1和Yn-1的LCS)
    • 如果xm ≠ yn ,那么有Z= {Xm-1,Yn}的LCS或者Z= {Xm,Yn-1}的LCS(因为X和Y的最后一个元素不相同,所以自然最后一个元素不在LCS序列中,但是并不知道到底是哪个字符串不存在于序列中,所以这里拆分成了两个子问题)

    所以通过上面的分析,可以得出状态转移方程(该方程记录的是所有状态改变的过程,即记录每个状态的过程,通过二维数组记录)

    c[i,j] =   1.  0                   i=0 || j =0

         2.  c[i-1,j-1] +1             i > 0 and j > 0 and x= yj

         3.  Math.Max(c[i-1,j],c[i,j-1])        i > 0 and j > 0 and xi ≠ yj

    (解释2:即始终保存的是目前最长的子串长度,通过解1的第一点可以看出如果最后一个元素相同那么LCS就是两个字符串长度-1的LCS,由于原问题比较庞大,所以现在是通过拆分原问题将它变成很多小问题来解决;解释3:同参考解释2和解1的第二点)

    状态转移表如下表显示:

          i            
          1 2 3 4 5 6 7
          c n b l o g s
    j 1 b 0 0 1 1 1 1 1
      2 e 0 0 1 1 1 1 1
      3 l 0 0 1 2 2 2 2
      4 o 0 0 1 2 3 3 3
      5 n 0 1 1 2 3 3 3
      6 g 0 1 1 2 3 4 4

    代码如下:

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 
     7 namespace ConsoleApp1
     8 {
     9     class Program
    10     {
    11         public static int[,] flag = new int[8,7];
    12         public static string str1 = "cnblogs";
    13         public static string str2 = "belong";
    14         static void Main(string[] args)
    15         {
    16             
    17             int[,] c = Lcs(str1, str2);
    18             for (int i = 0;i<str1.Length+1;++i)
    19             {
    20                 for(int j= 0;j<str2.Length+1;++j)
    21                 {
    22                     Console.Write(c[i, j]);
    23                 }
    24                 Console.Write("\n");
    25             }
    26             Console.WriteLine("输出LCS序列");
    27             printLcs(str1.Length,str2.Length);
    28             Console.ReadKey();
    29         }
    30 
    31         public static void printLcs(int i,int j)
    32         {
    33             if (i == 0 || j == 0) return;
    34             if (flag[i, j] == 0)
    35             {
    36                 //Console.Write(str1[i - 1]);
    37                 printLcs(i - 1, j - 1);
    38                 Console.Write(str1[i - 1]);
    39 
    40             }
    41             else if (flag[i, j] == 1)
    42             {
    43                 printLcs(i - 1, j);
    44 
    45             }
    46             else
    47                 printLcs(i, j - 1);
    48         }
    49 
    50         public static int[,] Lcs(string str1, string str2)
    51         {
    52             int len1 = str1.Length+1;
    53             int len2 = str2.Length+1;
    54             int[,] c = new int[len1, len2];
    55             string[] sc = new string[Math.Max(len1, len2)];
    56             for(int i = 0;i<len1;i++)
    57             {
    58                 for(int j =0;j<len2;j++)
    59                 {
    60                     if (i == 0 || j == 0)
    61                         c[i, j] = 0;
    62                     else if (str1[i - 1] == str2[j - 1])
    63                     {
    64                         c[i, j] = c[i - 1, j - 1] + 1;
    65                         flag[i, j] = 0;
    66                     }
    67                     else
    68                     {
    69                         c[i, j] = Math.Max(c[i, j - 1], c[i - 1, j]);
    70                         if (c[i, j - 1] <= c[i - 1, j])
    71                             flag[i, j] = 1;
    72                         else
    73                             flag[i, j] = -1;
    74                     }
    75                 }
    76             }
    77             for(int i = 0;i<8;++i)
    78             {
    79                 for(int j=0;j<7;++j)
    80                 {
    81                     Console.Write(flag[i, j] + "   ");
    82                 }
    83                 Console.Write("\n");
    84             }
    85             return c;
    86         }
    87     }
    88 }

    其中,完成状态转移的片段如下

     1 int len1 = str1.Length+1;
     2             int len2 = str2.Length+1;
     3             int[,] c = new int[len1, len2];
     4             string[] sc = new string[Math.Max(len1, len2)];
     5             for(int i = 0;i<len1;i++)
     6             {
     7                 for(int j =0;j<len2;j++)
     8                 {
     9                     if (i == 0 || j == 0)
    10                         c[i, j] = 0;
    11                     else if (str1[i - 1] == str2[j - 1])
    12                     {
    13                         c[i, j] = c[i - 1, j - 1] + 1;
    14                         flag[i, j] = 0;
    15                     }
    16                     else
    17                     {
    18                         c[i, j] = Math.Max(c[i, j - 1], c[i - 1, j]);
    19                         if (c[i, j - 1] <= c[i - 1, j])
    20                             flag[i, j] = 1;
    21                         else
    22                             flag[i, j] = -1;
    23                     }
    24                 }
    25             }

    注:该片段代码只提供状态转移的过程和该问题的最长子序列的长度,若需要确定LCS的元素,则需要通过另外一个数组保存状态转移的信息(即该状态是从何而来,是从哪个数据继承过来的);flag数组代表的就是状态转移的信息,这里分成三种情况,代表着当前状态的来源,分别是:c[i-1,j-1]、c[i-1,j]、c[i,j-1]。

    实现代码如下:

     1  public static void printLcs(int i,int j)
     2         {
     3             if (i == 0 || j == 0) return;
     4             if (flag[i, j] == 0)
     5             {
     6                 //Console.Write(str1[i - 1]);
     7                 printLcs(i - 1, j - 1);
     8                 Console.Write(str1[i - 1]);
     9 
    10             }
    11             else if (flag[i, j] == 1)
    12             {
    13                 printLcs(i - 1, j);
    14 
    15             }
    16             else
    17                 printLcs(i, j - 1);
    18         }

    该问题通过保存状态转移的信息,然后再利用递归的方法得出结果。

    代码解读:第一个  if 中,判断传入的 i 与 j  (i 和 j  代表存储状态转移信息的数组的下标),倘若所有数据已经遍历完毕,那么终止递归;第二个 if 中,如果flag中保存的数据 == 0 则表示当前数据来自于 c[i-1,j-1] 所以此时递归参数就是 i-1 和 j-1,而 else ifelse 也相应的代表另外两个来源方向。

    由于该方法是从后到前的递归,所以Console要在函数后面,意味着先递归再输出,那么输出的结果就是从头开始,加入先输出再递归的话,那么输出的结果就是刚好相反的,比如正确的结果是 blog,那么由于错误的输出方式则造成了现在的结果是 golb。

    为什么选择从后往前递归呢?第一点当然是为了方便,大家都习惯于将递归的结束条件写成与0有关,第二点是因为该数据状态的继承是从前面的数据得到的,而不是从后面的数据得到的,所以只有后面的数据可以找到前面的数据,而前面的无法预知后面的。(我猜是这样的!)

    最后一点:动态规划中最重要的一步是写出该问题的状态转移方程,将问题划分成若干子问题;最核心的一步是知道要用动态规划(这不是废话吗!通常碰到类似于递归,分治的,如果不可行或者时间复杂度过于庞大的话就要考虑动态规划了)

  • 相关阅读:
    ASP.NET SignalR 系列(九)之源码与总结
    ASP.NET SignalR 系列(八)之跨域推送
    ASP.NET SignalR 系列(七)之服务端触发推送
    ASP.NET SignalR 系列(六)之连接事件
    ASP.NET SignalR 系列(五)之群组推送
    ASP.NET SignalR 系列(四)之指定对象推送
    ASP.NET SignalR 系列(三)之代码实现
    ASP.NET SignalR 系列(二)之项目创建
    ASP.NET SignalR 系列(一)之SignalR介绍
    Splunk带来的好处
  • 原文地址:https://www.cnblogs.com/CHANGKTITI/p/11165025.html
Copyright © 2011-2022 走看看