20210424 Levenshtein距离解析

0.前言

距离上一次分析算法已经过去好几年了- -以我的算法水平其实是很讨厌分析算法的,一个是想写明白耗时很长,一个是说人话很啰嗦。但是写代码或公式又大部分人很难理解。今天要分析的算法是字符串相似度,Levenshtein距离

形式化描述:给定字符串strA,strB,求解dist(strA,strB)的值,dist为Levenshtein距离

说人话:两个字符串,编辑几次能变得完全一样,编辑允许的操作(插入,删除,替换字符)

0.定理/公理,人人都能看懂的情况

字符串基本操作:

连接 下文用+代表

删除字符 下文用 删除strA[0]表示

增加字符 下文用 str[x]=’a’表示 x从0开始

替换字符 下文用 str[x]=’a’表示 x从0开始

(这里一切的基础,类似于公理无需证明、显而易见、或可以轻易穷举证明的规律或法则)

0.1 (多嘴说两句,空串””可以理解为代数系统幺元的概念)

初始状态strA=”” strB=”abcdefgxxxxxx”

显然dist(strA,strB)=len(strB) len代表strB的长度

0.2 交换律

显然交换律成立

strA+strB == strB + strA

dist(strA,strB) == dist(strB,strA)

0.3 结合律(瞎起的名,可能不够严谨)

dist(strA,strB) == dist(strA+strC,strB+strC) == dist(strC+strA, strC+strB)

初始状态strA=”xxxxa…” strB=”xxxxb…”

显然,如果前缀x的部分相等,不论x多长,都可以等价于直接去掉,变成strA=”a” strB=”b”

初始状态strA=”axxxx” strB=”bxxxx”

显然,如果后缀x的部分相等,不论x多长,都可以等价于直接去掉,变成strA=”a” strB=”b”

特殊情况:初始状态strA=”a” strB=”a”

显然,当strA和strB完全相等时:dist(strA,strB)==dist(“”,””)==dist(“”,len(“”))==0

1.下面从最简单的情况开始分析

从最简单的情况开始分析,只有一个字母的时候,有这么几种情况

1.1.case1

初始状态strA=”a” strB=””,显然有这么几种情况

1.1步=删除1次

1.1.删除strA[0]->””,””

2.1步=插入1次

2.1.插入strB[0]=’a’,->”a”,”a”

1.2.case2

初始状态strA=”” strB=”b”,显然有这么几种情况

1.1步=删除1次

1.1.删除strB[0]->””,””

2.1步=插入1次

2.1.插入strA[0]=’b’,->”b”,”b”

2.稍微复杂一点,两个不等字符跟别构成的字符串

初始状态strA=”a” strB=”b”

以下表示格式为strA,strB

1.

“a”,”b”

1.1.替换strB[0]=’a’->”a”,”a”

共1步=替换1次 => dist(“”, “”) + 1

如何理解dist(“”,””)是这个地方的关键,在替换的策略下,总次数实际是是替换位置前面所有的部分的编辑次数,加上替换的这1次即是整个字符串的总次数,这个case下位置0前面没东西了,所以是空串””

2.

“a”,”b”

2.1.替换strA[0]=’b’->”b”,”b”

共1步=替换1次 => dist(“”, “”) + 1

同上:如何理解dist(“”,””)是这个地方的关键,在替换的策略下,总次数实际是是替换位置前面所有的部分的编辑次数(不包括替换的位置自己),加上替换的这1次即是整个字符串的总编辑次数,这个case下位置0前面没东西了,所以是空串””

3

“a”,”b”

3.1.插入strA[0]=’b’->”ba”,”b”

3.2.插入strB[1]=’a’->”ba”,”ba”

共2步=插入1次+ dist(“ba”,”b”)== 插入1次 + dist(“a”,””)  =>  dist(“a”,””) + 1

如何理解 dist(“a”,””)是这个地方的关键:

实际上原本是dist(“ba”,”b”),因为前缀相同,所以变成了dist(“a”,””)

