一文详解编辑距离(Levenshtein Distance)
更多博文请关注:https://blog.bigcoder.cn
一. 什么是Levenshtein Distance
Levenshtein Distance,一般称为编辑距离(Edit Distance,Levenshtein Distance只是编辑距离的其中一种)或者莱文斯坦距离,算法概念是俄罗斯科学家弗拉基米尔·莱文斯坦(Levenshtein · Vladimir I)在1965年提出。此算法的概念很简单:Levenshtein Distance指两个字串之间,由一个转换成另一个所需的最少编辑操作次数,允许的编辑操作包括:
- 将其中一个字符替换成另一个字符(
Substitutions)。 - 插入一个字符(
Insertions)。 - 删除一个字符(
Deletions)。
二. 解题思路
假如,我们定义个方法,入参为两个字符串,返回两个字符串的编辑距离:
public static int editDistance(String a,String b)
假设我们有“kitte”、“Sitti”两个字符串,我们暂且将这两个字符串成为”A“和”B“,我们的编辑操作大致可分为三种类型:
- 选择一:将B字符串最后一个字符替换,使得两个字符串相等

因为这个操作使得最后一个字符相等,那么我们只需要继续计算minDistance(A[i-1],B[j-1])的编辑距离即可(A[i-1],其中i代表A字符串的长度,A[i-1]表示A字符串(0~length-1)的子串)
- 选择二:将B字符串增加一个字符,使得两个字符串最后一个字符相等:

这样我们只需要继续计算minDisnace(A[i-1],B[j])的编辑距离即可
- 选择三:将B字符串最后一个字符删除

实际上,删除B字符串的最后一个字符与在A字符串添加一个字符是等价的:

因为他们的编辑过程都为1,最后都需要继续计算minDistance(A[i],B[j-1])的编辑距离。
需要注意的是,大家不要站在上帝视角认为此种情况下选择第一种方式,将最后一个字符替换即是最优解。因为小范围的最佳解,并不一定是大范围的最佳解,我们只有枚举出所有可能的情况才能得出最小的编辑距离。
综上所述,我们可以得出编辑距离算法的状态转移方程:

三. 动态规划
了解动态规划的同学应该都知道DP算法,需要借助DP数组去完成,但是DP数组的初始化会困扰很多同学,这里我可以给大家提供一个技巧,对于DP数组需要初始化哪些数据,我们可以观察状态转移方程依赖与哪些状态,以上面的例子来说:
lev(i,j)需要依赖于lev(i-1,j)、lev(i,j-1)、lev(i-1,j-1)三种状态:

这样我们就需要把这个二维数组蓝色部分初始化好后,才能根据初始化好的数组,将整个二维数组填满,当填满的那一刻,我们的最优解也就达到了。

至于计算的路径,我们从初始化好的状态以及依赖态的位置,我们可以得出两种计算路径:

