动态规划问题-最小编辑距离(Minimum Edit Distance)

这篇具有很好参考价值的文章主要介绍了动态规划问题-最小编辑距离(Minimum Edit Distance)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

动态规划问题-最小编辑距离(Minimum Edit Distance)

我们今天要探讨的动态规划问题来源于俄罗斯科学家Levenshtein提出的两个对象之间的不相似度,在音频、语言翻译等领域有广泛的应用。如果用于评估字符串之间的不相似度,那么又称为最小编辑距离MED(Minimum Edit Distance),它规定从string 1到转换成 string 2的最少操作数,最少操作数(MED)越大, 那么两个字符串相似度越低;最少操作数(MED)越小,那么两个字符串的相似度就越高,如果两个字符串完全相同,那么最小编辑距离值为0。

今天要解决的问题来源于Leecode 问题 72, 问题描述:

给你两个单词 word1word2请返回将 word1 转换成 word2 所使用的最少操作数

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

这是典型的动态规划问题,因为word 1和word 2可以划分为子问题,而且子问题与原问题有类似的解结构。为了证明其是动态规划问题,借助《算法导论》中的CRCC 分析流程,对此问题进行解决分析。

  1. 表征最优问题的结构(Characterize the structure of the optimal solution)

首先确定操作对象为word1当中的字符,需要确认将其转化为word2所使用的最少操作数(MED),那么对于word1中的每个字符(包括∅),针对word1中的第i个字符,和word2中的第j个字符,有两大类:

如果word1[i]和word[j]相等,那么只需要把各自的操作对象字符往前移动一位,对于实际的最少操作数(MED)没有影响。如果word1[i]和word[j]不相等,面临三个选项:

​ a.) 删除操作,删除word1中的当前字符i

​ b.) 替换/更新操作,对于word1中的当前i字符,使用word2中的第j个字符进行替代

​ c.) 插入操作,在第i个位置插入word2的第j个字符

需要操作的动作是,三个选项都需要尝试,然后采纳操作数最小的操作动作。我们不排斥三个选择当中的任何一个,我们要做的是try them and try THEM ALL, 这个思想类似穷举法,我们对所有的可能都进行判断,然后择优选择。这一点也是动态规划不同于贪心算法的关键,贪心算法每一步只选择某个最优项目,无需对所有选择进行判断,然后基于当前最优前景的选择,进行下一步的相关操作。所以动态规划类似撒网,贪心算法类似垂钓,两类问题在对待子问题的选择上不同。

  1. 递归定义最优解的值

接下来来到最关键的一步,通过递归来定义最优解的值,假定word1=<x1,x2,x3.xi…xm>, word2=<y1,y2,y3…yj.yn>,我们定义数组dp[i] [j]来定义<x1,x2,x3…xi>和<y1,y2,y3…yj>的最少操作数(MED),对于最少操作数的矩阵dp[i] [j],可以有下面的选择:

a.)如果x[i]==y[j],那么dp[i] [j]=dp[i-1] [j-1], 也就是说,如果二者相等,<x1,x2,x3…xi>和<y1,y2,y3…yj>的最少操作数(MED)等于<x1,x2,x3…xi-1>和<y1,y2,y3…yj-1>的最少操作数(MED)。
d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] , w h e n   t h e   x [ i ]   e q u a l s   t o   y [ j ] dp[i][j]=dp[i-1][j-1], when\ the\ x[i]\ equals\ to\ y[j] dp[i][j]=dp[i1][j1],when the x[i] equals to y[j]

b.)如果x[i]≠y[j],这时候需要从删除、插入、更新三个选择中选择最小的最少操作数(MED),况且这三个选择的代价相同,无论选择哪一个,其最少操作数(MED)都需要追加1。

​ 接下来我们用公式表示这三个选择。
d p [ i ] [ j ] = m i n { d p [ i − 1 ] [ j ] + 1 , d p [ i ] [ j − 1 ] + 1 , d p [ i − 1 ] [ j − 1 ] + 1 } dp[i][j]=min\left\{dp[i-1][j]+1,dp[i][j-1]+1,dp[i-1][j-1]+1\right\} dp[i][j]=min{dp[i1][j]+1,dp[i][j1]+1,dp[i1][j1]+1}
dp[i-1] [j]+1表示假定选择删除第word1中的第i个字符,作为最少操作数(MED)的情况,相当于word1指向i字符的指针先向前移动1位,同时由于word2中的第j位字符还没有进行任何比较(删除word1[i]后,无法进行比较),所以其指针位置不变化。