在这种插入策略下,插入位置之前的部分总是一样的(包括插入位置自己),所以总次数实际是是插入位置后面所有的部分的编辑次数(不包括插入的位置自己),加上插入的这1次即是整个字符串的总次数

这个case下strA[0]位置后面是”a”,strB后面是””

4

“a”,”b”

4.1.插入strB[0]=’a’->”a”,”ab”

4.2.插入strA[1]=’b’->”ab”,”ab”

共2步=插入1次+ dist(“a”,”ab”)== 插入1次 + dist(“”,”b”)  =>  dist(“”,”b”) + 1

如何理解 dist(“”,”b”) 是这个地方的关键:

实际上原本是dist(“a”,”ab”),因为前缀相同,所以变成了dist(“”,”b”)

在这种插入策略下,插入位置之前的部分总是一样的(包括插入位置自己),所以总次数实际是是插入位置后面所有的部分的编辑次数(不包括插入的位置自己),加上插入的这1次即是整个字符串的总次数

这个case下strA[0]位置后面是””,strB后面是”b”

5

“a”,”b”

共2步 显然等价于1

5.1.删除strB[0]->”a”,””

5.2.插入strB[0]=’a’->”a”,”a”

6

“a”,”b”

共2步 显然等价于2

6.1.删除strA[0]->””,”b”

6.2.插入strA[0]=’b’->”b”,”b”

7

“a”,”b”

7.1.插入strB[1]=’a’->”a”,”ba”

7.2.插入strA[0]=’b’->”ba”,”ba”

共2步=插入1次+ dist(“a”,”ba”)== 插入1次 + dist(“”,”b”)  =>  dist(“”,”b”) + 1  同4

如何理解 dist(“”,”b”)是这个地方的关键:

实际上原本是dist(“a”,”ba”),因为后缀相同,所以变成了dist(“”,”b”)

在这种插入策略下,插入位置之后的部分总是一样的(包括插入位置自己),所以总次数实际是是插入位置前面所有的部分的编辑次数(不包括插入的位置自己),加上插入的这1次即是整个字符串的总次数

这个case下strA[0]位置前面是””,strB[1]前面是”b”

8.

“a”,”b”

8.1.插入strA[1]=’b’->”ab”,”b”

8.2.插入strB[0]=’a’->”ab”,”ab”

共2步=插入1次+ dist(“ab”,”b”)== 插入1次 + dist(“a”,””)  =>  dist(“a”,””) + 1  同3

如何理解 dist(“a”,””) 是这个地方的关键:

实际上原本是dist(“ab”,”b”),因为后缀相同,所以变成了dist(“a”,””)

在这种插入策略下,插入位置之后的部分总是一样的(包括插入位置自己),所以总次数实际是是插入位置前面所有的部分的编辑次数(不包括插入的位置自己),加上插入的这1次即是整个字符串的总次数

这个case下strA[1]位置前面是”a”,strB[0]前面是””

 

综上1278,不难发现,最小的次数是1278中的最小值(选1234就是倒着了,一样的,两个方向都可以的)

dist(“a”,”b”) == min(dist(“”, “”) + 1, dist(“a”, “”) + 1, dist(“”, “b”) + 1)

或写做min(dist(“”, “”) , dist(“a”, “”) , dist(“”, “b”) ) + 1

认真思考下,替换策略计算的结果为替换位置之前的部分,插入策略因位置不同但相互对称,所以刚好是去掉插入位置的字符前面的部分

所以dist(“a”,”b”) == 1

3.再复杂一些

根据2的结论,能否扩充到长度为n的情况呢?

dist(“abcdef”,”agchef”)

根据定理3 ==> dist(“abcd”, “agch”)

根据定理3 ==> dist(“bcd”, “gch”)

根据规律 ==> min(    dist(“bc”, “gc”),    dist(“bcd”, “gc”),    dist(“bc”, “gch”) ) + 1

