在Java中计算Levenshtein莱文斯坦(相似度)编辑距离

news2024/11/19 3:17:32

在本教程中,我们将研究 Levenshtein 距离算法,该算法也称为编辑距离算法,用于比较单词的相似性。

什么是列文施泰因距离

Levenshtein距离算法由俄罗斯科学家Vladimir Levenshtein创建。

Levenshtein 距离算法通过计算将一个字符串转换为另一个字符串所需的最小更改/替换次数来比较单词的相似性。这些更改包括:

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

该算法用于不同的应用或作为以下基础:

  • 拼写检查
  • 语音识别
  • 基因匹配
  • 抄袭检测

列文施泰因距离如何工作

该算法使用暴力技术将源字符串转换为目标字符串。它查看所有排列,以找到执行转换所需的最小更改数。列文施泰因距离算法也称为编辑距离算法。

例如,考虑源词狗和目标词闪避。这两个单词之间的编辑距离是 2,因为狗可以通过在 g 之前插入 d 和之后插入 e 来转换为闪避。

Levenshtein 距离算法还可以为每种类型的编辑分配不同的成本。例如,插入和删除的成本可能为 2,替换成本可能较低,成本为 1。

列文施泰因距离和动态规划

在本教程中,我们将遵循动态编程方法来实现 Levenshtein 距离算法。为了说明这种方法,我们将使用如下所示的矩阵。

上面的矩阵使用行和列来表示源单词 dog,而目标单词 dashge。矩阵将用于计算编辑距离。矩阵的行表示要转换的源单词,其条目是插入每个字符的成本。此外,矩阵中的列用于要转换的目标单词,条目是删除的成本。

  • 我们使用上面的矩阵来测量源词和目标词中字符之间的距离。单元格(行,列)是给定列索引处的行字符和列字符之间的距离。
  • 矩阵将从左上角填充到右下角。
  • 水平或垂直的每个移动都表示插入或删除。
  • 源词和目标词之间的列文施泰因距离结果将显示在右下角。

为了计算每个单元格中的值,我们将使用一个公式,例如:

1
minCost(value of left diagonal + substitution cost, value above + deletion cost, left value + insertion cost)

我们将使用以下值作为编辑成本。

1
2
3
insertion: 1
deletion: 1
substitution: 1

此外,在上面的公式中,我们将始终将插入和删除的成本设置为 1,并且如果索引 (row:col) 处的字符不同,我们将仅使用替换值 1。

让我们看一个计算第一个单元格的示例。对于此单元格,位置值将为:

1
2
3
left diagonal: 0
value above: 1
left value:  1

编辑成本将为:

1
2
3
substitution: 0 – characters are the same
insertion: 11 is a constant value
deletion: 11 is a constant value

所以:

1
minCost(0 + 0, 1 + 1, 1 + 1) → min(0, 2, 2) = 0

让我们继续看单元格 (1,2)。对于此单元格,值将为:

1
min(1 + 1, 2 + 1, 0 + 1) → min(2, 3, 1) = 1

我们将继续对其余细胞进行处理。计算完这些值后,我们得到了如下所示的矩阵。

在上面的矩阵中,右下角的值是列文施泰因距离计算的结果。在这种情况下,单词 dog 和 dakge 之间的最小编辑距离为 2。

在分析上述算法时,我们可以看到该算法以二次复杂度 O(MN) 执行,因为将源 M 中的每个字符与目标 N 中的每个字符进行比较以生成完全填充的矩阵。

列文施泰因距离实现

让我们编写一些代码来实现上述算法。

首先,我们将允许可配置编辑的成本:

1
2
3
4
5
6
7
8
9
public LevenshteinDistance(int insertionCost, int deletionCost, int substitutionCost) {
    AssertUtils.gte(insertionCost, 0, "Insertion cost must be greater than or equal to 0");
    AssertUtils.gte(deletionCost, 0, "Deletion cost must be greater than or equal to 0");
    AssertUtils.gte(substitutionCost, 0, "Substitution cost must be greater than or equal to 0");

    this.insertionCost = insertionCost;
    this.deletionCost = deletionCost;
    this.substitutionCost = substitutionCost;
}

