关于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 相关文章推荐
常用表单验证类,有了这个,一般的验证就都齐了。
Dec 06 PHP
PHP4与PHP5的时间格式问题
Feb 17 PHP
为PHP初学者的8点有效建议
Nov 20 PHP
详解php的魔术方法__get()和__set()使用介绍
Sep 19 PHP
PHP删除HTMl标签的三种解决方法
Jun 30 PHP
php上传文件,创建递归目录的实例代码
Oct 18 PHP
php中使用base HTTP验证的方法
Apr 20 PHP
CodeIgniter配置之SESSION用法实例分析
Jan 19 PHP
Zend Framework教程之Application和Bootstrap用法详解
Mar 10 PHP
thinkphp中的多表关联查询的实例详解
Oct 12 PHP
PHP实现的字符串匹配算法示例【sunday算法】
Dec 19 PHP
laravel csrf排除路由,禁止,关闭指定路由的例子
Oct 21 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中检查PHP文件是否有语法错误的方法
2009/12/23 PHP
PHP显示今天、今月、上月、今年的起点/终点时间戳的代码
2011/05/25 PHP
php smarty 二级分类代码和模版循环例子
2011/06/16 PHP
php fsockopen伪造post与get方法的详解
2013/06/14 PHP
解析phpstorm + xdebug 远程断点调试
2013/06/20 PHP
PHP模拟post提交数据方法汇总
2016/02/16 PHP
设定php简写功能的方法
2019/11/28 PHP
js弹窗代码 可以指定弹出间隔
2010/07/03 Javascript
JavaScript的事件绑定(方便不支持js的时候)
2013/10/01 Javascript
JavaScript+CSS控制打印格式示例介绍
2014/01/07 Javascript
JavaScript中的索引数组、关联数组和静态数组、动态数组讲解
2014/11/08 Javascript
jQuery中的编程范式详解
2014/12/15 Javascript
使用jQuery Ajax 请求webservice来实现更简练的Ajax
2016/08/04 Javascript
JavaScript实现Fly Bird小游戏
2016/12/15 Javascript
Angular2 之 路由与导航详细介绍
2017/05/26 Javascript
Angular4的输入属性与输出属性实例详解
2017/11/29 Javascript
去掉vue 中的代码规范检测两种方法(Eslint验证)
2018/03/21 Javascript
Angular2 自定义表单验证器的实现方法
2018/12/14 Javascript
node.js实现微信开发之获取用户授权
2019/03/18 Javascript
vue.js中导出Excel表格的案例分析
2019/06/11 Javascript
vue 数据遍历筛选 过滤 排序的应用操作
2020/11/17 Javascript
[01:45]DOTA2众星出演!DSPL刀塔次级职业联赛宣传片
2014/11/21 DOTA
在Django的模型中执行原始SQL查询的方法
2015/07/21 Python
python reduce 函数使用详解
2017/12/05 Python
查看端口并杀进程python脚本代码
2019/12/17 Python
Python模块/包/库安装的六种方法及区别
2020/02/24 Python
Python selenium爬虫实现定时任务过程解析
2020/06/08 Python
分享CSS3制作卡片式图片的方法
2016/07/08 HTML / CSS
俄罗斯外国汽车和国产汽车配件网上商店:Движком
2020/04/19 全球购物
璀璨的珍珠、密钉和个性化珠宝:Lily & Roo
2021/01/21 全球购物
大学生社团活动总结
2014/04/26 职场文书
2014银行领导班子群众路线对照检查材料思想汇报
2014/09/17 职场文书
年度考核个人总结
2015/03/06 职场文书
2015安全保卫工作总结
2015/04/25 职场文书
企业廉洁教育心得体会
2016/01/20 职场文书
工作汇报材料难写?方法都在这里了!
2019/07/01 职场文书