Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >求编辑距离

求编辑距离

作者头像
高爽
发布于 2019-07-02 02:13:34
发布于 2019-07-02 02:13:34
65800
代码可运行
举报
文章被收录于专栏:高爽的专栏高爽的专栏
运行总次数:0
代码可运行

版权声明:本博客所有的原创文章,作者皆保留版权。 https://cloud.tencent.com/developer/article/1454159

定义

编辑距离又称Leveinshtein距离,是由俄罗斯科学家Vladimir Levenshtein在1965年提出。编辑距离是计算两个文本相似度的算法之一,以字符串为例,字符串a和字符串b的编辑距离是将a转换成b的最小操作次数,这里的操作包括三种:

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

举个例子,kitten和sitting的编辑距离是3,kitten -> sitten(k替换为s) -> sittin(e替换为i) -> sitting(插入g),至少要做3次操作。

实现

用leva,b(i,j)lev_{a,b}(i,j)来表示a和b的Leveinshtein距离(i和j分别代表a和b的长度),则:

  1. 当min(i,j)=0时,leva,b(i,j)=max(i,j),一个字符串的长度为0,编辑距离自然是另一个字符串的长度当min(i,j)=0时,lev_{a,b}(i,j)=max(i,j),一个字符串的长度为0,编辑距离自然是另一个字符串的长度
  2. 当ai=bj时,leva,b(i,j)=leva,b(i−1,j−1),比如xxcz和xyz的距离=xxc和xy的距离当a_i=b_j时,lev_{a,b}(i,j)=lev_{a,b}(i-1,j-1),比如xxcz和xyz的距离=xxc和xy的距离
  3. 否则,leva,b(i,j)为如下三项的最小值:否则,lev_{a,b}(i,j)为如下三项的最小值:
代码语言:txt
AI代码解释
复制
- leva,b(i−1,j)+1(删除ai),比如xxc和xyz的距离=xx和xyz的距离+1lev\_{a,b}(i-1,j)+1(删除a\_i),比如xxc和xyz的距离=xx和xyz的距离+1
- leva,b(i,j−1)+1(插入bj),比如xxc和xyz的距离=xxcz和xyz的距离+1=xxc和xy的距离+1lev\_{a,b}(i,j-1)+1(插入b\_j),比如xxc和xyz的距离=xxcz和xyz的距离+1=xxc和xy的距离+1
- leva,b(i−1,j−1)+1(替换bj),比如xxc和xyz的距离=xxz和xyz的距离+1=xx和xy的距离+1lev\_{a,b}(i-1,j-1)+1(替换b\_j),比如xxc和xyz的距离=xxz和xyz的距离+1=xx和xy的距离+1

用公式表示如下:

leva,b(i,j)=⎧⎩⎨⎪⎪⎪⎪⎪⎪if min(i,j)=0 max(i,j)if ai=bjleva,b(i−1,j−1)otherwisemin⎧⎩⎨⎪⎪leva,b(i−1,j)+1leva,b(i,j−1)+1leva,b(i−1,j−1)+1