根据规律 ==>min(

dist(“b”, “g”),

min(  dist(“bc”, “g”),     dist(“bcd”, “g”),      dist(“bc”, “gc”)     ) + 1 ,

min( dist(“b”, “gc”),     dist(“bc”, “gc”),       dist(“b”, “gch”)   )  + 1

) + 1

根据规律 ==>min(

dist(“b”, “g”),

min(

min(dist(“b”, “”),dist(“bc”, “”),dist(“b”, “g”))+1,

min(dist(“bc”,””), dist(“bcd”,””), dist(“bc”,”g”))+ 1

dist(“b”,”g”)

) + 1,

min(dist(“b”, “gc”),  dist(“bc”, “gc”),  dist(“b”, “gch”)) + 1

) + 1

根据规律 ==>min(

dist(“b”, “g”),

min(

min(dist(“b”, “”),dist(“bc”, “”),dist(“b”, “g”))+1,

min(dist(“bc”,””), dist(“bcd”,””), min(dist(“b”,””), dist(“bc”,”g”), dist(“b”,”g”)) + 1) +  1

dist(“b”,”g”)

) + 1,

min(dist(“b”, “gc”),  dist(“bc”, “gc”),  min(dist(“”, “gc”), dist(“b”, “gc”), dist(“”, “gch”)) + 1) + 1

) + 1

根据规律 ==>min(

1,

min(

min(1,2,1)+1,

min(2,3, min(1,2,1) + 1) +  1

1

) + 1,

min(2, 1,  min(2, 2, 3) + 1) + 1

) + 1

根据规律 ==>2

4.程序实现

次数统计后如下,显然部分内容被重复计算了

dist(“”,”g”)
dist(“”,”gc”) * 2
dist(“”,”gch”)
dist(“b”,””) * 3
dist(“b”,”g”) * 5
dist(“b”,”gc”)
dist(“bc”,””) * 3
dist(“bc”,”gc”)
dist(“bcd”,””)

直接加缓存,或按状态转移方程填表即可

参考资料

https://zhuanlan.zhihu.com/p/91645988

2020javaweb开发环境搭建

1.前言

目前常用的IDE有eclipse、idea、vscode,本文暂时介绍前两个

tomcat、mysql、jdbc驱动无论哪个方案都需要

2.基础组件下载

2.1.tomcat9

不要用10,eclipse还不支持

https://tomcat.apache.org/download-90.cgi

2.2.mysql

https://dev.mysql.com/downloads/installer/

2.3.jdbc

https://dev.mysql.com/downloads/connector/j/

3.eclipse方案

目前最新eclipse运行本身需要java11以上,这里为了方便使用openjdk15

3.1.IDE下载

下载地址  https://www.eclipse.org/downloads/packages/

3.2.jre下载

如果你本地有,可以不用下载

下载地址 http://jdk.java.net/15/

3.3.配置

见视频

3.4.helloworld

见视频

4.idea方案

4.1.idea下载

下载地址 https://www.jetbrains.com/zh-cn/idea/download/download-thanks.html?platform=windows&code=IIC

4.2.tomcat插件下载

见视频

4.3.个人偏好配置(可选)

见视频

4.4.helloworld

见视频

 

2020C教程大纲

环境搭建
环境搭建说明

1.基础操作
https://blog.hylstudio.cn/archives/686
2.循环增强训练
https://blog.hylstudio.cn/archives/688
3.井字棋的实现
https://blog.hylstudio.cn/archives/690
4.函数、指针、结构体重构井字棋
TODO

大纲

介绍程序框架
学习printf
打印固定字符/字符串
引入字符变量
打印变量
引入逻辑表达式、判断
根据条件打印字符/字符串
引入scanf
学习字符的输入
根据输入内容打印字符/字符串
引入for循环
学习打印固定次数/不固定次数
打印各种三角形
引入system(“cls”)
制作简易字符动画
井字棋棋盘/棋子打印
编写游戏操作逻辑
引入循环控制
制作游戏流程控制
引入数组
支持游戏状态内存判断,胜负判定
引入二维数组
简易数据结构struct
引入内存分配
面向对象的本质
劣质的人工智障程序
相关代码和题目

