动态规划问题一:最长公共子序列(矩阵-二重指针对应指针数组)

什么是子序列?
-对序列 1,3,5,4,2,6,8,7来说,序列3,4,8,7 是它的一个子序列,也即子序列不是子串,不一定要连续。
但是也不是子集,也即需要按原序列的顺序,不可打乱元素顺序。

什么是动态规划?
-动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。
动态规划法的关键就在于,对于重复出现的子问题只在第一次遇到时加以求解,并把答案保存起来,让以后再遇到时直接引用,不必重新求解。

动态规划解题的一般思路

  1. 将原问题分解为子问题
    把原问题分解为若干个子问题,子问题和原问题形式相同或类似,只不过规模变小了。子问题都解决,原问题即解决(数字三角形例)。
    子问题的解一旦求出就会被保存,所以每个子问题只需求 解一次。

  2. 确定状态
    在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状态”。一个“状态”对应于一个或多个子问题, 所谓某个“状态”下的“值”,就是这个“状 态”所对应的子问题的解。
    所有“状态”的集合,构成问题的“状态空间”。“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。

  3. 确定一些初始状态(边界状态)的值

  4. 确定状态转移方程
    定义出什么是“状态”,以及在该“状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(递推型)。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。

回到最长公共子序列的问题

简单来说可以这样计算:
①若S1的最后一个元素 与 S2的最后一个元素相等
S1和S2的LCS等于 {S1减去最后一个元素} 与 {S2减去最后一个元素} 的 LCS 再加上 S1和S2相等的最后一个元素。
②若S1的最后一个元素 与 S2的最后一个元素不等
S1和S2的LCS就等于 {S1减去最后一个元素} 与 S2 的LCS, {S2减去最后一个元素} 与 S1 的LCS 中的最大的那个序列。

第一步:计算最长公共子序列的长度并记录状态。
用二维矩阵解决:
二维数组中s1为列,s2为行(掉过来也行),先将二维数组中所有的数字都初始化为0
如果横竖(i,j)对应的两个元素相等,该格子的值 = c[i-1,j-1] + 1。如果不等,取c[i-1,j] 和 c[i,j-1]的最大值。
最后一个格子的值就是最长公共子序列的长度。

代码实现:

//不需要输出长度的话这个函数就不需要有返回值,b和c会把改动带回去
void LCSLength(char *x, char *y, int m, int n, int **c, int **b)
{
    int i, j;
    for (i = 0; i <= m; i++) c[i][0] = 0;
    for (i = 0; i <= n; i++) c[0][i] = 0;
    for (i = 1; i <= m; i++)
        for (j = 1; j <= n; j++)//这里不能由0开始,不然[i-1][j-1]会越界
        {
            if (x[i] == y[j])
            {
                c[i][j] = c[i - 1][j - 1] + 1;
                b[i][j] = 1;//b[i][j]用于标记该格是否为相同的格子,以及到底是i-1还是j-1
            }
            else if (c[i - 1][j] >= c[i][j - 1])
            {
                c[i][j] = c[i - 1][j];
                b[i][j] = 2;
            }
            else
            {
                c[i][j] = c[i][j - 1];
                b[i][j] = 3;
            }
        }
}



第二步:从最后一个格子开始(刚好是b[i][j]),通过对状态的判别判断下一个应该输出的格子来输出公共子串

//由于是公共子串,可以只用其中一个串,不需要y
void LCS(int i, int j, char *x, int **b)
{
    if (i == 0 || j == 0) return;
    if (b[i][j] == 1)
    {
        LCS(i - 1, j - 1, x, b);
        cout << x[i];
    }
    else if (b[i][j] == 2)
        LCS(i - 1, j, x, b);
    else LCS(i, j - 1, x, b);
}

完整的代码(注意在调用函数前声明变量时对指针数组的初始化):

#include <iostream>
using namespace std;

void LCSLength(char *x, char *y, int m, int n, int **c, int **b)//不需要输出长度的话这个函数就不需要有返回值,b和c会把改动带回去
{
    int i, j;
    for (i = 0; i <= m; i++) c[i][0] = 0;
    for (i = 0; i <= n; i++) c[0][i] = 0;
    for (i = 1; i <= m; i++)
        for (j = 1; j <= n; j++)//这里不能由0开始,不然[i-1][j-1]会越界
        {
            if (x[i] == y[j])
            {
                c[i][j] = c[i - 1][j - 1] + 1;
                b[i][j] = 1;//b[i][j]用于标记该格是否为相同的格子,以及到底是i-1还是j-1
            }
            else if (c[i - 1][j] >= c[i][j - 1])
            {
                c[i][j] = c[i - 1][j];
                b[i][j] = 2;
            }
            else
            {
                c[i][j] = c[i][j - 1];
                b[i][j] = 3;
            }
        }
}