dp[i][j-1]+1表示假定在word1的i字符之后插入与word2当中的第j个字符,那么这个时候相当于word1的i指针没有变化,当时word2[j]由于已经找到匹配(与word1中的插入字符相同),那么就需要把word2的j指针向前移动1位。

dp[i-1][j-1]+1表示假定把word1中的第i个字符替换为与第j个字符相同,那么这时候,word1中的指针i和word2当中的指针j都需要往前移动1位。

上述三个情况完成后,需要选择三类操作中最小的最少操作数,作为当前dp[i][j]的最少操作数(MED)的值。如果word1字符串为空,那么这时候就需要追加word2当前的字符串长度至word1当中,插入的长度等于当前word2的长度;如果word2为空串,那么就需要删除word1当中的所有字符串,确保二者一致,删除的长度等于当前word1的长度。

为了能更简洁阐述上述观点,采用下列表格对dp[i][j]的变化过程进行说明;
动态规划问题-最小编辑距离(Minimum Edit Distance)

我们现在假定需要求解DP[1][2]的值(深蓝色单元格),具体可以理解为word1=‘∅h’, word2='∅ro’情况下最小操作数。

由于word1[1]!=word2[2],所以我们需要在删除、替换、更新三个策略中选择结算的最小操作数(MED)选取最小的值,这时候我们有:

DP[1][2]=DP[0][2]+1=2+1=3,我们用图示来说明具体操作路径:

  • 删除操作

动态规划问题-最小编辑距离(Minimum Edit Distance)

如橙色箭头所示,首先在word1插入’r’,word1变为’∅rh’,最少操作数为1; 接着在word1中插入’o’,word1变为’∅roh’,最少操作数为2(=1+1);最后我们删除’h’,word1变为’∅ro’,完成操作(最少操作数为3),在此过程中,最少操作数总数为3,为了方便,抽象为:

D[i][j]=D[i-1][j]+1, 最后一步操作表示删除word1中的第i个元素,我们可以得到当前D[i][j]值为这个数字,值得一提的是,一定要强调当前,因为这个数字不是最终的D[i][j]的数字。回到前文的示例,我们有当前的D[1][2]的具体值。

D[1][2]=D[0][2]+1=2+1=3;

  • 替换/更新操作

动态规划问题-最小编辑距离(Minimum Edit Distance)

具体过程解析为,在word1的∅后面先插入’r’, word1字符转换称为 ‘∅rh’,最小操作数为1;接着我们替换word1中的’h’为’o’,word1转换为’∅ro’,最小操作数为2(1+1).

最后一步的操作可以表示为:

DP[i][j]=DP[i-1][j-1]+1,此步骤表示替换操作,具体到上面的例子中,我们可以看到,当前的DP[1][2]的值可以表示为:

DP[1][2]=DP[0][1]+1=1+1=2;

  • 插入操作
    动态规划问题-最小编辑距离(Minimum Edit Distance)

第一步操作,替换word1(‘∅h’)中的’h’为’r’,转换word1为(‘∅r’),此步骤的最小操作数1;

第二操作,在当前的word1(‘∅r’)后面追加’o’,转换word1为(‘∅ro’),此步骤的最小操作数为2(1+1), 其中最后一步的操作可以抽象为:

DP[i][j]=DP[i][j-1]+1, 表示插入操作,具体到当前的实际例子中,我们有:

DP[1][2]=DP[1][1]+1=1+1=2

截止到目前位置,我们分别对删除、替换/更新、插入三个选择进行计算,汇总结果:

DP_Delete=3;

DP_Update=2;

DP_Insert=2;

DP[1][2]=min{DP_Delete, DP_Update,DP_Insert}=2;