接下来我们将编写一些代码来计算距离。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public int calculateDistance(CharSequence source, CharSequence target) {
    AssertUtils.notNull(source, "Source cannot be null");
    AssertUtils.notNull(target, "Target cannot be null");

    int sourceLength = source.length();
    int targetLength = target.length();

    int[][] matrix = new int[sourceLength + 1][targetLength + 1];
    matrix[0][0] = 0;

    for (int row = 1; row <= sourceLength; ++row) {
        matrix[row][0] = row;
    }

    for (int col = 1; col <= targetLength; ++col) {
        matrix[0][col] = col;
    }

    for (int row = 1; row <= sourceLength; ++row) {
        for (int col = 1; col <= targetLength; ++col) {
            matrix[row][col] = calcMinCost(source, target, matrix, row, col);
        }
    }

    return matrix[sourceLength][targetLength];
}

我们现在将解释上面列出的代码。在上面的代码中,我们首先使用源和目标长度初始化矩阵:

1
int[][] matrix = new int[sourceLength + 1][targetLength + 1];

我们还将矩阵行和列大小设置为比源和目标字长多 1。这样我们就可以通过添加矩阵第一行和第一列的默认值来填充空矩阵。

1
2
3
4
5
6
7
for (int row = 1; row <= sourceLength; ++row) {
    matrix[row][0] = row;
}

for (int col = 1; col <= targetLength; ++col) {
    matrix[0][col] = col;
}

上面的代码填充矩阵第一行和第一列的默认值。矩阵完全初始化后,下一步是计算矩阵中每个单元格的值。

1
2
3
4
5
for (int row = 1; row <= sourceLength; ++row) {
    for (int col = 1; col <= targetLength; ++col) {
        matrix[row][col] = calcMinCost(source, target, matrix, row, col);
    }
}

上面,我们遍历矩阵的每个单元格,计算其成本。完成此步骤后,可以在矩阵的最后一个单元格中找到最小距离:

1
return matrix[sourceLength][targetLength];

现在,让我们看看如何计算每个单元的最小成本。

1
2
3
4
5
6
7
8
private int calcMinCost(CharSequence source, CharSequence target,
                        int[][] matrix, int row, int col) {
    return Math.min(
            calcSubstitutionCost(source, target, matrix, row, col), Math.min(
                    calcDeletionCost(matrix, row, col),
                    calcInsertionCost(matrix, row, col))
    );
}

该方法查找单元格编辑的最小成本。它计算替换、插入和删除成本。此方法使用该方法返回计算成本的最小值。calcMinCostMath.min

完整的代码清单如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public class LevenshteinDistance {
    private final int insertionCost;
    private final int deletionCost;
    private final int substitutionCost;

    public LevenshteinDistance() {
        this(1, 1, 1);
    }

    public LevenshteinDistance(int insertionCost, int deletionCost, int substitutionCost) {
        AssertUtils.gte(insertionCost, 0, "Insertion cost must be greater than or equal to 0");
        AssertUtils.gte(deletionCost, 0, "Deletion cost must be greater than or equal to 0");
        AssertUtils.gte(substitutionCost, 0, "Substitution cost must be greater than or equal to 0");

        this.insertionCost = insertionCost;
        this.deletionCost = deletionCost;
        this.substitutionCost = substitutionCost;
    }

    public int calculateDistance(CharSequence source, CharSequence target) {
        AssertUtils.notNull(source, "Source cannot be null");
        AssertUtils.notNull(target, "Target cannot be null");

        int sourceLength = source.length();
        int targetLength = target.length();

        int[][] matrix = new int[sourceLength + 1][targetLength + 1];
        matrix[0][0] = 0;

        for (int row = 1; row <= sourceLength; ++row) {
            matrix[row][0] = row;
        }

        for (int col = 1; col <= targetLength; ++col) {
            matrix[0][col] = col;
        }

        for (int row = 1; row <= sourceLength; ++row) {
            for (int col = 1; col <= targetLength; ++col) {
                matrix[row][col] = calcMinCost(source, target, matrix, row, col);
            }
        }

        return matrix[sourceLength][targetLength];
    }

    private int calcMinCost(CharSequence source, CharSequence target,
                            int[][] matrix, int row, int col) {
        return Math.min(
                calcSubstitutionCost(source, target, matrix, row, col), Math.min(
                        calcDeletionCost(matrix, row, col),
                        calcInsertionCost(matrix, row, col))
        );
    }

    private int calcInsertionCost(int[][] matrix, int row, int col) {
        return matrix[row][col - 1] + insertionCost;
    }

    private int calcDeletionCost(int[][] matrix, int row, int col) {
        return matrix[row - 1][col] + deletionCost;
    }

    private int calcSubstitutionCost(CharSequence source, CharSequence target,
                                     int[][] matrix, int row, int col) {
        int cost = 0;
        if (source.charAt(row - 1) != target.charAt(col - 1)) {
            cost = substitutionCost;
        }
        return matrix[row - 1][col - 1] + cost;
    }
}