void LCS(int i, int j, char *x, int **b)//由于是公共子串,可以只用其中一个串,不需要y
{
    if (i == 0 || j == 0) return;
    if (b[i][j] == 1)
    {
        LCS(i - 1, j - 1, x, b);
        cout << x[i];
    }
    else if (b[i][j] == 2)
        LCS(i - 1, j, x, b);
    else LCS(i, j - 1, x, b);
}

int main()
{
    //x,y是两个串,b、c是二维数组;
    //局部变量必须初始化

    int m = 0, n = 0;
    char x[10000], y[10000];
    int *b[10000], *c[10000];//**b匹配的是指针数组 而不是b[10000][10000]
    for (int i = 0; i < 10000; i++)//划重点
    {
        b[i] = new int[10000];
        c[i] = new int[10000];
    }

    cout << "请输入第一串数字/字母的长度:";
    cin >> m;
    cout << "请输入第二串数字/字母的长度:";
    cin >> n;


    cout << "请输入第一串数字/字母:";
    for (int i = 1; i <= m; i++)//这里用1~m 而不是 0~m-1 因为在上面的函数中初始化为0的那两行是没意义的
        cin >> x[i];
    cout << "请输入第二串数字/字母:";
    for (int i = 1; i <= n; i++)
        cin >> y[i];

    LCSLength(x, y, m, n, c, b);
    LCS(m, n, x, b);
    cout << endl;

    system("pause");
}



当然其实也可以不用到数组b,直接用数组c进行判断
LCSLength主要用于先一步把矩阵建立起来
LCS才能进行回溯

#include <iostream>
using namespace std;

void LCSLength(char *x, char *y, int m, int n, int **c)//不需要输出长度的话这个函数就不需要有返回值,b和c会把改动带回去
{
    int i, j;
    for (i = 0; i <= m; i++) c[i][0] = 0;
    for (i = 0; i <= n; i++) c[0][i] = 0;
    for (i = 1; i <= m; i++)
        for (j = 1; j <= n; j++)//这里不能由0开始,不然[i-1][j-1]会越界
        {
            if (x[i] == y[j])
            {
                c[i][j] = c[i - 1][j - 1] + 1;
            }
            else if (c[i - 1][j] >= c[i][j - 1])
            {
                c[i][j] = c[i - 1][j];
            }
            else
            {
                c[i][j] = c[i][j - 1];
            }
        }
}
void LCS(int i, int j, char *x,char *y, int **c)//由于是公共子串,可以只用其中一个串,不需要y
{
    if (i == 0 || j == 0) return;
    if (x[i] == y[j])
    {
        LCS(i - 1, j - 1, x, y, c);
        cout << x[i];
    }
    else if (c[i - 1][j] >= c[i][j - 1])
        LCS(i - 1, j, x, y, c);
    else LCS(i, j - 1, x, y, c);
}

int main()
{
    //x,y是两个串,b、c是二维数组;
    //局部变量必须初始化

    int m = 0, n = 0;
    char x[10000], y[10000];
    int  *c[10000];//**b匹配的是指针数组 而不是b[10000][10000]
    for (int i = 0; i < 10000; i++)
        c[i] = new int[10000];

    cout << "请输入第一串数字/字母的长度:";
    cin >> m;
    cout << "请输入第二串数字/字母的长度:";
    cin >> n;


    cout << "请输入第一串数字/字母:";
    for (int i = 1; i <= m; i++)//这里用1~m 而不是 0~m-1 因为在上面的函数中初始化为0的那两行是没意义的
        cin >> x[i];
    cout << "请输入第二串数字/字母:";
    for (int i = 1; i <= n; i++)
        cin >> y[i];

    LCSLength(x, y, m, n, c);
    LCS(m, n, x, y, c);
    cout << endl;

    system("pause");
}

猜你喜欢

转载自blog.csdn.net/yogima/article/details/80042735