使用Damerau-Levenshtein自动机实现字符串模糊查询
2016-08-04 17:16
567 查看
用途
文本模糊匹配,例如搜索引擎Lucene 从4.0版本开始就使用了LevenshteinAutomaton对用户输入的搜索字符进行模糊查询原理
Levenshtein 距离
计算用户输入字符串和索引字符串之间的编辑距离, 最常用的是Levenshtein距离。基于Levenshtein距离的模糊匹配允许用户即使输入的查询字符串与匹配的目标字符串存在n个字符的插入/删除/替换 差异, 匹配仍然能够成功。 例如, 索引字符串 ‘ab’ 的匹配Levenshtein距离为1的全部DFA状态转移图如下 , 其中深色表示accepting state实际使用的时候不一会构建一个这样复杂的匹配所有全部可能状态的DFA,这里为举例说明
Levenshtein距离公式
表示字符串a的前i个字符与字符串b的前j个字符的Levenshtein距离, min函数对应的第一项表示从a[i] 删除最后一个字符时的情况, 第二项表示从插入一个字符的情况, 第3项表示替换, 即a[i]与b[j]匹配或者不匹配时候的情况
例如, 输入字符串 ‘cabana’, 匹配目标 ‘banana’, Levenshtein矩阵计算过程如下:
b, a, n, a, n, a [0, 1, 2, 3, 4, 5, 6] c [1, 1, 2, 3, 3, 3, 3] a [2, 2, 1, 2, 3, 3, 3] b [3, 2, 2, 2, 3, 3, 3] a [3, 3, 2, 3, 2, 3, 3] n [3, 3, 3, 2, 3, 2, 3] a [3, 3, 3, 3, 2, 3, 2]
Damerau-Levenshtein 距离
Damerau-Levenshtein距离公式该公式与Levenshtein距离公式的主要区别是加入了一项
用来计算相邻两个字符的置换( transposition)
Damerau-Levenshtein DFA, string=‘ab’, n = 1 , 深色表示accepting state
可以看出, 该DFA与纯Levenshtein的主要区别是可以接受 ‘ba’ 作为输入。实际使用的时候不一定会构建一个这样复杂的匹
4000
配所有全部可能状态的DFA,这里仅为举例说明
与其它算法相比的优缺点
与trie比较
缺点1. 字符串匹配速度较marisa trie慢, 匹配时的时间复杂度里面包括索引数量项,而trie树一旦构建完成就与索引数量无关搜索速度只与最大索引长度有关
2. 自动机前缀匹配结果远不如trie, 例如输入‘快乐’无法匹配到‘快乐大本营’, 因为这两个字符串相差3个字符,而实际使用时一般最多设置编辑距离为2.
优点
1. 容错,支持模糊查询,DamerauLevenshtein还支持匹配任意两个相邻字符换位后的结果,非常适合用来做拼写检查以及语义分析
与KMP算法比较
优点1. 本文实现的SparseDamerauLevenshtein速度比KMP算法快,时间复杂度为M * N * C, 其中M为索引数量, N为输入的搜索字符串长度, C为设定的最大编辑距离,一般为1或者2. 而Kmp算法时间复杂度为N + M * avg_len, 其中avg_len为平均索引长度, N是需要匹配前先计算的,由于一般搜索时输入的字符串不会太长,这一项开销可以忽略不计。综上, 比较SparseDamerauLevenshtein和KMP的时间复杂度实际上就是比较M * N和M * avg_len的复杂度。由于N一般都小于索引的平均长度avg_len, 所以实际用于自动完成或者前缀搜索时 SparseDamerauLevenshtein 比 KMP 算法快。
缺点
1. 前后缀搜索效果均不如KMP(Trie树只能搜索前缀, 基于编辑距离的匹配算法在两个字符串长度相差较大时无能为力), 尤其是用户输入的搜索字符串较短时.
实现细节
SparseLevenshteinAutomaton的实现基于github repo, 本文实现与该repo相比的主要区别是1. jules jacobs的实现主要是simple proof of concept, 本文的实现是一个包含了测试代码,提供了隐藏了具体实现细节、包括完整字符串模糊匹配接口的wrapper class
2. 扩展了jules jacobs实现的功能。 本文实现了基于Damerau-Levenshtein距离的Sparse Damerau-Levenshtein Automaton, 可以识别任意相邻两个字符置换/换位后的字符串, 提供了state_equal等验证空间压缩版本的Sparse Automaton功能正确性的检验函数。
实现细节
1. 不保存整个Levenshtein距离矩阵,只保存矩阵当前行(DameruLevenshteinAutomaton会保存矩阵的前一行作为内部状态, 而LevenshteinAutomaton类不直接保存任何状态)
2. SparseLevenshteinAutomaton和SparseDameruLevenshteinAutomaton类每一行只保存当前小于max_edit的矩阵行元素的下标和值, 这样每一次匹配一个输入字符的时候只需要处理最多2 * max_edit + 1个元素, 使得匹配单个字符的时间复杂度由Length(string) 下降为c, 其中Length(string)为索引字符串长度,c为最大编辑距离,一般可忽略, 从而使得整个算法变得实际可用
3. SparseDameruLevenshteinAutomaton实现使用optimal string alignment算法, 该算法的伪码来自wiki
测试输出
本文所述python实现github, 其中包括字符串模糊匹配类定义及实现文件,测试代码文件, 测试输出文件
DamerauLevenshteinAutomaton测试输出
SparseLevenshteinAutomaton test 快乐大本营 -> 0.9 天天向上 -> 0.85 快乐大本营: 大电影 -> 0.8 大本营花絮 -> 0.75 快乐购 -> 0.7 快乐家族 -> 0.6 快乐男声 -> 0.5 快乐中国 -> 0.4 快乐垂钓 -> 0.3 快乐本大营 -> 0.1 lev distance = 0 lev distance 0, time: 0.000234 '快乐大本营' -> "快乐大本营" "----" lev distance 0, time: 0.000047 '大本营' -> "----" lev distance = 1 lev distance 1, time: 0.000439 '快乐大本营' -> "快乐大本营" "----" lev distance 1, time: 0.000067 '大本营' -> "----" lev distance = 2 prefix: 快乐大本营, word: 快乐本大营, cur_i: 4, match error count 2 lev distance 2, time: 0.000830 '快乐大本营' -> "快乐大本营" "----" "快乐本大营" prefix: 大本营, word: 大本营花絮, cur_i: 2, match error count 2 lev distance 2, time: 0.000134 '大本营' -> "----" "大本营花絮" lev distance = 3 prefix: 快乐大本营, word: 快乐本大营, cur_i: 4, match error count 2 prefix: 快乐大本营, word: 快乐家族, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐购, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐男声, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐中国, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐垂钓, cur_i: 4, match error count 3 lev distance 3, time: 0.000943 '快乐大本营' -> "快乐大本营" "----" "快乐购" "快乐家族" "快乐男声" "快乐中国" "快乐垂钓" "快乐本大营" prefix: 大本营, word: 大本营花絮, cur_i: 2, match error count 2 lev distance 3, time: 0.000144 '大本营' -> "----" "大本营花絮" lev distance = 4 prefix: 快乐大本营, word: 快乐本大营, cur_i: 4, match error count 2 prefix: 快乐大本营, word: 快乐家族, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐购, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐男声, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐中国, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐垂钓, cur_i: 4, match error count 3 lev distance 4, time: 0.000998 '快乐大本营' -> "快乐大本营" "----" "快乐购" "快乐家族" "快乐男声" "快乐中国" "快乐垂钓" "快乐本大营" prefix: 大本营, word: 大本营花絮, cur_i: 2, match error count 2 lev distance 4, time: 0.000148 '大本营' -> "----" "大本营花絮"
DamerauLevenshteinAutomaton测试输出
DamerauLevenshteinAutomaton test 快乐大本营 -> 0.9 天天向上 -> 0.85 快乐大本营: 大电影 -> 0.8 大本营花絮 -> 0.75 快乐购 -> 0.7 快乐家族 -> 0.6 快乐男声 -> 0.5 快乐中国 -> 0.4 快乐垂钓 -> 0.3 快乐本大营 -> 0.1 lev distance = 0 lev distance 0, time: 0.000271 '快乐大本营' -> "快乐大本营" "----" lev distance 0, time: 0.000051 '大本营' -> "----" lev distance = 1 prefix: 快乐大本营, word: 快乐本大营, cur_i: 4, match error count 1 lev distance 1, time: 0.000553 '快乐大本营' -> "快乐大本营" "----" "快乐本大营" lev distance 1, time: 0.000076 '大本营' -> "----" lev distance = 2 prefix: 快乐大本营, word: 快乐本大营, cur_i: 4, match error count 1 lev distance 2, time: 0.000786 '快乐大本营' -> "快乐大本营" "----" "快乐本大营" prefix: 大本营, word: 大本营花絮, cur_i: 2, match error count 2 lev distance 2, time: 0.000146 '大本营' -> "----" "大本营花絮" lev distance = 3 prefix: 快乐大本营, word: 快乐本大营, cur_i: 4, match error count 1 prefix: 快乐大本营, word: 快乐家族, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐购, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐男声, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐中国, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐垂钓, cur_i: 4, match error count 3 lev distance 3, time: 0.001079 '快乐大本营' -> "快乐大本营" "----" "快乐购" "快乐家族" "快乐男声" "快乐中国" "快乐垂钓" "快乐本大营" prefix: 大本营, word: 大本营花絮, cur_i: 2, match error count 2 lev distance 3, time: 0.000155 '大本营' -> "----" "大本营花絮" lev distance = 4 prefix: 快乐大本营, word: 快乐本大营, cur_i: 4, match error count 1 prefix: 快乐大本营, word: 快乐家族, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐购, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐男声, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐中国, cur_i: 4, match error count 3 prefix: 快乐大本营, word: 快乐垂钓, cur_i: 4, match error count 3 lev distance 4, time: 0.001142 '快乐大本营' -> "快乐大本营" "----" "快乐购" "快乐家族" "快乐男声" "快乐中国" "快乐垂钓" "快乐本大营" prefix: 大本营, word: 大本营花絮, cur_i: 2, match error count 2 lev distance 4, time: 0.000160 '大本营' -> "----" "大本营花絮"
参考文献
Levenshtein automata can be simple and fastLevenshtein_distance
Damerau–Levenshtein distance
相关文章推荐
- asp.net+SQL2005实现字符串的模糊查询
- mysql使用参数化查询,like模糊查询,应如何拼接字符串
- 使用jQuery实现实时模糊查询功能
- 在使用ibatis实现多条件模糊查询的语句
- Hibernate中使用@Query实现模糊查询功能
- Mybatis中用sql实现模糊查询,使用concat连接
- 使用全文索引技术实现高效的like模糊查询功能
- 字符串模糊匹配使用递归实现
- Asp.Net使用Easy UI DataGrid 实现行过滤[行模糊查询数据]
- ORACLE如何使用模糊查询(如何实现关键字搜索)
- Redis集群下使用Jedis实现keys模糊查询
- 使用全文索引技术实现高效的like模糊查询功能
- Oracle 模糊查询中不包括某字符串的实现方式
- 实现List按与一个字符串的相似度和字母顺序排序(适用于模糊查询后的排序)
- PHP+MySQL使用mysql_num_rows实现模糊查询图书信息功能
- 使用HQL语句方式实现多表多条件组合模糊查询
- c# 使用linq查询子句方式实现 字符串数组统计操作
- C语言之字符串模糊查询方法的实现
- 使用easyUI的combogrid来实现模糊查询,jsp部分
- 在使用ibatis实现多条件模糊查询的语句