实现Levenshtein距离计算算法的Java程序
Levenshtein 距离也称为编辑距离,是将一个字符串转换为另一个字符串所需的最少操作数。
通常,执行三种类型的操作(一次一种):
- 替换一个字符。
- 删除一个字符。
- 插入一个字符。
例子:
Input: str1 = “glomax”, str2 = “folmax”
Output: 3
str1 is converted to str2 by replacing ‘g’ with ‘o’, deleting the second ‘o’, and inserting ‘f’ at the beginning. There is no way to do it with fewer than three edits.
Input: s1 = “GIKY”, s2 = “GEEKY”
Output: 2
s1 is converted to s2 by inserting ‘E’ right after ‘G’,and replacing ‘I’ with ‘E’.
这个问题可以通过两种方式解决:
- 使用递归。
- 使用动态规划。
方法 1:递归方法
让我们举个例子来考虑
给定两个字符串s1 = “sunday” 和 s2 = “saturday”。我们希望以最少的编辑将“星期日”转换为“星期六”。
- 将 'i' 和 'j' 视为使用 s1 和 s2 生成的子串的上限索引。
- 让我们选择 i = 2 和 j = 4,即前缀字符串分别是 'su' 和 'satu'(假设字符串索引从 1 开始)。最右边的字符可以以三种不同的方式对齐。
- 可能的情况 1 :对齐字符'u' 和 'u'。它们是相等的,不需要编辑。我们仍然留下了 i = 1 和 j = 3 的问题,所以我们应该继续寻找 Levenshtein distance(i-1, j-1)。
- 可能的情况 2 (删除):对齐第一个字符串中的右侧字符,而第二个字符串中没有字符。我们需要在这里删除。我们仍然留下了 i = 1 和 j = 4 的问题,所以我们应该继续寻找 Levenshtein distance(i-1, j)。
- 可能的情况 3 (插入):对齐第二个字符串中的右侧字符,而不对齐第一个字符串中的字符。我们需要在这里插入。我们仍然留下了 i = 2 和 j = 3 的问题,所以我们应该继续寻找 Levenshtein distance(i, j-1)。
- 我们假设要插入第一个字符串的字符与第二个字符串的右侧字符相同。
- 可能的情况 4 (替换):对齐第一个字符串和第二个字符串中的右侧字符。我们这里需要一个替代品。我们仍然留下了 i = 1 和 j = 3 的问题,所以我们应该继续寻找 Levenshtein distance(i-1, j-1)。
- 我们假设第一个字符串中被替换的字符与第二个字符串的右侧字符相同。
- 我们必须找到所有可能的三种情况中的最小值。
递归实现:
Java
// Java implementation of recursive Levenshtein distance
// calculation
import java.util.*;
class LevenshteinDistanceRecursive {
static int compute_Levenshtein_distance(String str1,
String str2)
{
// If str1 is empty, all
// characters of str2 are
// inserted into str1, which is
// of the only possible method of
// conversion with minimum
// operations.
if (str1.isEmpty())
{
return str2.length();
}
// If str2 is empty, all
// characters of str1 are
// removed, which is the
// only possible
// method of conversion with minimum
// operations.
if (str2.isEmpty())
{
return str1.length();
}
// calculate the number of distinct characters to be
// replaced in str1
// by recursively traversing each substring
int replace = compute_Levenshtein_distance(
str1.substring(1), str2.substring(1))
+ NumOfReplacement(str1.charAt(0),str2.charAt(0));
// calculate the number of insertions in str1
// recursively
int insert = compute_Levenshtein_distance(
str1, str2.substring(1))+ 1;
// calculate the number of deletions in str1
// recursively
int delete = compute_Levenshtein_distance(
str1.substring(1), str2)+ 1;
// returns minimum of three operatoins
return minm_edits(replace, insert, delete);
}
static int NumOfReplacement(char c1, char c2)
{
// check for distinct characters
// in str1 and str2
return c1 == c2 ? 0 : 1;
}
static int minm_edits(int... nums)
{
// receives the count of different
// operations performed and returns the
// minimum value among them.
return Arrays.stream(nums).min().orElse(
Integer.MAX_VALUE);
}
// Driver Code
public static void main(String args[])
{
String s1 = "glomax";
String s2 = "folmax";
System.out.println(compute_Levenshtein_distance(s1, s2));
}
}
Java
// Java implementation of Levenshtein distance calculation
// Using Dynamic Programming (Optimised solution)
import java.util.*;
class LevenshteinDistanceDP {
static int compute_Levenshtein_distanceDP(String str1,
String str2)
{
// A 2-D matrix to store previously calculated
// answers of subproblems in order
// to obtain the final
int[][] dp = new int[str1.length() + 1][str2.length() + 1];
for (int i = 0; i <= str1.length(); i++)
{
for (int j = 0; j <= str2.length(); j++) {
// If str1 is empty, all characters of
// str2 are inserted into str1, which is of
// the only possible method of conversion
// with minimum operations.
if (i == 0) {
dp[i][j] = j;
}
// If str2 is empty, all characters of str1
// are removed, which is the only possible
// method of conversion with minimum
// operations.
else if (j == 0) {
dp[i][j] = i;
}
else {
// find the minimum among three
// operations below
dp[i][j] = minm_edits(dp[i - 1][j - 1]
+ NumOfReplacement(str1.charAt(i - 1),str2.charAt(j - 1)), // replace
dp[i - 1][j] + 1, // delete
dp[i][j - 1] + 1); // insert
}
}
}
return dp[str1.length()][str2.length()];
}
// check for distinct characters
// in str1 and str2
static int NumOfReplacement(char c1, char c2)
{
return c1 == c2 ? 0 : 1;
}
// receives the count of different
// operations performed and returns the
// minimum value among them.
static int minm_edits(int... nums)
{
return Arrays.stream(nums).min().orElse(
Integer.MAX_VALUE);
}
// Driver Code
public static void main(String args[])
{
String s1 = "glomax";
String s2 = "folmax";
System.out.println(compute_Levenshtein_distanceDP(s1, s2));
}
}
3
时间复杂度: O(3^n),因为在每一步,我们都分为三个递归调用。这里,'n' 是第一个字符串的长度。
方法 2:动态规划方法
如果我们绘制上述解决方案的递归树,我们可以看到相同的子问题被一次又一次地计算。我们知道,当子问题的解决方案可以被记忆而不是一次又一次地计算时,动态规划就会出现。
- Memoized版本遵循自上而下的方法,因为我们首先将问题分解为子问题,然后计算和存储值。
- 我们也可以用自下而上的方法来解决这个问题。以自下而上的方式,我们首先解决子问题,然后从中解决更大的子问题。
动态规划实现(优化方法)
Java
// Java implementation of Levenshtein distance calculation
// Using Dynamic Programming (Optimised solution)
import java.util.*;
class LevenshteinDistanceDP {
static int compute_Levenshtein_distanceDP(String str1,
String str2)
{
// A 2-D matrix to store previously calculated
// answers of subproblems in order
// to obtain the final
int[][] dp = new int[str1.length() + 1][str2.length() + 1];
for (int i = 0; i <= str1.length(); i++)
{
for (int j = 0; j <= str2.length(); j++) {
// If str1 is empty, all characters of
// str2 are inserted into str1, which is of
// the only possible method of conversion
// with minimum operations.
if (i == 0) {
dp[i][j] = j;
}
// If str2 is empty, all characters of str1
// are removed, which is the only possible
// method of conversion with minimum
// operations.
else if (j == 0) {
dp[i][j] = i;
}
else {
// find the minimum among three
// operations below
dp[i][j] = minm_edits(dp[i - 1][j - 1]
+ NumOfReplacement(str1.charAt(i - 1),str2.charAt(j - 1)), // replace
dp[i - 1][j] + 1, // delete
dp[i][j - 1] + 1); // insert
}
}
}
return dp[str1.length()][str2.length()];
}
// check for distinct characters
// in str1 and str2
static int NumOfReplacement(char c1, char c2)
{
return c1 == c2 ? 0 : 1;
}
// receives the count of different
// operations performed and returns the
// minimum value among them.
static int minm_edits(int... nums)
{
return Arrays.stream(nums).min().orElse(
Integer.MAX_VALUE);
}
// Driver Code
public static void main(String args[])
{
String s1 = "glomax";
String s2 = "folmax";
System.out.println(compute_Levenshtein_distanceDP(s1, s2));
}
}
3
时间复杂度: O(m*n),其中 m 是第一个字符串的长度,n 是第二个字符串的长度。
辅助空间: O(m*n),因为上述实现中使用的矩阵具有维度m*n 。
应用:
- 拼写检查器。
- 语音识别。
- DNA 分析。