关于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 相关文章推荐
php5.2.0内存管理改进
Jan 22 PHP
php目录管理函数小结
Sep 10 PHP
PHP安全性漫谈
Jun 28 PHP
关于PHP自动判断字符集并转码的详解
Jun 26 PHP
php中simplexml_load_file函数用法实例
Nov 12 PHP
php计算整个目录大小的方法
Jun 19 PHP
WordPress中编写自定义存储字段的相关PHP函数解析
Dec 25 PHP
php mysql操作mysql_connect连接数据库实例详解
Dec 26 PHP
php+webSoket实现聊天室示例代码(附源码)
Feb 17 PHP
php微信开发之图片回复功能
Jun 14 PHP
Laravel-admin之修改操作日志的方法
Sep 30 PHP
Laravel框架Blade模板简介及模板继承用法分析
Dec 03 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 模拟get_headers函数的代码示例
2013/04/27 PHP
linux mint下安装phpstorm2020包括JDK部分的教程详解
2020/09/17 PHP
PHP fopen中文文件名乱码问题解决方案
2020/10/28 PHP
33个优秀的 jQuery 图片展示插件分享
2012/03/14 Javascript
深入理解javascript变量声明
2014/11/20 Javascript
Node.js 学习笔记之简介、安装及配置
2015/03/03 Javascript
Javascript 计算字符串在localStorage中所占字节数
2015/10/21 Javascript
nodeJs内存泄漏问题详解
2016/09/05 NodeJs
Javascript实现从小到大的数组转换成二叉搜索树
2017/06/13 Javascript
Vue 中如何正确引入第三方模块的方法步骤
2019/05/05 Javascript
vue 集成 vis-network 实现网络拓扑图的方法
2019/08/07 Javascript
vscode 配置vue+vetur+eslint+prettier自动格式化功能
2020/03/23 Javascript
[02:11]2014DOTA2 TI专访VG战队Fenrir:队伍气氛良好
2014/07/11 DOTA
[53:29]完美世界DOTA2联赛循环赛 DM vs Matador BO2第二场 11.04
2020/11/05 DOTA
PyChar学习教程之自定义文件与代码模板详解
2017/07/17 Python
解决Pycharm无法import自己安装的第三方module问题
2018/05/18 Python
python通过配置文件共享全局变量的实例
2019/01/11 Python
谈谈Python中的while循环语句
2019/03/10 Python
Pytorch中的VGG实现修改最后一层FC
2020/01/15 Python
Tensorflow训练MNIST手写数字识别模型
2020/02/13 Python
一家专门经营包包的英国网站:MyBag
2019/09/08 全球购物
List、Map、Set三个接口,存取元素时,各有什么特点?
2015/09/27 面试题
师范应届生教师求职信
2013/11/05 职场文书
化工专业应届生求职信
2013/11/08 职场文书
2014学校庆三八妇女节活动总结
2014/03/01 职场文书
如何写自我评价?自我评价写什么好?
2014/03/14 职场文书
财务负责人任命书
2014/06/06 职场文书
合作协议书格式
2014/08/19 职场文书
教师师德考核自我评价
2014/09/13 职场文书
2014年大学生村官工作总结
2014/11/19 职场文书
杜甫草堂导游词
2015/02/03 职场文书
天鹅湖观后感
2015/06/09 职场文书
2016公司中秋节寄语
2015/12/07 职场文书
街道办残联2016年助残日活动总结
2016/04/01 职场文书
总结Python连接CS2000的详细步骤
2021/06/23 Python
Spring Boot 实现 WebSocket
2022/04/30 Java/Android