CAS对接的坑,一个字母引发的惨案

有两个类定义如下

管理后台生产的JSON配置是 @class: org.apereo.cas.ws.idp.services.WsFederationClaimsReleasePolicy

后台反序列化提示WsFederationClaimsReleasePolicy不是RegisteredServiceAttributeReleasePolicy的子类

MD 找了我一天,最后就改了一个字母,给官方发了pullrequest

js写的类名是Ws,后台JAVA写的是WS,已经给官方提了PR

https://github.com/apereo/cas-management/pull/160/files

20190923 随笔

我居然在B站学习系列:

https://www.bilibili.com/video/av35819757

今天看到了这个,看到一半讲到了flag才忽然想起自己立的flag已经一年了,http://blog.hylstudio.cn/archives/330

在这篇预告中,我去年6月7日说补充数据结构和HTTP服务器编写,到现在过了一年多了还没开始动笔,实在是惭愧hhhh。

另外今年暑假又教了几个新生学习C语言,2019版本的C语言教程也没来得及更新,用的依然是http://blog.hylstudio.cn/archives/174 这篇大一期末时候写的文章。发现了许多教学上的问题,其实当时这篇文章只是用来给大一结束的同学复习用的总结,拿来当作教程却不配合讲解对很多新人还是会有困难。特此今年还设计了一些题目由简至难的来让新生零基础学习C语言,并且有计划参考杨中科老师的思路,使用C语言制作web程序,让C语言脱离cmd下来运行,这样对新人的成就感应该会更强吧,这部分思路待会再细说。相关资源之后也会更新的(貌似又给自己立了个flag,还是优先收下之前的flag吧=-=

(PS:电脑刚重装,微软的输入法调校的还不是很好,但我又不像下qq拼音,因为垃圾输入法对高分屏支持太差了。更不想下搜狗,所以还是坚持使用微软拼音,如果有错字可以告诉我下,反正我也不一定改。。。hhhh)

说起来C语言的课程设计,其实从大二开始就在做了,每年都会又机会带新生从头开始学习,也不断的提高自己设计课程的能力。往往自身的水平提升之后,会忘记零基础人学习的痛苦,觉得一些内容理所当然就应该明白,但是事实多次告诉我并不是这样的。每次教零基础的人学习都有这样的体会,但其实我自己入门环境带来的痛苦程度远比现在要多的多,所以更能体会新人的感受。所以才会选择与传统课程不一样的路线来设计和教学。

传统的教学往往上来就讲C语言的数据类型,乱七八糟各种数据类型都讲一遍,实际的情况是听完之后一脸懵B。我是谁?我在哪?我要干什么?讲了这么多依然不知道该用什么,该怎么用。

这部分其实是借鉴了游戏设计的做法,人力资源机上来第一关只有基础的几个操作,你没得可选。大部分游戏都有的教学关卡的思路也是逐步升级任务难度,而开始的选择是极少的,起到了教学的作用。很少又游戏难度是上来就是hard(部分硬核游戏单说哈)。因此我设计的第一部分其实只有输出,类型只会又字符。其实更简单的是数字,但数字太过于枯燥了,还是字符的表达能力更强一些。那么整体的思路就是这样

2020c教程大纲 https://blog.hylstudio.cn/archives/746

后面的部分还没想的特别细,所以可能会有疏漏,不过目前还没人学到这里,到时候有啥问题再调整。

关于web的开发,其实最早是考虑使用socket的,和java的一样,但是百度百科那段socket的程序居然没了。并且C语言和平台相关,我找了好几种方式代码都太复杂了,不是非常友好,所以考虑使用和杨中科老师一样的思路,外挂一个web服务器,用cgi的方式来调用。这样C的标准输出会直接给浏览器,可以用C做一些有意思的事情了,并且因为类型依然是字符/字符串,所以不会有更大的负担

2019C语言题目3

做点有意思的

代码如下,每变动一次重新粘贴一版,方便你们观察变化

第二题

第三题

第四题

第五题