Sweet Snippet 之 字符串编辑距离
- 2019 年 10 月 4 日
- 筆記
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/tkokof1/article/details/100709721
字符串编辑距离的简单实现
字符串编辑距离应该是动态规划中的代表问题了:
给定两个字符串 aaa 与 bbb,求解将 aaa 编辑至 bbb 的操作步数(距离),编辑包含以下两种操作:
- 删除某一字符
- 增加某一字符
(这里我们不允许变更某一字符,注意一下)
求解方法则是根据子问题的结果"递推"出原问题的结果:
设字符串 aaa 的长度为 mmm, 字符串 bbb 的长度为 nnn, 我们定义问题 C(i,j)C(i, j)C(i,j)
C(i,j)C(i, j)C(i,j) : aaa 的(前缀)子串(长度为 iii) 与 bbb 的(前缀)子串(长度为 jjj) 的字符串编辑距离.
接着就是 C(i,j)C(i, j)C(i,j) 的递推公式了(这里就不做细节的讲述了,不熟悉的朋友可以参考进一步的资料)
C(i,j)={i,if j=0j,if i=0C(i−1,j−1),if a[i]=b[j]min(C(i−1,j),C(i,j−1))+1,otherwise C(i, j) = left{ begin{aligned} % & 0, & if i = 0 and j = 0 \ & i, & if j = 0 \ & j, & if i = 0 \ & C(i – 1, j – 1), & if a[i] = b[j] \ & min(C(i – 1, j), C(i, j – 1)) + 1, & otherwise end{aligned} right. C(i,j)=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧i,j,C(i−1,j−1),min(C(i−1,j),C(i,j−1))+1,if j=0if i=0if a[i]=b[j]otherwise
下面简单列份实现(Lua):
-- get key from two index function get_key(m, n) return m .. "_" .. n end function edit_dist_iter(a, b, m, n) local edit_dist_buffer = {} edit_dist_buffer[get_key(0, 0)] = 0 for i = 1, m do edit_dist_buffer[get_key(i, 0)] = i end for i = 1, n do edit_dist_buffer[get_key(0, i)] = i end for i = 1, m do for j = 1, n do local ac = a:sub(i, i) local bc = b:sub(j, j) if ac == bc then edit_dist_buffer[get_key(i, j)] = edit_dist_buffer[get_key(i - 1, j - 1)] else local d1 = edit_dist_buffer[get_key(i - 1, j)] local d2 = edit_dist_buffer[get_key(i, j - 1)] edit_dist_buffer[get_key(i, j)] = math.min(d1, d2) + 1 end end end return edit_dist_buffer[get_key(m, n)] end function edit_dist(a, b) return edit_dist_iter(a, b, #a, #b) end
以上的代码使用了迭代形式,我们也可以用递归形式(来编写代码),只是递归会引起不少的重复计算,所以(工程)实现上,我们需要使用缓存来记录计算过的子问题结果(迭代版本也使用了缓存,作用上和递归版本其实也是一致的,记录的也是子问题的结果):
-- get key from two index function get_key(m, n) return m .. "_" .. n end function edit_dist_recur(a, b, m, n, buffer) if m <= 0 then -- result is trivial, do not need buffer return n elseif n <= 0 then -- result is trivial, do not need buffer return m else local ac = a:sub(m, m) local bc = b:sub(n, n) if ac == bc then local d = buffer[get_key(m - 1, n - 1)] if d then buffer[get_key(m, n)] = d return d else local d = edit_dist_recur(a, b, m - 1, n - 1, buffer) buffer[get_key(m, n)] = d return d end else local d1 = buffer[get_key(m - 1, n)] if not d1 then d1 = edit_dist_recur(a, b, m - 1, n, buffer) end local d2 = buffer[get_key(m, n - 1)] if not d2 then d2 = edit_dist_recur(a, b, m, n - 1, buffer) end local d = math.min(d1, d2) + 1 buffer[get_key(m, n)] = d return d end end end function edit_dist(a, b) -- create buffer local edit_dist_buffer = {} return edit_dist_recur(a, b, #a, #b, edit_dist_buffer) end
另外还看到一种基于编辑图(Edit Graph)的实现方式,不过思路上仍然和之前的讲述是一致的,实现上则会更复杂些,在此就不列代码了~