莱文斯坦距离
莱文斯坦距离是一种衡量两个字符串差异程度的编辑距离,用来表示将一个字符串转换为另一个字符串所需的最少编辑操作次数。允许进行的基本操作包括:
- 插入一个字符
- 删除一个字符
- 替换一个字符
莱文斯坦距离广泛应用于拼写检查、文本纠错、模糊搜索和自然语言处理等领域,是衡量字符串相似度最常用的方法之一。例如,搜索引擎和拼写检查程序都会利用它找出与目标单词最接近的候选结果。
计算莱文斯坦距离通常采用动态规划算法。算法通过构建一个矩阵,逐步计算两个字符串各个前缀之间的最小编辑代价,最终得到整个字符串之间的最小编辑次数。
与汉明距离不同,莱文斯坦距离不仅适用于长度相同的字符串,也适用于长度不同的字符串。
计算示例
下面以两个字符串 \( s \) 和 \( t \) 为例:
$$ s = "kitten" $$
$$ t = "sitting" $$
目标是计算它们之间的莱文斯坦距离,也就是把 "kitten" 转换为 "sitting" 所需的最少编辑次数。
- 替换
将 \( k \) 替换为 \( s \): $$ "kitten" \rightarrow "sitten" $$ - 替换
将 \( e \) 替换为 \( i \): $$ "sitten" \rightarrow "sittin" $$ - 插入
在字符串末尾插入字符 \( g \): $$ "sittin" \rightarrow "sitting" $$
整个转换过程共进行了 3 次编辑操作,其中包括 2 次替换和 1 次插入,因此:
$$ D(\text{kitten},\text{sitting}) = 3 $$
为了计算这一结果,可以建立一个大小为 \( (m+1)\times(n+1) \) 的矩阵,其中 \( m=6 \) 是字符串 "kitten" 的长度,\( n=7 \) 是字符串 "sitting" 的长度。
矩阵额外增加的一行和一列用于表示空字符串,从而能够统一处理所有编辑操作。

第一行 \( (0,j) \) 表示将空字符串转换为 "sitting" 前 \( j \) 个字符所需的编辑次数,因此数值依次为 0、1、2,一直到 7。
第一列 \( (i,0) \) 表示将 "kitten" 前 \( i \) 个字符转换为空字符串所需的编辑次数,因此数值依次增加到 6。
对于矩阵中的每一个单元格 \( (i,j) \),都需要比较以下三种可能的操作:
- 删除字符,对应上方单元格的值加 1。
- 插入字符,对应左侧单元格的值加 1。
- 替换字符。如果两个字符不同,则取左上角单元格的值加 1;如果两个字符相同,则直接使用左上角单元格的值。
随后按照从左到右、从上到下的顺序,依次填充整个矩阵。

例如,空字符串依次转换为 "s"、"si"、"sit" 时,每增加一个字符,编辑代价都会增加 1。
同样,将 "k"、"ki"、"kit" 等转换为空字符串时,每删除一个字符,编辑代价也增加 1。
- 单元格 (1,1) 表示将 "k" 转换为 "s"。由于只需一次替换(k→s),因此代价为 1。

- 单元格 (2,2) 表示将 "ki" 转换为 "si"。第二个字符相同,因此总代价仍为 1。
- 单元格 (3,3) 表示将 "kit" 转换为 "sit"。第三个字符相同,因此总代价仍为 1。
- 单元格 (4,4) 表示将 "kitt" 转换为 "sitt"。第四个字符也相同,因此总代价依然为 1。
- 单元格 (5,5) 表示将 "kitte" 转换为 "sitti"。由于需要将 \( e \) 替换为 \( i \),总代价变为 2。

- 单元格 (6,6) 表示将 "kitten" 转换为 "sittin"。最后一个字符相同,因此总代价仍然为 2。
- 单元格 (6,7) 表示将 "kitten" 转换为 "sitting"。由于最后还需要插入字符 \( g \),总代价增加到 3。

