编辑距离
编辑距离(Edit Distance 或者 Levenshtein Distance)是为了量化两个字符串之间的差异,简单来说就是将一个字符串改成另一个字符串最少需要多少步
举个栗子
有一个字符串 a='love',b='lolpe'.那么计算a和b的编辑距离,就是要算出从a变化到 b需要经过多少个步骤。
1.love->lolve(插入l)
2.lolve->lolpe(用v替换成p)
那么我们就说他们的编辑距离为2
应用场景
从ssmetic的层面计算文本相似度
输入纠错 等等
定义公式
定义如下Edit Distance Defination
这里a_i不等于b_j时,增加的距离d=1
但是我在斯坦福的课程里看到的定义是这里加的是2
这里d=1修改成了d=2,是为了区分替换字符串与增删字符串的距离
如果不改变
假设字符串a='love',b='sffg',c='lovefghaa' 那么如果我们用上面计算出a和b,c的距离:
lev(a,b)=4,lev(a,c)=5
a跟b的距离小于a跟c的距离,显然不合理
所以实践中会采用下面这种定义
但是下面leetcode的题目里是用的第一种定义(d=1)
相关python库
那么如果不想自己写算法的话
可以偷懒使用以下库
python中的类库:pip install python-Levenshteindistance(str1, str2),计算编辑鲁丽
hamming(str1, str2),计算长度相等的字符串str1和str2的汉明距离
ratio(str1, str2),计算莱文斯坦比。计算公式 r = (sum – ldist) / sum, 其中sum是指str1 和 str2 字串的长度总和,ldist是类编辑距离。注意这里是类编辑距离,在类编辑距离中删除、插入依然+1,但是替换+2。
jaro(str1, str2)
jaro_winkler(str1, str2)
python实现方法
leetcode 题目链接
https://leetcode-cn.com/problems/edit-distance/
有两种思路,一种是递归,一种是动态规划
递归可以理解为倒推的,动态规划可以理解为正推
递归的自调用可能会超出限制,动态规划需要的内存比较多
递归方法
递归的方法在leetcode中会输出超出限制
很明显字符串超长的时候并不能使用递归
递归方法代码如下
class Solution(object):
def minDistance(self, word1, word2):
""":type word1: str:type word2: str:rtype: int"""
print(word1, word2)
if word1 == word2:
print('a=b')
return 0
elif (len(word2) == 0 )or (len(word1) == 0):
return max(len(word1),len(word2))
if word1[-1] == word2[-1]:
d = 0
else:
d = 2
return min(
self.minDistance(word1[:-1],word2) +1,
self.minDistance(word1,word2[:-1]) +1,
self.minDistance(word1[:-1],word2[:-1]) + d,
)
动态规划
我一开始是这么写的
def minDistance(word1, word2):
"""
:type word1: str
:type word2: str
:rtype: int
"""
#注意这里的矩阵行列
matrix = [[ i + j for j in range(wl_2 + 1)] for i in range(wl_1 + 1)]
for i in range(1,wl_1 +1):
for j in range(1,wl_2+1):
if word1[i-1] == word2[j-1]:
d = 0
else:
d = 1
matrix[i][j] = min(
matrix[i][j-1] +1,
matrix[i-1][j] +1,
matrix[i-1][j-1] + d
)
return matrix[-1][-1]
很明显这个虽然跑通了,但是排名是非常的amazing啊感人
显然有很多优化的空间
最容易优化的一开始就列出一些简单比较的情况
比如word1跟word2相同或者word1/word2中至少有一个为空的情况
def minDistance(word1, word2):
"""
:type word1: str
:type word2: str
:rtype: int
"""
if word1 == word2:
return 0
# 增加一点小小的限制
wl_1 = len(word1)
wl_2 = len(word2)
if wl_1==0 or lwl_2 ==0:
return max(wl_1, wl_2)
matrix = [[ i + j for j in range(wl_2 + 1)] for i in range(wl_1 + 1)]
for i in range(1,wl_1 +1):
for j in range(1,wl_2+1):
if word1[i-1] == word2[j-1]:
d = 0
else:
d = 1
matrix[i][j] = min(
matrix[i][j-1] +1,
matrix[i-1][j] +1,
matrix[i-1][j-1] + d
)
return matrix[-1][-1]
结果非常的amazing啊,时间一下就少了500+ms,所以细节非常重要显然还是不大行
但是还是很菜
但是经过我们精妙的观察
每次计算编辑距离的时候其实只涉及了当前计算的前一行数据
每次循环用到的数据范围如图
以此类推
所以我们根本不需要存整个matrix,只需要存两行数据就行
魔改后代码如下
class Solution(object):
def minDistance(self, word1, word2):
"""
:type word1: str
:type word2: str
:rtype: int
"""
if word1 == word2:
return 0
wl_1 = len(word1)
wl_2 = len(word2)
if wl_1==0 or wl_2 ==0:
return max(wl_1, wl_2)
v1, v2 = [], []
for i in range(wl_2+1):
v1.append(i), v2.append(i+1)
for i in range(1,wl_1 +1):
for j in range(1,wl_2+1):
if word1[i - 1] == word2[j - 1]:
d = 0
else:
d = 1
minValue = min( v1[j] + 1,
v1[j - 1] + d ,
v2[j-1] + 1,
)
v2[j] = minValue
for j in range(wl_2 + 1):
v1[j] = v2[j]
v2[j] = i+1
return v1[-1]
修改后的代码内存消耗骤降内存方面已经成为王者了,但是在时间消耗方面还可以优化下
怎么减少时间我还没有想好QAQ
等我想好了再来更新