这里就是我对动态规划DP数组的初始化以及计算路径的明确技巧。
可以使用动态规划的方法去测量DP的值,步骤大致如下:
- 初始化一个
DP矩阵(M,N),M和N分别是两个输入字符串的长度。 - 矩阵可以从左上角到右下角进行填充,每个水平或垂直跳转分别对应于一个插入或一个删除。
- 通过定义每个操作的成本为1,如果两个字符串不匹配,则对角跳转的代价为1,否则为0,简单来说就是:
- 如果
[i][j]位置的两个字符串相等,则从[i][j]位置左加1,上加1,左上加0,然后从这三个数中取出最小的值填充到[i][j]。 - 如果
[i][j]位置的两个字符串不相等,则从[i][j]位置左、左上、上三个位置的值中取最小值,这个最小值加1(或者说这三个值都加1然后取最小值),然后填充到[i][j]。
- 如果
- 按照上面规则
LD矩阵(M,N)填充完毕后,最终矩阵右下角的数字就是两个字符串的LD值。
这里不打算证明上面动态规划的结论(也就是默认这个动态规划的结果是正确的),直接举两个例子说明这个问题:
- 例子一(两个等长字符串):
son和sun。 - 例子二(两个非等长字符串):
doge和dog。
例子一:
初始化LD矩阵(3,3):
s |
o |
n |
||
|---|---|---|---|---|
0 |
1 |
2 |
3 |
|
s |
1 |
|||
u |
2 |
|||
n |
3 |
计算[0][0]的位置的值,因为's' = 's',所以[0][0]的值 = min(1+1, 1+1, 0+0) = 0。
s |
o |
n |
||
|---|---|---|---|---|
0 |
1 |
2 |
3 |
|
s |
1 |
0 | ||
u |
2 |
|||
n |
3 |
按照这个规则计算其他位置的值,填充完毕后的LD矩阵`如下:
s |
o |
n |
||
|---|---|---|---|---|
0 |
1 |
2 |
3 |
|
s |
1 |
0 | 1 | 2 |
u |
2 |
1 | 1 | 2 |
n |
3 |
2 | 2 | 1 |
那么son和sun的LD值为1。
例子二:
初始化LD矩阵(4,3):
d |
o |
g |
||
|---|---|---|---|---|
0 |
1 |
2 |
3 |
|
d |
1 |
|||
o |
2 |
|||
g |
3 |
|||
e |
4 |
接着填充矩阵:
d |
o |
g |
||
|---|---|---|---|---|
0 |
1 |
2 |
3 |
|
d |
1 |
0 |
1 |
2 |
o |
2 |
1 |
0 |
1 |
g |
3 |
2 |
1 |
0 |
e |
4 |
3 |
2 |
1 |
那么doge和dog的LD值为1。
四. 代码实现
public static int minDistance(String word1, String word2) {
if (word1 == null || word2 == null) {
throw new RuntimeException("参数不能为空");
}
int[][] dp = new int[word1.length() + 1][word2.length() + 1];
//初始化DP数组
for (int i = 0; i <= word1.length(); i++) {
dp[i][0] = i;
}
for (int i = 0; i <= word2.length(); i++) {
dp[0][i] = i;
}
int cost;
for (int i = 1; i <= word1.length(); i++) {
for (int j = 1; j <= word2.length(); j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
cost = 0;
} else {
cost = 1;
}
dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
}
}
return dp[word1.length()][word2.length()];
}
private static int min(int x, int y, int z) {
return Math.min(x, Math.min(y, z));
}
五. 编辑距离的应用
- 搜索建议:当我们Google搜索
Jave时,Google会智能的提醒我们是否在搜索Java - DNA分析
- 抄袭侦测
…
本文参考至:
一文详解编辑距离(Levenshtein Distance)的更多相关文章
- 编辑距离算法详解:Levenshtein Distance算法
算法基本原理:假设我们可以使用d[ i , j ]个步骤(可以使用一个二维数组保存这个值),表示将串s[ 1…i ] 转换为 串t [ 1…j ]所需要的最少步骤个数,那么,在最基本的情况下,即在i等 ...
- 一文详解Hexo+Github小白建站
作者:玩世不恭的Coder时间:2020-03-08说明:本文为原创文章,未经允许不可转载,转载前请联系作者 一文详解Hexo+Github小白建站 前言 GitHub是一个面向开源及私有软件项目的托 ...
- 一文详解 Linux 系统常用监控工一文详解 Linux 系统常用监控工具(top,htop,iotop,iftop)具(top,htop,iotop,iftop)
一文详解 Linux 系统常用监控工具(top,htop,iotop,iftop) 概 述 本文主要记录一下 Linux 系统上一些常用的系统监控工具,非常好用.正所谓磨刀不误砍柴工,花点时间 ...
- 动态规划 001 - 编辑距离(Levenshtein Distance)问题
问题 字符串的编辑距离也被称为距Levenshtein距离(Levenshtein Distance),属于经典算法,常用方法使用递归,更好的方法是使用动态规划算法,以避免出现重叠子问题的反复计算,减 ...
- 一文详解 OpenGL ES 3.x 渲染管线
OpenGL ES 构建的三维空间,其中的三维实体由许多的三角形拼接构成.如下图左侧所示的三维实体圆锥,其由许多三角形按照一定规律拼接构成.而组成圆锥的每一个三角形,其任意一个顶点由三维空间中 x.y ...
- 一文详解 WebSocket 网络协议
WebSocket 协议运行在TCP协议之上,与Http协议同属于应用层网络数据传输协议.WebSocket相比于Http协议最大的特点是:允许服务端主动向客户端推送数据(从而解决Http 1.1协议 ...
- 1.3w字,一文详解死锁!
死锁(Dead Lock)指的是两个或两个以上的运算单元(进程.线程或协程),都在等待对方停止执行,以取得系统资源,但是没有一方提前退出,就称为死锁. 1.死锁演示 死锁的形成分为两个方面,一个是使用 ...
- 一文详解Redis键过期策略
摘要:Redis采用的过期策略:惰性删除+定期删除. 本文分享自华为云社区<Redis键过期策略详解>,作者:JavaEdge. 1 设置带过期时间的 key # 时间复杂度:O(1),最 ...
- 一文详解 Linux Crontab 调度任务
最近接到这样一个任务: 定期(每天.每月)向"特定服务器"传输"软件服务"的运营数据,因此这里涉及到一个定时任务,计划使用Python语言添加Crontab依赖 ...
- 一文详解如何在基于webpack5的react项目中使用svg
本文主要讨论基于webpack5+TypeScript的React项目(cra.craco底层本质都是使用webpack,所以同理)在2023年的今天是如何在项目中使用svg资源的. 首先,假定您已经 ...
随机推荐
- "鸿蒙生态专家面对面"三月专场等你前来!
- docker 应用篇————dockerfile[十四]
前言 简单介绍一下dockerfile. 正文 dockerfile就是docker image的构建文件. 然后来运行一下. 然后就产生了test:1.0 这个镜像. 然后启动一下你就发现了这个镜像 ...
- Ajax 原理是什么?如何实现?
一.是什么 AJAX全称(Asynchronous Javascript And XML) 即异步的JavaScript 和XML,是一种创建交互式网页应用的网页开发技术,可以在不重新加载整个网页的情 ...
- CSS之定位Position
前言 之前在<CSS之浮动>中,我当时是想一起说说定位的,因为我在很多地方看到有把float和position放在一起讲的,说它们的一些属性值可以使元素脱离文档流,但是没想到在准备内容的时 ...
- 力扣665(java)-非递减数列(中等)
题目: 给你一个长度为 n 的整数数组 nums ,请你判断在 最多 改变 1 个元素的情况下,该数组能否变成一个非递减数列. 我们是这样定义一个非递减数列的: 对于数组中任意的 i (0 <= ...
- 模型代码联动难? BizWorks来助力
简介: 本文介绍了业务模型设计和实现保持一致的重要性以及实际落地可能遇到的问题,以及BizWorks如何设计并提供一种双向联动能力, 通过BizWorks Toolkit(IDE 插件) 来解决和优化 ...
- 【OpenYurt 深度解析】边缘网关缓存能力的优雅实现
简介: 阿里云边缘容器服务上线 1 年后,正式开源了云原生边缘计算解决方案 OpenYurt,跟其他开源的容器化边缘计算方案不同的地方在于:OpenYurt 秉持 Extending your nat ...
- 汽车之家基于 Flink 的数据传输平台的设计与实践
简介: 数据接入与传输作为打通数据系统与业务系统的一道桥梁,是数据系统与架构中不可或缺的一个重要部分.数据传输系统稳定性和准确性,直接影响整个数据系统服务的 SLA 和质量.此外如何提升系统的易用性, ...
- PolarDB-X 2.0:使用一个透明的分布式数据库是一种什么体验
简介: 透明分布式,是PolarDB-X即将发布的能力,它能让应用在使用PolarDB-X的过程中,犹如使用单机数据库一般的体验.与传统的中间件类型的"分布式数据库"相比,有了透明 ...
- Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: "/xxx". at createRouterError 的说明和解决
错误说明 Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: & ...