当整个矩阵填充完成后,右下角的单元格就是最终结果。
在本例中,右下角单元格 \( (6,7) \) 的值为 3,这意味着将 "kitten" 转换为 "sitting" 至少需要 3 次编辑操作。
具体来说,这 3 次操作分别是:将 k 替换为 s,将 e 替换为 i,以及在末尾插入字符 g。这个最小编辑次数就是两个字符串之间的莱文斯坦距离。
莱文斯坦距离诱导的拓扑
与汉明距离一样,莱文斯坦距离满足度量的全部基本公理,因此能够在字符串集合上定义一个度量空间。
- 非负性:对于任意两个字符串 \( x \) 和 \( y \),都有 \( D_L(x,y)\ge0 \)。只有当 \( x=y \) 时,才有 \( D_L(x,y)=0 \)。也就是说,一个字符串无需经过任何编辑操作即可转换为自身,因此只有完全相同的字符串之间距离才为零。
- 对称性:莱文斯坦距离满足 \( D_L(x,y)=D_L(y,x) \)。将字符串 \( x \) 转换为 \( y \) 所需的最少编辑次数,与将 \( y \) 转换为 \( x \) 所需的最少编辑次数完全相同,因为插入、删除和替换操作都具有对应的逆操作。
- 三角不等式:莱文斯坦距离满足三角不等式: \( D_L(x,z)\le D_L(x,y)+D_L(y,z) \)。 换句话说,从 \( x \) 到 \( z \) 的最短编辑距离,不会超过先将 \( x \) 转换为 \( y \),再将 \( y \) 转换为 \( z \) 所需编辑次数之和。这是因为任何从 \( x \) 到 \( z \) 的编辑过程,都可以拆分为这两个连续的转换过程。
正因为满足上述三个性质,莱文斯坦距离能够在字符串集合上诱导出一个度量空间。
进一步地,由于它满足度量空间的全部公理,因此还可以利用它在字符串集合上定义一个由度量诱导的拓扑。
设字符串 \( x \) 为中心、半径为 \( r \),则以 \( x \) 为中心的开球定义为:
$$ B(x,r)=\{\,y\mid D_L(x,y)
该拓扑中的所有开集,都可以表示为这些开球的任意并集。
这种拓扑结构在实际应用中具有重要意义,尤其适用于需要衡量字符串"接近程度"的场景。例如,在自动拼写纠错中,如果某个单词与词典中的某个单词具有较小的莱文斯坦距离,那么它就可能被推荐为正确的拼写。
因此,借助莱文斯坦距离,我们不仅能够比较字符串之间的差异,还可以把字符串看作度量空间中的点,并利用由该度量诱导出的拓扑研究它们之间的邻近关系。
实例演示
设字符串集合 \( X \) 为:
$$ X=\{\text{"cat"},\text{"bat"},\text{"cut"}\} $$
下面以半径 \( r=2 \) 的开球为例,构造该拓扑的一组基。
$$ B(x,r)=\{\,y\mid D_L(x,y)
由于这里采用的是开球,因此要求满足 \( D_L(x,y)<2 \)。这意味着,开球中包含所有与中心字符串编辑距离为 0 或 1 的字符串,而编辑距离等于 2 的字符串则不包含在内。
说明:半径取 \( r=2 \) 时,开球定义为: $$ B(x,2)=\{\,y\mid D_L(x,y)<2\,\} $$ 由于开集不包含边界,因此编辑距离恰好等于 2 的字符串不会属于该集合。如果希望将边界也包含在内,则应使用闭球: $$ C(x,2)=\{\,y\mid D_L(x,y)\le2\,\} $$ 这样,所有编辑距离不超过 2 的字符串都会包含在集合中。
下面分别计算集合 \( X \) 中各个字符串对应的半径为 2 的开球。
- 以 "cat" 为中心的开球 $$ B(\text{"cat"},2)=\{\text{"cat"},\text{"bat"},\text{"cut"}\} $$ 在集合 \( X \) 中,"bat" 和 "cut" 与 "cat" 的莱文斯坦距离都等于 1,因此它们都属于该开球。
- 以 "bat" 为中心的开球 $$ B(\text{"bat"},2)=\{\text{"bat"},\text{"cat"}\} $$ 在集合 \( X \) 中,只有 "cat" 与 "bat" 相距一次编辑操作,因此它属于该开球。
- 以 "cut" 为中心的开球 $$ B(\text{"cut"},2)=\{\text{"cut"},\text{"cat"}\} $$ 同样,"cat" 与 "cut" 的编辑距离为 1,因此也包含在该开球中。
因此,下面三个开球:
$$ B(\text{"cat"},2),\quad B(\text{"bat"},2),\quad B(\text{"cut"},2) $$
共同构成了由莱文斯坦距离诱导拓扑的一组基。
借助这组基,可以通过任意多个开球的并生成该拓扑中的所有开集。
例如,考虑开球 \( B(\text{"bat"},2) \) 与 \( B(\text{"cut"},2) \) 的并集:
$$ B(\text{"bat"},2)\cup B(\text{"cut"},2) $$
已知:
$$ B(\text{"bat"},2)=\{\text{"bat"},\text{"cat"}\} $$
以及:
$$ B(\text{"cut"},2)=\{\text{"cut"},\text{"cat"}\} $$
因此:
$$ \{\text{"bat"},\text{"cat"}\} \cup \{\text{"cut"},\text{"cat"}\} = \{\text{"cat"},\text{"bat"},\text{"cut"}\} $$
由此可见,集合
$$ \{\text{"cat"},\text{"bat"},\text{"cut"}\} $$
也是该拓扑中的一个开集。
通过这种构造方法,就可以在集合 \( X \) 上建立由莱文斯坦距离诱导的度量拓扑,并利用这些基本开球生成拓扑中的任意开集。
其他情况也可以采用完全相同的方法进行分析。