最后,我们将展示用于验证实现的测试:

1
2
3
4
5
6
@Test
public void calculateDistance() {
    LevenshteinDistance calculator = new LevenshteinDistance(1,1,1);
    final int result = calculator.calculateDistance("dog", "dodge");
    assertEquals(2,result);
}

结论

在本教程中,我们研究了 levenshtein 距离算法及其一些应用。我们还研究了使用动态规划实现它的一种方法。我们实现了一种以二次复杂度O(MN)执行的算法。因此,可以使用不同的技术来提高该算法的性能。就目前而言,该算法在相对于 O(MN) 的时间内执行,因此,如果不进行一些调整,它可能不会表现出生产用例所需的性能,例如高效的拼写检查器。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/11247.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于单片机的贪吃蛇设计

1 绪论 1.1 设计目的 在21世纪的今天&#xff0c;人们的生活开始变得更加丰富多彩。在繁忙的工作之余&#xff0c;娱乐成为人们生活不可或缺的一份子&#xff0c;而游戏作为近年来逐渐兴起的一种娱乐方式&#xff0c;已经越来越受到人们的青睐。在工作学习之余&#…

dreamweaver网页设计作业制作 学生NBA篮球网页 WEB静态网页作业模板 大学生校园篮球网页代码 dw个人网页作业成品

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

第五章:双指针与离散化的映射

第五章&#xff1a;双指针、离散化、二进制运算与区间合并一、双指针1、什么是双指针&#xff1f;2、双指针的模板3、双指针例题&#xff08;1&#xff09;思路&#xff1a;&#xff08;2&#xff09;解答&#xff1a;C版&#xff1a;C版&#xff1a;二、离散化1、什么是离散化…

java面试强基(3)

重载和重写的区别? 重载 发生在同一个类中&#xff0c;方法名必须相同&#xff0c;参数类型不同、个数不同、顺序不同&#xff0c;方法返回值和访问修饰符可以不同。 重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。 重写 重写发生在运行期&#xff0c;…

go语言基本环境搭建

下载地址 Go官网下载地址&#xff1a;https://studygolang.com/dl 一、下载对应电脑得安装包 二、下载完成点击安装下一步&#xff08;选择目录尽量简单&#xff09; 三、是否安装成功 四、环境变量 GOROOT和GOPATH都是环境变量&#xff0c;其中GOROOT是我们安装go开发包的路…

【计算机毕业设计】Springboot医疗管理系统源码

一、系统截图&#xff08;需要演示视频可以私聊&#xff09; 摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 医疗服务系统&#xff0c;主要的模块包括查看管理员&#xff1b;首页、个人中心…

tomcat官网下载配置全部详细步骤(包含各种报错解决办法)

前言&#xff1a; 名字由来&#xff1a;翻译是野猫&#xff0c;tomcat的作者初衷是希望这个软件可以自力更生&#xff0c;自给自足。不依赖其他插件&#xff0c;独立达到提供web服务的效果 1.tocat和java的关系&#xff1f; tomcat是用Java语言编写的&#xff0c;需要运行在…

大三,请问现在自学Java还来得及吗?

前言 如果还在为入门Java晚而发愁时间够不够&#xff0c;首先你是准备自学&#xff0c;那么我们可以看看现在网络上一些比较热门的Java全体系的学习需要化多长时间&#xff0c;先拿B站上做的比较好的黑马教程和尚硅谷举例&#xff1a; 2022黑马程序员Java学习路线图​www.bili…

耗时半月,终于把牛客网软件测试面试八股文,整理成了文档资料.....