在此示例中,如果仔细分析,可以发现DP_Update 和DP_Insert均可以取得相同的最小操作数,实际上这两种结果都是合法的结果,所以我们可以所,最小编辑距离(MED)的具体的解选择并不唯一。

回到最初的问题,如果要求解word1='∅horse’和word2='∅ros’二者字符串的最小操作数,我们沿着图示中蓝色方块进行,就可以求得结果。

动态规划问题-最小编辑距离(Minimum Edit Distance)

  1. 计算最优解的值

完成上述定义后,计算最优解的值过程就相对就比较简单。递归或者迭代都是可以选择的程序构建方法。本例中我们采用两个方法进行说明。

在具体执行程序之前,需要特别提到的是,MED问题属于选择性的子问题,不同于Rod-cutting或者Matrix-Chain-Multiplication问题,不需要对前面所有的子问题进行评估,只需要评估当前以及当前临界的问题即可,所以如果采用递归流程,在程序中就可以省去循环过程,只需要多个不同的判断分支即可解决问题。这是MED问题不同于rod-cutting和Matrix-chain-multiplication的问题的关键地方。我们先采用bottom-up的方法计算求解过程。

a-1) 头文件定义

/**
 * @file Edit_Distance.h
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2023-02-25
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#ifndef EDIT_DISTANCE_H
#define EDIT_DISTANCE_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define M 5
#define N 3

/**
 * @brief Fid the minimum operation times by changing the source string to target source
 * 
 * @param source Source string to be updated/changed
 * @param target Target string we need pay attention to
 * @param m Length of source string
 * @param n Length of target string
 * @param DP To store the min_edit_distance 
 */
void min_edit_distance(char *source,char *target, int m, int n, int DP[M+1][N+1]);

/**
 * @brief Find the minimum opeation times from three types
 *
 * @param num_delte Number of deltetion
 * @param num_insert Number of insertion
 * @param num_update Number of updating
 * @return int -Return the minimum number of three type of operatoin
 */
int min(int num_delete, int num_insert, int num_update);

#endif

b-1) 函数实施

/**
 * @file Edit_Distance.c
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2023-02-25
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#ifndef EDIT_DISTANCE_C
#define EDIT_DISTANCE_C
#include "Edit_Distance.h"

void min_edit_distance(char *source, char *target, int m, int n, int DP[M + 1][N + 1])
{
    int i;
    int j;
    int num_delete;
    int num_update;
    int num_insert;
	
    //If source string is not empty while target string is empty
    //just delete the current length of source string from the source string
    for(i=0;i<=m;i++)
    {
        DP[i][0]=i;
    }
	  //If source string is empty while target string is not empty
    //just insert the current length of source string into the source string
    for(i=0;i<=n;i++)
    {
        DP[0][i]=i;
    }

    for(i=1;i<=m;i++)
    {
        for(j=1;j<=n;j++)
        {
            if(source[i]==target[j])
            {
                //no operatoin needed if both are source[i]==target[j] equal
                DP[i][j]=DP[i-1][j-1]; 
            }
            else
            {
                //Given three choices, we need to evaluate the minimum one
                //Follow the rule: try them and try them ALL
                num_delete=DP[i-1][j]+1;
                num_insert=DP[i][j-1]+1;
                num_update=DP[i-1][j-1]+1;
                DP[i][j]=min(num_delete,num_insert,num_update);
            }
        }
    }

    return;
}

int min(int num_delete, int num_insert, int num_update)
{
    int min_value;
    
    min_value=(num_delete<num_insert?num_delete:num_insert);
    min_value=(num_update<min_value?num_update:min_value);

    return min_value;
}

#endif

c-1) 测试主函数

/**
 * @file Edit_Distance_Main.c
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2023-02-25
 * 
 * @copyright Copyright (c) 2023
 * 
 */
#ifndef EDIT_DISTANCE_MAIN_C
#define EDIT_DISTANCE_MAIN_C
#include "Edit_Distance.c"

int main(void)
{
    int m;
    int n;
    int DP[M+1][N+1];
    //'#' indicate the string is empty and equals to '∅'
    char source[] = {'#', 'h', 'o', 'r', 's', 'e'};
    char target[] = {'#', 'r', 'o', 's'};
    m=M;
    n=N;

    min_edit_distance(source,target,m,n,DP);

    printf("The minimum edit distance is %d\n",DP[m][n]);

    getchar();
    return EXIT_SUCCESS;
}
#endif

