关于PHP的相似度计算函数:levenshtein的使用介绍


Posted in PHP onApril 15, 2013

使用说明
先看手册上 levenshtein() 函数的说明:

levenshtein() 函数返回两个字符串之间的 Levenshtein 距离。

Levenshtein 距离,又称编辑距离,指的是两个字符串之间,由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。

例如把 kitten 转换为 sitting:

sitten (k→s)
sittin (e→i)
sitting (→g)levenshtein() 函数给每个操作(替换、插入和删除)相同的权重。不过,您可以通过设置可选的 insert、replace、delete 参数,来定义每个操作的代价。

语法:

levenshtein(string1,string2,insert,replace,delete)

参数 描述

string1 必需。要对比的第一个字符串。
string2 必需。要对比的第二个字符串。
insert 可选。插入一个字符的代价。默认是 1。
replace 可选。替换一个字符的代价。默认是 1。
delete 可选。删除一个字符的代价。默认是 1。
提示和注释

如果其中一个字符串超过 255 个字符,levenshtein() 函数返回 -1。
levenshtein() 函数对大小写不敏感。
levenshtein() 函数比 similar_text() 函数更快。不过,similar_text() 函数提供需要更少修改的更精确的结果。
例子

<?php
    echo levenshtein("Hello World","ello World");
    echo "<br />";
    echo levenshtein("Hello World","ello World",10,20,30);
    ?>

输出: 1 30

源码分析
levenshtein() 属于标准函数,在/ext/standard/目录下有专门针对此函数实现的文件:levenshtein.c。

levenshtein()会根据参数个数选择实现方式,针对参数为2和参数为5的情况,都会调用 reference_levdist() 函数计算距离。其不同在于对后三个参数,参数为2时,使用默认值1。

并且在实现源码中我们发现了一个在文档中没有说明的情况: levenshtein() 函数还可以传递三个参数,其最终会调用 custom_levdist() 函数。它将第三个参数作为自定义函数的实现,其调用示例如下:

echo levenshtein("Hello World","ello World", 'strsub');

执行会报Warning: The general Levenshtein support is not there yet。这是因为现在这个方法还没有实现,仅仅是放了一个坑在那。

reference_levdist() 函数的实现算法是一个经典的DP问题。

给定两个字符串x和y,求最少的修改次数将x变成y。修改的规则只能是如下三种之一:删除、插入、改变。
用a[i][j]表示把x的前i个字符变成y的前j个字符所需的最少操作次数,则状态转移方程为:

当x[i]==y[j]时:a[i][j]  = min(a[i-1][j-1], a[i-1][j]+1, a[i][j-1]+1);
当x[i]!=y[j]时:a[i][j] =  min(a[i-1][j-1], a[i-1][j], a[i][j-1])+1;

在用状态转移方程前,我们需要初始化(n+1)(m+1)的矩阵d,并让第一行和列的值从0开始增长。 扫描两字符串(nm级的),对比字符,使用状态转移方程,最终$a[$l1][$l2]为其结果。

简单实现过程如下:

<?PHP
    $s1 = "abcdd";
    $l1 = strlen($s1);
    $s2 = "aabbd";
    $l2 = strlen($s2); 
    for ($i = 0; $i < $l1; $i++) {
        $a[0][$i + 1] = $i + 1;
    }
    for ($i = 0; $i < $l2; $i++) {
        $a[$i + 1][0] = $i + 1;
    }
    for ($i = 0; $i < $l2; $i++) {
        for ($j = 0; $j < $l1; $j++) {
            if ($s2[$i] == $s1[$j]) {
                $a[$i + 1][$j + 1] = min($a[$i][$j], $a[$i][$j + 1] + 1, $a[$i + 1][$j] + 1);
            }else{
                $a[$i + 1][$j + 1] = min($a[$i][$j], $a[$i][$j + 1], $a[$i + 1][$j]) + 1;
            }
        }
    }
    echo $a[$l1][$l2];
    echo "n";
    echo levenshtein($s1, $s2);

在PHP的实现中,实现者在注释中很清楚的标明:此函数仅优化了内存使用,而没有考虑速度,从其实现算法看,时间复杂度为O(m×n)。其优化点在于将上面的状态转移方程中的二维数组变成了两个一组数组。简单实现如下:
<?PHP
    $s1 = "abcjfdkslfdd";
    $l1 = strlen($s1);
    $s2 = "aab84093840932bd";
    $l2 = strlen($s2);    $dis = 0;
    for ($i = 0; $i <= $l2; $i++){
        $p1[$i] = $i;
    }
    for ($i = 0; $i < $l1; $i++){
        $p2[0] = $p1[0] + 1;
        for ($j = 0; $j < $l2; $j++){
            if ($s1[$i] == $s2[$j]){
                $dis = min($p1[$j], $p1[$j + 1] + 1, $p2[$j] + 1);
            }else{
                $dis = min($p1[$j] + 1, $p1[$j + 1] + 1, $p2[$j] + 1);  // 注意这里最后一个参数为$p2  
            }
            $p2[$j + 1] = $dis;
        }
        $tmp = $p1;
        $p1 = $p2;
        $p2 = $tmp;  
    }
    echo "n";
    echo $p1[$l2];
    echo "n";
    echo levenshtein($s1, $s2);