一、面试基础题 简述测试流程: 1、阅读相关技术文档&#xff08;如产品PRD、UI设计、产品流程图等&#xff09;。 2、参加需求评审会议。 3、根据最终确定的需求文档编写测试计划。 4、编写测试用例&#xff08;等价类划分法、边界值分析法等&#xff09;。 5、用例评审(…

飞象星球落地重庆云阳86所学校,县乡4万学生迎来素质课堂

猜生字笔画顺序、学习硬笔书法&#xff1b;跟随老师认识情绪、写下心里话……自从重庆云阳县86所中小学引入飞象星球双师素质课堂&#xff0c;4万多名县城和乡村孩子的课后素质课堂一下子变得丰富多彩起来。 图&#xff1a;洞鹿小学双河村校上双师素质书法课 云阳县地处三峡库…

代码随想录算法训练营第三十六天| LeetCode435. 无重叠区间、LeetCode763. 划分字母区间、LeetCode56. 合并区间

一、LeetCode435. 无重叠区间 1&#xff1a;题目描述&#xff08;435. 无重叠区间&#xff09; 给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。 2&#xff1a;解题思路 class …

MySQL面试问题汇总(2022)

一、MySQL架构 锁 什么是锁&#xff1f; 当多个连接并发地存取MySQL数据时&#xff0c;在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据&#xff0c;破坏数据库的一致性。 加锁是实现数据库并发控制的一个非常重要的…

BFV同态加密方案初步学习

BFV是把Bra12的LWE版本推到了RLWE版本&#xff0c;Bra12也可以叫做BFV。 经典的RLWE的公钥加密算法回顾 对比以前的Regev的LWE公钥加密方案&#xff0c;其实几乎只是把明文空间换了&#xff0c;也就是在最大比特编码的时候把2换成t&#xff0c;即&#xff0c;Δ⌊q/t⌋\Delta…

web网页设计期末课程大作业——香格里拉旅游网页设计 5页 HTML+CSS+JavaScript

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…

一起来看看AMD最新显卡驱动22.11.1!

AMD最新推出了显卡驱动22.11.1&#xff0c;可以支持新上线的使命召唤&#xff1a;战区2&#xff0c;并且还修复了一系列的问题&#xff0c;AMD忠实用户们期待了嘛~ 更新内容 支持 使命召唤&#xff1a;战区 2.0 漫威蜘蛛侠&#xff1a;迈尔斯莫拉莱斯™ 固定问题 Radeon™ RX 6…

【mycat】mycat水平分表

mycat完成水平拆分 简介 相对于垂直拆分&#xff0c;水平拆分不是将表做分类&#xff0c;而是按照某个字段的某种规则来分散到多个库之中&#xff0c;每个表中包含一部分数据。简单来说&#xff0c;我们可以将数据的水平切分理解为是按照数据行的切分&#xff0c;就是将表中的…

【Try Hack Me】内网专项---Wreath

THM 学习笔记 【Try Hack Me】内网专项—Wreath &#x1f525;系列专栏&#xff1a;Try Hack Me &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月17日&#x1f334; &#x1f36d;作…

编程基础都要懂的计算机组成

学习目标: 1. 能够说出计算机有那两部分组成 2. 能够说出操作系统的作用 1.1计算机组成 计算机是可以进行数值计算和逻辑运算, 并且具有存储功能的电子机器. 计算机由硬系统件和软件系统组成. 1.1.1 硬件系统 主要分为主机和外设两部分, 是指那些构成计算机系统的物理实体,…

123456

hostname web1 bash 113.219.215.44 ctyun/Ctyun2022 cd /data/html/ tar -cvf /home/ctyun/html_zy.tar ./ mysqldump -uctyun -p --skip-lock-tables -R ultrax>ultrax_zhouyue.sql cd /etc/httpd tar -cvf /home/ctyun/httpd_zhouyue.tar ./ 关机改云主机私有网卡…

APP逆向案例之(一)过 app 更新提示

案例&#xff1a;某APP打开时提示更新 思路&#xff1a;想这是查壳看看有没有加壳&#xff0c;没有就去反编译按照提示搜索下&#xff0c;结果却是没有加壳反编译了下搜索&#xff0c;搜索不到&#xff0c;后来想到是不是网络传过来的&#xff0c;那这样是不是就要先获取本身AP…