对于递归程序,我们这里仅呈现函数的实现,头文件和测试问题件,我们就不一一罗列了。

/**
 * @file Edit_Distance.c
 * @author your name (you@domain.com)
 * 采用递归形式,实现MED计算
 * @version 0.1
 * @date 2023-02-25
 *
 * @copyright Copyright (c) 2023
 *
 */
#ifndef EDIT_DISTANCE_C
#define EDIT_DISTANCE_C
#include "Edit_Distance.h"

int min_edit_distance(char *source, char *target, int m, int n, int DP[M + 1][N + 1])
{
    int i;
    int j;

    for(i=0;i<=m;i++)
    {
        for(j=0;j<=n;j++)
        {
            DP[i][j]=-1;
        }
    }

    return min_edit_distance_aux(source, target,m,n,DP);
}

int min_edit_distance_aux(char *source, char *target, int m, int n, int DP[M + 1][N + 1])
{
    int num_delete;
    int num_insert;
    int num_update;
    int dist;

    if (DP[m][n] !=-1)
    {
        return DP[m][n];
    }

   
    if(m==0)
    {
        DP[m][n]=n;
    
    }
    else if(n==0)
    {
        DP[m][n]=m;
    }
    else
    {
        if(source[m]==target[n])
        {
            DP[m][n] = min_edit_distance_aux(source,target,m-1,n-1,DP);
        }
        else
        {
            num_delete = min_edit_distance_aux(source, target, m-1, n, DP)+1;
            num_insert = min_edit_distance_aux(source, target, m, n-1, DP)+1;
            num_update = min_edit_distance_aux(source, target, m-1, n-1, DP)+1;
            DP[m][n]=min(num_delete,num_insert,num_update);
        }
    }
    return DP[m][n];
}


int min(int num_delete, int num_insert, int num_update)
{
    int min_value;
    
    min_value=(num_delete<num_insert?num_delete:num_insert);
    min_value=(num_update<min_value?num_update:min_value);

    return min_value;
}

#endif
  1. 构建最优解的信息

对于如何构建最优解的路径,我们可以设定另外数组 P[i][j]进行记录,其选择值可以为’D’(Delete), ‘U’(Update)和’I’(Insert)三个值之一,从尾部到头部的方法从(递归)遍历,然后打印出操作方法即可,在此略过。

  1. 总结:

对于此动态规划问题,在解决之前将近花了一天时间进行思考,很多时候走入思维的误区,最大的误区是,希望对每一步计算之前,通过word1[i]和word2[j]的先行比较,从而选择Delete, Update或Insert操作之一。其实这个思维过程已经违反了动态遍历的基本思维: Try them and try them all. 后来又复习了《算法导论》中LCS的相关章节,才豁然开朗,打开了思路。

另外碰到的思维难点,在于递归的结束条件或迭代的其实条件的计算,其实过程中没有抽象到word1等于’∅’或word2’∅’的情况,进入死胡同,无法结束递归或开启迭代的相应计算。

其三,通过反思和比较,更进一步体会到了有条件选择和全部选择的不同,主要是MED问题和rod-cutting问题的比较,在rod-cutting问题中,我们需要对{pi+r(n-i)}的所以切割位置进行循环判断,然后才能得到最优的值。而此问题实际上只需要对当前问题的邻接问题进行判断即可,也即: 上方,左方,左上方的值进行判断即可,对于左上方的值,需要通过字符比较,确认选择的代价是0还是1。

DP[i-1][j-1] DP[i-1][j]
DP[i][j-1] DP[i][j]

参考资料:

  1. 《Introduction to algorithm 4ed》

  2. Leecode 问题 #72文章来源地址https://www.toymoban.com/news/detail-492794.html