如上为PHP内核开发者对前面经典DP的优化,其优化点在于不停的复用两个一维数组,一个记录上次的结果,一个记录这一次的结果。如果按照PHP的参数,分别给三个操作赋值不同的值,在上面的算法中将对应的1变成操作对应的值就可以了。 min函数的第一个参数对应的是修改,第二个参数对应的是删除源码天空,第三个参数对应的是添加。

Levenshtein distance说明
Levenshtein distance最先是由俄国科学家Vladimir Levenshtein在1965年发明,用他的名字命名。不会拼读,可以叫它edit distance(编辑距离)。Levenshtein distance可以用来:
Spell checking(拼写检查)
Speech recognition(语句识别)
DNA analysis(DNA分析)
Plagiarism detection(抄袭检测) LD用mn的矩阵存储距离值。

PHP 相关文章推荐
用PHP和ACCESS写聊天室(一)
Oct 09 PHP
PHP 变量定义和变量替换的方法
Jul 30 PHP
PHP 分页类(模仿google)-面试题目解答
Sep 13 PHP
在windows平台上构建自己的PHP实现方法(仅适用于php5.2)
Jul 05 PHP
php漏洞之跨网站请求伪造与防止伪造方法
Aug 15 PHP
php生成过去100年下拉列表的方法
Jul 20 PHP
php设置页面超时时间解决方法
Sep 22 PHP
Smarty分页实现方法完整实例
May 11 PHP
php自动载入类用法实例分析
Jun 24 PHP
浅谈PHP中静态方法和非静态方法的相互调用
Oct 04 PHP
PHP 进度条函数的简单实例
Sep 19 PHP
PHP二维数组分页2种实现方法解析
Jul 09 PHP
关于PHP递归算法和应用方法介绍
Apr 15 #PHP
PHP 读取Postgresql中的数组
Apr 14 #PHP
php简单开启gzip压缩方法(zlib.output_compression)
Apr 13 #PHP
做了CDN获取用户真实IP的函数代码(PHP与Asp设置方式)
Apr 13 #PHP
php检测图片木马多进制编程实践
Apr 11 #PHP
谈谈关于php的优点与缺点
Apr 11 #PHP
如何用PHP实现插入排序?
Apr 10 #PHP
You might like
PHP学习资料汇总与网址
2007/03/16 PHP
Centos 6.5系统下编译安装PHP 7.0.13的方法
2016/12/19 PHP
详解ThinkPHP3.2.3验证码显示、刷新、校验
2016/12/29 PHP
PHP测试框架PHPUnit组织测试操作示例
2018/05/28 PHP
优秀js开源框架-jQuery使用手册(1)
2007/03/10 Javascript
document.getElementById方法在Firefox与IE中的区别
2010/05/18 Javascript
ExtJs grid行 右键菜单的两种方法
2010/06/19 Javascript
js Dialog 去掉右上角的X关闭功能
2014/04/23 Javascript
js操作table元素实现表格行列新增、删除技巧总结
2015/11/18 Javascript
详解react-native-fs插件的使用以及遇到的坑
2017/09/12 Javascript
在 React、Vue项目中使用SVG的方法
2018/02/09 Javascript
Node.js net模块功能及事件监听用法分析
2019/01/05 Javascript
vue 实现cli3.0中使用proxy进行代理转发
2019/10/30 Javascript
js实现前端界面导航栏下拉列表
2020/08/27 Javascript
vue使用vue-quill-editor富文本编辑器且将图片上传到服务器的功能
2021/01/13 Vue.js
python中global用法实例分析
2015/04/30 Python
pymssql数据库操作MSSQL2005实例分析
2015/05/25 Python
Python中表示字符串的三种方法
2017/09/06 Python
python 随机数使用方法,推导以及字符串,双色球小程序实例
2017/09/12 Python
Python语言描述最大连续子序列和
2017/12/05 Python
K-近邻算法的python实现代码分享
2017/12/09 Python
python3实现二叉树的遍历与递归算法解析(小结)
2019/07/03 Python
详解python路径拼接os.path.join()函数的用法
2019/10/09 Python
VSCode中自动为Python文件添加头部注释
2019/11/14 Python
python使用paramiko实现ssh的功能详解
2020/03/06 Python
python图片剪裁代码(图片按四个点坐标剪裁)
2020/03/10 Python
python使用pyecharts库画地图数据可视化的实现
2020/03/25 Python
python Zmail模块简介与使用示例
2020/12/19 Python
html5本地存储 localStorage操作使用详解
2016/09/20 HTML / CSS
初中生评语大全
2014/04/24 职场文书
消防标语大全
2014/06/07 职场文书
领导干部个人对照检查材料(群众路线)
2014/09/26 职场文书
2014年财务部工作总结
2014/11/11 职场文书
《工作是最好的修行》读后感3篇
2019/12/13 职场文书
CSS3实现列表无限滚动/轮播效果
2021/06/23 HTML / CSS
Win11运行cmd提示“请求的操作需要提升”的两种解决方法
2022/07/07 数码科技