算法·EditDistanceProbrlem字符串最少字符操作次数C++版本

这个问题困惑我两天
终于整明白了(学校坐标东北)
我先描述问题
再循序渐进

问题描述

专业名称:Edit Distance Probrlem
设A 和B 是2 个字符串。要用最少的字符操作将字符串A 转换为字符串B。
这里所说的字符操作包括:
(1)删除一个字符;
(2)插入一个字符;
(3)将一个字符改为另一个字符。
将字符串A变换为字符串B 所用的最少字符操作次数也称为字符串A到B 的编辑距离,记为 d(A,B)
试设计一个有效算法,对任给的2 个字符串A和B,计算出它们的编辑距离d(A,B)

求解

  • 思路一(错解)

    • step1.1
      起码要分三种情况吧 1)A比B短 2)A同B长 3)A比B长
      如果都是 2)A同B长 的情况,那就好办了
      遍历两个字符串,遇到不一样的,进行字符更改操作就好
    • step1.2
      那我们继续, 2)A同B长 情况解决了
      我们只需要把 1)A比B短 3)A比B长 的情况转换成 2)A同B长的情况就好对吧
      问题是,能,但不是最优解!
      举个例子
      A:rw
      B:rew
      按照我们目前的思路,先把A后面补齐一个字符*,在顺序转换,需要1+3=4步
      而最优解是,我们只需要再r和w之间补一个e,只需要1步

    所以,思路一并不是我们想要的。

  • 思路二(正解)

    • 我们其实都知道,这是一道动态规划的题目。
      动态规划的核心是什么,我的理解是:建模,缩小,找到问题边界。
      而这道题目的边界,就是A和B都有且仅有一个字符。

      • 如果A==B,即A和B的字符相等
        OK,不用操作,0步

      • 如果A!=B,即A和B的字符不相等
        OK,更改操作,1步

    • 解决了一个字符的问题,我们再解决两个字符的问题,这时候,如果你问:“问题出现分歧,到底是A增加一个字符还是B增加一个字符呀?”我的回答是,这不重要!我们的题目虽然要求将A变成B,但把A变成B和把B变成A的过程,是不是互逆的?
      有了互逆的思路,好,两个字符即再一个字符的基础上,再进行一次增加的操作,直接增加第二个字符。
      三个字符再两个字符的基础上,再直接增加第三个字符…
      四个字符再三个字符的基础上,再直接增加第四个字符…

      类推!
      休息一下♨️接下来,我们不再抽象了,我们给A和B赋值,为了方便理解,我们两种情况一起做,这两种情况大家在上面也都见过了:

      • 如果A==B,即A和B的字符相等

        我们更改为 A首字符==B首字符
        此时我们把A叫做sameA,赋值asdf
        把B叫做sameB,赋值aerty
        建表

        same a s d f
        a 0 1 2 3
        e 1
        r 2
        t 3
        y 4
      • 如果A!=B,即A和B的字符不相等
        我们更改为 A首字符!=B首字符
        此时我们把A叫做diffA,赋值qwer
        把B叫做diffB,赋值aerty

        diff q w e r
        a 1 2 3 4
        e 2
        r 3
        t 4
        y 5
    • 划重点!最核心的地方来了1
      以diff表为例

      • [ i ][ j ] 与 [ i-1 ][ j-1 ] 的关系
        • 由对角线引入
          取部分diff为例?

          diff q w e r
          a 1 2 3 4
          e 2 here

          其实此时diffA可以看作qe,因为我们在操作第二个字符的前提是第一个字符已经相同了!那么我们只需要更改第二个字符即可,即操作数在第[i-1]第行[i-1]列的基础上+1即可;

          diff q w e r
          a 1 2 3 4
          e 2 2

          什么情况下不用+1呢,即A的第i个字符和B的第i个字符相同,那我还操作什么呢,什么都不操作就可以。即

          diff_0 q e e r
          a 1 2 3 4
          e 2 here
          diff_0 q e e r
          a 1 2 3 4
          e 2 1
        • 再不局限于对角线
          看表,我们此时要操作的地方,对应A与B字符相同,是不过不再是对角线上了,但,思想是不是和对角线上‘A的第i个字符和B的第i个字符相同’的思想一样?

          diff q w e r
          a 1 2 3 4
          e 2 2 here
          diff q w e r
          a 1 2 3 4
          e 2 2 2

          问题又来了,现在我们知道了解决对角线上的问题,和不是对角线上但是末尾相同的问题,那么不在对角线上而且末尾不相同的呢?
          +1!别问我为什么!

    • [ i ][ j ] 与 [ i][ j-1 ] 的关系(或者我们成为[ i ][ j ] 与 [ i-1 ][ j ] 的关系)
      还记得我们说,我们的操作只能有增删改三种嘛?
      其实[ i ][ j ] 与 [ i-1 ][ j-1 ] 的关系就对应这改
      [ i ][ j ] 与 [ i][ j-1 ] 的关系对应着增
      [ i ][ j ] 与 [ i-1 ][ j ]的关系对应着删
      但我们不是说,A到B的操作与B到A的操作是互逆的么!
      增和删其实是一种操作。Amazing.
      继续,即然我们进行操作了,无论是增还是删,都+1。
      增的话:[ i ][ j ] = [ i ][ j-1 ] + 1
      删都话:[ i ][ j ] = [ i-1 ][ j ] + 1
      我们再使用一次互逆的思想
      A增加=B删除,那么在目前的基础上,到底是A增加的操作少,还是B删除的操作少呢?
      比较!
      [ i ][ j ] = min([ i ][ j-1 ] + 1 , [ i-1 ][ j ] + 1)
      轻巧绝伦

    • 综上
      [ i ][ j ] 的取值可能是什么呢,可能是

      • [ i-1 ][ j-1 ]+1(对应元素不相等)
      • [ i-1 ][ j-1 ](对应元素相等)
      • [ i ][ j-1 ] + 1
      • [ i-1 ][ j ] + 1

      总结一下

      • 对应元素相等:[ i-1 ][ j-1 ]
      • 对应元素不相等:[ i-1 ][ j-1 ]+1 ,[ i ][ j-1 ] + 1 , [ i-1 ][ j ] + 1 三者中最小的一个
    temp = min(dp[i][j-1]+1, dp[i-1][j]+1);
    dp[i][j]=min(temp,dp[i-1][j-1]+(stringA[i-1]==stringB[j-1]?0:1));
    
    • 现在,我们能对所有操作进行处理了,但是一开始我们分了两种情况,same和diff,这一点都不优雅,我们想,能不能用已知的操作,对两种情况进行同一呢?
      能!
      看表
    perfect B B0 B1 B2 B3
    A 0 1 2 3 4
    A0 1
    A1 2
    A2 3
    A3 4
    A4 5

    A0==B0的话

    same B B0 B1 B2 B3
    A 0 1 2 3 4
    A0 1 0
    A1 2
    A2 3
    A3 4
    A4 5

    A0!=B0的话

    diff B B0 B1 B2 B3
    A 0 1 2 3 4
    A0 1 1
    A1 2
    A2 3
    A3 4
    A4 5

    大一统。