lev_{a,b}(i,j)= \left{ \begin{array}{ll} if\space min(i,j)=0\qquad\space max(i,j)\ if\space a_i=b_j\qquad\qquad lev_{a,b}(i-1,j-1)\ otherwise\qquad\qquad min\left{ \begin{array}{ll} lev_{a,b}(i-1,j)+1\ lev_{a,b}(i,j-1)+1\ lev_{a,b}(i-1,j-1)+1 \end{array} \right. \end{array} \right.

递归实现

用上面的公式可以很容易的写出递归实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static int levenshteinDistance(String left, String right) {
    return levenshteinDistance(left.toCharArray(), left.length(), right.toCharArray(), right.length());
}
private static int levenshteinDistance(char[] left, int leftLen, char[] right, int rightLen) {
    if (Math.min(leftLen, rightLen) == 0) {
        return Math.max(leftLen, rightLen);
    }
    if (left[leftLen - 1] == right[rightLen - 1]) {
        return levenshteinDistance(left, leftLen - 1, right, rightLen - 1);
    }
    return Math.min(levenshteinDistance(left, leftLen - 1, right, rightLen),
            Math.min(levenshteinDistance(left, leftLen, right, rightLen - 1),
                    levenshteinDistance(left, leftLen - 1, right, rightLen - 1))) + 1;
}

递归的实现比较简单,递归的思想是通过递归的形式,最终得到一个由不可继续分割(递归出口)的式子组成的表达式,最终会存在非常多的重复的不可继续分割的式子,造成大量的重复计算,所以很低效。

动态规划实现

递归是从后向前分解,那与之相对的就是从前向后计算,逐渐推导出最终结果,此法被称之为动态规划,动态规划很适用于具有重叠计算性质的问题,但这个过程中会存储大量的中间计算的结果,一个好的动态规划算法会尽量减少空间复杂度。

全矩阵

以xxc和xyz为例,建立一个矩阵,通过矩阵记录计算好的距离:

当min(i,j)=0时,leva,b(i,j)=max(i,j)当min(i,j)=0时,lev_{a,b}(i,j)=max(i,j),根据此初始化矩阵的第一行和第一列:

依据上面的公式可以继续推导出第二行:

继续迭代,直至推导出最终结果:

这个过程记录了所有中间结果,空间复杂度为O(n2)O(n^2),来看一下代码实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static int levenshteinDistance(String left, String right) {
    // 创建矩阵
    int[][] d = new int[left.length() + 1][right.length() + 1];
    // 初始化第一列
    for (int i = 0; i <= left.length(); i++) {
        d[i][0] = i;
    }
    // 初始化第一行
    for (int j = 1; j <= right.length(); j++) {
        d[0][j] = j;
    }
    // 从第二行第二列开始迭代
    for (int i = 1; i <= left.length(); i++) {
        for (int j = 1; j <= right.length(); j++) {
            // 套公式计算
            if (left.charAt(i - 1) == right.charAt(j - 1)) {
                d[i][j] = d[i - 1][j - 1];
            } else {
                d[i][j] = Math.min(d[i - 1][j], Math.min(d[i][j - 1], d[i - 1][j - 1])) + 1;
            }
        }
    }
    // 最后一个格子即为最终结果
    return d[left.length()][right.length()];
}
两行

空间复杂度可以继续优化,我们计算当前行时,只依赖上一行的数据,所以我们只需要O(2n)O(2n)的空间复杂度,代码实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static int levenshteinDistance3(String left, String right) {
    int[] pre = new int[right.length() + 1];// 上一行
    int[] current = new int[right.length() + 1];// 当前行
    // 初始化第一行
    for (int i = 0; i < pre.length; i++) {
        pre[i] = i;
    }
    for (int i = 1; i <= left.length(); i++) {
        current[0] = i;// 第一列
        for (int j = 1; j <= right.length(); j++) {
            // 套公式计算
            if (left.charAt(i - 1) == right.charAt(j - 1)) {
                current[j] = pre[j - 1];
            } else {
                current[j] = Math.min(current[j - 1], Math.min(pre[j], pre[j - 1])) + 1;
            }
        }
        // current -> pre
        System.arraycopy(current, 0, pre, 0, current.length);
    }
    return pre[pre.length - 1];
}
单行

我们还可以进一步优化,其实只需要一行就可以了,计算当前格子时,只需要左、上、左上的值,左面的值可以直接得到,上面的值是当前格子修改前的旧值,也可以直接得到,左上角的值是左面格子修改前的旧值,需要暂存,这时空间复杂度为O(n)O(n)。

代码实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static int levenshteinDistance(String left, String right) {
    // 初始化当前行
    int[] d = new int[right.length() + 1];
    for (int i = 0; i < d.length; i++) {
        d[i] = i;
    }
    int leftTop, nextLeftTop;
    for (int i = 1; i <= left.length(); i++) {     
        leftTop = i - 1;// 当前行的左上角初始值
        d[0] = i;// 第一列
        for (int j = 1; j <= right.length(); j++) {
            nextLeftTop = d[j];// 暂存,此时d[j]为上一行的值,也是d[j+1]左上角的值
            // 套公式计算
            if (left.charAt(i - 1) == right.charAt(j - 1)) {
                d[j] = leftTop;
            } else {
                d[j] = Math.min(d[j - 1], Math.min(d[j], leftTop)) + 1;
            }
            leftTop = nextLeftTop;
        }
    }
    return d[d.length - 1];
}

应用

编辑距离是基于文本自身去计算,没有办法深入到语义层面,可以胜任一些简单的分析场景,如拼写检查、抄袭侦测等,在我的工作中,该算法在数据聚合时有一定的运用。

参考

https://en.wikipedia.org/wiki/Levenshtein_distance

http://www.dreamxu.com/books/dsa/dp/edit-distance.html


版权声明

本博客所有的原创文章,作者皆保留版权。转载必须包含本声明,保持本文完整,并以超链接形式注明作者高爽和本文原始地址:http://blog.csdn.net/ghsau/article/details/78903076

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2017年12月26日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
从编辑距离、BK树到文本纠错
搜索引擎里有一个很重要的话题,就是文本纠错,主要有两种做法,一是从词典纠错,一是分析用户搜索日志,今天我们探讨使用基于词典的方式纠错,核心思想就是基于编辑距离,使用BK树。下面我们来逐一探讨: 编辑距离 1965年,俄国科学家Vladimir Levenshtein给字符串相似度做出了一个明确的定义叫做Levenshtein距离,我们通常叫它“编辑距离”。 字符串A到B的编辑距离是指,只用插入、删除和替换三种操作,最少需要多少步可以把A变成B。例如,从FAME到GATE需要两步(两次替换),从GAME到A
JadePeng
2018/03/12
2.2K0
从编辑距离、BK树到文本纠错
Leetcode No.72 编辑距离(动态规划)
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
week
2021/11/29
3610
Leetcode No.72 编辑距离(动态规划)
每日一刷《剑指offer》字符串篇之编辑距离
给定两个字符串 str1 和 str2 ,请你算出将 str1 转为 str2 的最少操作数。
终有救赎
2023/11/23
2730
每日一刷《剑指offer》字符串篇之编辑距离
基于编辑距离来判断词语相似度方法(scala版)
词语相似性比较,最容易想到的就是编辑距离,也叫做Levenshtein Distance算法。在Python中是有现成的模块可以帮助做这个的,不过代码也很简单,我这边就用scala实现了一版。 编辑距离 编辑距离是指一个字符串改编成另一个字符串的最短距离,它描述了两个字符串的相近程度。比如: son -> sun ,只需要把o改成u即可,编辑距离为1 xing -> long,需要把x改成l,i改成o,编辑距离为2 o->long,需要在前面加上l,在后面加上ng,编辑距离为3 因此所有修改,移动,删
用户1154259
2018/01/17
1.4K0
LeetCode 训练场:72. 编辑距离
示例 1: **输入:**word1 = “horse”, word2 = “ros” **输出:**3 解释: horse -> rorse (将 ‘h’ 替换为 ‘r’) rorse -> rose (删除 ‘r’) rose -> ros (删除 ‘e’) 示例 2: **输入:**word1 = “intention”, word2 = “execution” **输出:**5 解释: intention -> inention (删除 ‘t’) inention -> enention (将 ‘i’ 替换为 ‘e’) enention -> exention (将 ‘n’ 替换为 ‘x’) exention -> exection (将 ‘n’ 替换为 ‘c’) exection -> execution (插入 ‘u’)
村雨遥
2022/06/15
1330
Levenshtein Distance(编辑距离)算法与使用场景
已经很久没深入研究过算法相关的东西,毕竟日常少用,就算死记硬背也是没有实施场景导致容易淡忘。最近在做一个脱敏数据和明文数据匹配的需求的时候,用到了一个算法叫Levenshtein Distance Algorithm,本文对此算法原理做简单的分析,并且用此算法解决几个常见的场景。
Throwable
2020/06/23
3.7K0
☆打卡算法☆LeetCode 72、编辑距离 算法解析
链接:72. 编辑距离 - 力扣(LeetCode) (leetcode-cn.com)
恬静的小魔龙
2022/08/07
4580
☆打卡算法☆LeetCode 72、编辑距离  算法解析
精读《算法题 - 编辑距离》
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数。
黄子毅
2023/10/25
1950
精读《算法题 - 编辑距离》
LintCode 编辑距离题目分析代码
给出两个单词word1和word2,计算出将word1 转换为word2的最少操作次数。
desperate633
2018/08/22
4790
72. 编辑距离
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作: 插入一个字符 删除一个字符 替换一个字符 示例 1: 输入:word1 = "horse", word2 = "ros" 输出:3 解释: horse -> rorse (将 'h' 替换为 'r') rorse -> rose (删除 'r') rose -> ros (删除 'e') class Solution {
CaesarChang张旭
2021/06/29
5870
8.动态规划(1)——字符串的编辑距离
  动态规划的算法题往往都是各大公司笔试题的常客。在不少算法类的微信公众号中,关于“动态规划”的文章屡见不鲜,都在试图用最浅显易懂的文字来描述讲解动态规划,甚至有的用漫画来解释,认真读每一篇公众号推送的文章实际上都能读得懂,都能对动态规划有一个大概了解。   什么是动态规划?通俗地理解来说,一个问题的解决办法一看就知道(穷举),但不能一个一个数啊,你得找到最优的解决办法,换句话说题目中就会出现类似“最多”、“最少”,“一共有多少种”等提法,这些题理论上都能使用动态规划的思想来求解。动态规划与分治方法类似,都
用户1148394
2018/01/12
1.8K0
8.动态规划(1)——字符串的编辑距离
动态规划 72. 编辑距离
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。
MashiroT
2023/01/30
1660
72. 编辑距离
给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。
张伦聪zhangluncong
2022/10/26
3370
LeetCode-72-编辑距离
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
benym
2022/07/14
2690
动态规划之终极绝杀:编辑距离
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
代码随想录
2021/04/08
5540
最短编辑距离
https://leetcode.cn/problems/edit-distance/description/
WuShF
2024/05/25
910
最短编辑距离
LeetCode 0072. 编辑距离[动态规划详解]
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
Yano_nankai
2021/03/05
5150
LeetCode 0072. 编辑距离[动态规划详解]
每日两题 T33
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
合一大师
2020/07/20
2700
经动态规划:编辑距离
前几天在网上看到一份鹅场的面试题,算法部分大半是动态规划,最后一题就是写一个计算编辑距离的函数,今天就专门写一篇文章来探讨一下这个经典问题。
labuladong
2021/09/23
3750
LeetCode 72. 编辑距离(DP)
给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。
Michael阿明
2020/07/13
4760
相关推荐
从编辑距离、BK树到文本纠错
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验