编辑距离单词匹配算法
1、介绍
编辑距离(Edit Distance),在本文指的是Levenshtein距离,也就是字符串S1通过插入、修改、删除三种操作最少能变换成字符串S2的次数。例如:S1 = abc,S2 = abf,编辑距离d = 1(只需将c修改为f)。在本文中将利用动态规划的算法思想对字符串的编辑距离求解。
定义:S1、S2表示两个字符串,S1(i)表示S1的第一个字符,d[i, j]表示S1的第i个前缀到S2的第j个前缀(例如:S1 = ”abc”,S2 = ”def”,求解S1到S2的编辑距离为d[3, 3])。
若S1 = ”abc”, S2 = ”dec”,此时它们的编辑距离为d[3, 3] = 2,观察两个字符串的最后一个字符是相同的,也就是说S1(3) = S2(3)不需要做任何变换,故S1 = ”abc”, S2 = ”dec” <= > S1’ = ”ab”, S2’ = ”de”,即当S1[i] = S[j]时,d[i, j] = d[i-1,j -1]。得到公式:d[i, j] = d[i - 1, j - 1] (S1[i] = S2[j])
上面一条得出了当S1[i] = S2[j]的计算公式,显然还有另一种情况就是S1[i] ≠ S2[j],若S1 = ”abc”, S2 = ”def”。S1变换到S2的过程可以“修改”,但还可以通过“插入”、“删除”使得S1变换为S2。
1)在S1字符串末位插入字符“f”,此时S1 = ”abcf”,S2 = ”def”,此时即S1[i] = S2[j]的情况,S1变换为S2的编辑距离为d[4, 3] = d[3, 2]。所以得出d[i, j]=d[i, j - 1] + 1。(+1是因为S1新增了”f”)
2)在S2字符串末位插入字符“c”,此时S1 = ”abc”,S2 = ”defc”,此时即S1[i] = S[j]的情况,S1变换为S2的编辑距离为d[3, 4] = d[2, 3]。所以得出d[i, j]=d[i - 1, j] + 1,实际上这是对S1做了删除。(+1是因为S2新增了”c”)
3)将S1字符串末位字符修改为”f”,此时S1 = ”abf”,S2 = ”def”,此时即S1[i] = S[j]的情况,S1变换为S2的编辑距离为d[3, 3] = d[2, 2]。所以得出d[i, j] = d[i – 1, j - 1] + 1。(+1是因为S1修改了“c”)
4)综上,得出递推公式:
=>
5)图解过程:
通过依次计算出每个子串之间的d[ i ][ j ],最终得到以下的表格
2、C代码
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define N 660
typedef struct {
char word[64];
char meaning[128];
} node;
node dict[N];//结构体数组,存储单词文件
int distance(char *a, char *b) {//形参a,b分别是输入的单词和dict数组中的单词
int i, j, m, n;
m = strlen(a);
n = strlen(b);
int c[m + 1][n + 1];
for (i = 0; i <= m; i++)
c[i][0] = i;
for (j = 1; j <= n; j++)
c[0][j] = j;
for (i = 1; i <= m; i++)
for (j = 1; j <= n; j++) {
int u, v, w;
v = c[i][j - 1] + 1;//对应1中的1)
u = c[i - 1][j] + 1;//对应1中的2)
//对应1中的3)
w = c[i - 1][j - 1];
//取比较的最小值就是当前子串的编辑距离
if (a[i - 1] != b[j - 1])
w++;
//对应1中的3)
if (u > v) u = v;
if (u > w) u = w;
c[i][j] = u;
//取比较的最小值就是当前子串的编辑距离
}
return c[m][n];//返回两个单词比较的最终编辑距离
}
void find(char word[64], int dist) {
int mark, i, count;
count = 0;
for (i = 0; i < N; ++i) {//循环读取dict数组中的单词约输入单词匹配
mark = distance(word, dict[i].word);//返回编辑距离
if (mark <= dist) {//返回值是否和输入的范围一致
printf("\t%s\n",dict[i].meaning);
count++;//匹配到的单词个数
}
}
if (count==0){//没有匹配到单词
printf("无匹配单词!\n");
}
}
void readdict(char dictname[]) {
char item[64];
int i;
FILE *f = fopen(dictname, "r");//读取文件,模式为只读
for (i = 0; i < N; ++i) {
fscanf(f, "%s", dict[i].word);//存入dict结构体数组
while (fscanf(f, "%s", item), isalpha(item[0]) > 0) {//读取词组
strcat(dict[i].word, " ");//拼接词组
strcat(dict[i].word, item);
}
strcpy(dict[i].meaning, item);//单词解释
}
fclose(f);
}
int main(int argc, char *argv[]) {
char word[64];
int dist;
if (argc < 1)//判断命令行参数个数
return 1;
readdict(argv[1]);//读取单词文件ps_ec.txt
while (scanf("%s%d", word, &dist) != EOF) {//命令行输入单词,以及编辑距离的范围
find(word, dist);//查找模块,参数为单词,编辑距离范围,例:word 3,表示匹配和word编辑距离为0-3的单词。
}
}
3、相关文件:Github