代码

为了方便调试

  1. 输入1继续程序,输入0结束程序
  2. 输出了表,实际上没有必要
//
//  main.cpp
//  字符串最小操作
//
//  Created by Allen on 2019/4/18.
//  Copyright © 2019 Allen. All rights reserved.
//

#include 
#include 
using namespace std;

int count(){
    string stringA,stringB;
    cout<<"请输入第一个字符串:";
    cin>>stringA;
    cout<<"请输入第二个字符串:";
    cin>>stringB;
    int Alength = stringA.length()+1;
    int Blength = stringB.length()+1;
    int dp[Alength][Blength],i,j,temp;
    for (i=0; i<Alength; i++) {//对列初始化
        dp[i][0]=i;
    }
    for (i=0; i<Blength; i++) {//对行初始化
        dp[0][i]=i;
    }
    for (i=1; i<Alength; i++) {
        for (j=1; j<Blength; j++) {
            //核心代码部分
            temp = min(dp[i][j-1]+1, dp[i-1][j]+1);
            dp[i][j]=min(temp,dp[i-1][j-1]+(stringA[i-1]==stringB[j-1]?0:1));
        }
    }
    for (i=0; i<Alength; i++) {
        for (j=0; j<Blength; j++) {
            cout<<dp[i][j]<<" ";
        }
        cout<<endl;
    }
    cout<<"最少操作次数为:"<<dp[Alength-1][Blength-1]<<endl;
    return 0;
}

int main() {
    int n ;
    while (cin>>n&&n!=0) {
        count();
    }
    return 0;
}

写了一个半小时博客,深夜两点。
两个动力,一怕灵感消失,二是 小可爱debug法 真好,令我开心地写完这篇log。

以上。

你可能感兴趣的:(算法)