到了这里,关于动态规划问题-最小编辑距离(Minimum Edit Distance)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【算法一则】编辑距离 【动态规划】

    给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作: 插入一个字符 删除一个字符 替换一个字符 这个问题可以使用动态规划来解决。我们可以定义一个二维数组dp,其中dp[i][j]表示将word1的前i个字符转换成word

    2024年04月24日
    浏览(28)
  • 编辑距离的动态规划

    编辑距离,也称 Levenshtein 距离,指两个字符串之间互相转换的最少修改次数,通常用于在信息检索和自然语言处理中度量两个序列的相似度。 Question 输入两个字符串 (s) 和 (t) ,返回将 (s) 转换为 (t) 所需的最少编辑步数。 你可以在一个字符串中进行三种编辑操作

    2024年04月08日
    浏览(29)
  • 【Leetcode72】编辑距离(动态规划)

    (1)确定状态 dp[i][j] 表示word1中前 i 个单词,变换到word2中前 j 个字符,最少需要的动作次数。 (2)状态转移方程 编辑距离可以通过以下三种操作进行计算:插入一个字符、删除一个字符、替换一个字符。因此,可以使用以下状态转移方程: 如果 A[i] == B[j] ,那么 dp[i][j]

    2024年02月12日
    浏览(31)
  • 【算法】动态规划算法求(编辑距离)

    目录 编辑距离: 举例: 代码如下 调试: 核心代码: 画图演示上述代码:    是一种计算两个自符串之间差异程度的方法,它通过比较 两个字符串之间的插入,删除和 替换操作的数量 ,来确定他们之间的距离。 现有两个字符串 字符串s1:”CTGA\\\" 字符串s2:  \\\"ACGCTA\\\" 求s1和

    2024年02月12日
    浏览(28)
  • 【LeetCode:72. 编辑距离 | 暴力递归=>记忆化搜索=>动态规划 】

    🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,CSDN-Java领域新星创作者🏆,保研|国家奖学金|高中学习JAVA|大学完善JAVA开发技术栈|面试刷题|面经八股文

    2024年02月06日
    浏览(31)
  • 动态规划Day16(编辑距离,删除元素待写完)

    目录 583. 两个字符串的删除操作 看到题目的第一想法                看到代码随想录之后的想法 自己实现过程中遇到的困难(看代码) 72. 编辑距离 看到题目的第一想法                看到代码随想录之后的想法 自己实现过程中遇到的困难(看代码) 力扣题目链接(opens new

    2024年01月24日
    浏览(40)
  • 动态规划16 | ● 583. 两个字符串的删除操作 ● *72. 编辑距离

    https://programmercarl.com/0583.%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C.html 考点 子序列问题 我的思路 dp[i][j]的含义是,当两个字符串分别取前i和j个元素时,对应的最少相等删除步数是多少 递推公式为,如果两个字符串第i和j个元素恰好相等,则dp值应

    2024年04月22日
    浏览(35)
  • 编辑距离算法(Levenshtein Distance Algorithm)的概念理解及其应用

    编辑距离 ,由俄罗斯科学家Vladimir Levenshtein于1965年提出,因此又称为 Levenshtein Distance ,简称 LD ,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。 可用的编辑操作包括: - 将某个字符替换为另一个字符 - 插入字符 - 删除字符 将两个字符串 a, b 的Levenshtein D

    2024年02月16日
    浏览(26)
  • LeetCode | C++ 动态规划——583. 两个字符串的删除操作、72. 编辑距离

    583题目链接 做法一: 本题和1143.最长公共子序列基本相同,只要求出两个字符串的最长公共子序列长度即可,那么除了最长公共子序列之外的字符都是必须删除的,最后用两个字符串的总长度减去两个最长公共子序列的长度就是删除的最少步数。 做法二: 本题和115.不同的子

    2024年02月16日
    浏览(39)
  • 动态规划经典题:编辑距离(hard) 详解,看了还不会你来砍我

    🧸🧸🧸 各位大佬大家好,我是猪皮兄弟 🧸🧸🧸 为了更好的理解,我们从易到难的来解决编辑距离的问题 Leetcode最长公共子序列 一般的,我们在做序列DP问题的时候,遇到两个字符串都会用一个二维数组来进行DP,最长公共子序列简称LCS(longest common subsequence) 对于本题 状

    2024年01月22日
    浏览(28)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包