用后缀数组,求两个字符的公共子串,或者两个文件的公共子串

复杂度

1、求后缀数组,用的二分查找法和基类比较,所以时间复杂度是 n*lg2n,只保存后缀的位置,空间复杂度是n

2、比较查找,没有公共部分的情况,str1排名的最小值>str2排名的最大值 或者 str1排名的最大值>str2排名的最小值,就认为没有公共部分,复杂度为2

3、比较查找,有公共部分的情况,按照n、m从小到大的顺序查找的,复杂度为n+m

所以:

时间复杂度是 O(2n*logn)之间,logn的底数是2

空间复杂度是O(2n)

什么是后缀数组

1、给字符的后缀排好序的数组

2、保持的值是字符的后缀

3、排序规则是基类排序,从小到大

后缀

后缀就是从字符串的某个位置i到字符串末尾的子串,我们定义以s的第i个字符为第一个元素的后缀为suff(i)

 

例如

字符 s='123456',它的后缀数组表示为[ 0, 1, 2, 3, 4, 5 ],也即是 [123456,23456,3456,456,56,6]

字符 s='热点112',它的后缀数组表示为[ 2, 3, 4, 1, 0 ],也即是[112,12,2,点112,热点112]

 

算法思路

1、对字符str1进行排序,求出后缀数组sa1,对字符str2进行排序,求出后缀数组sa2

2、设 n表示字符str1的排名,m表示字符str2的排名,初始设 n=0,m=0

sa1[n]表示排名为n的字符str1位置

sa2[m]表示排名为m的字符str2位置

逻辑部分

初次查询从n=m=0开始,从小到大比较位置sa1[i]和sa2[j],如果字符相同,则求出相同部分的长度len,更新查询起始位置 n=i,m=j

下次查询,从n、m位置从小到大继续比较位置sa1[i]和sa2[j],如果字符相同,则求出相同部分的长度len,更新查询起始位置 n=i,m=j

如果 n<sa1.length&&m<sa2.length,重复上述过程

3、此时按照排名顺序求出了所有的公共部分,还需要去重

因为后一个字符长度,必然比前一个字符长度小,所以判断当前和上一个有没有重叠,重叠的话就去掉当前,保留了最长的公共子串

4、返回的数据格式如下,按照str1位置顺序排列的

[
[str1位置i、长度len、str2位置j],
[str1位置i、长度len、str2位置j]
]
//压缩增量包
function MinChunk(chunkArr,s1) {
var minChunk=[]
for(let i=0;i<chunkArr.length;i++){
let arr=chunkArr[i];
if(arr[0]==='e'&&arr[2]<String(arr[1]).length+String(arr[2]).length){
arr=['a',s1.substr(arr[1],arr[2])]
}
var lastArr=minChunk[minChunk.length-1];
if(arr[0]==='a'&&lastArr&&lastArr[0]==='a'){
lastArr[1]=lastArr[1]+arr[1];
}else{
minChunk.push(arr)
}
}
const narr=[]
for(let i=0;i<minChunk.length;i++){
let arr=minChunk[i];
if(arr[0]==='a'){
narr.push(arr[1])
}else if(arr[0]==='e'){
narr.push([arr[1],arr[2]])
}
}
return narr;
}

//比较两字符的相等长度和大小
function compareLen(n1,n2,str1,str2) {
//求出相等部分
var len=0;
while (n1+len<=str1.length&&n2+len<=str2.length&&str1.charCodeAt(n1+len)===str2.charCodeAt(n2+len)){
len++;
}
var code1=str1.charCodeAt(n1+len);
var code2=str2.charCodeAt(n2+len);

if(Number.isNaN(code1)&&Number.isNaN(code2)){
return [len,0]
}else if(Number.isNaN(code1)){
return [len,-1]
}else if(Number.isNaN(code2)){
return [len,1]
}else{
//求出大小
var dis=0;
if(code1<code2){
dis=-1
}else if(code1>code2){
dis=1
}
if(str1.substr(n1,len)!==str2.substr(n2,len)){
console.log(dis,len,str1.substr(n1,len),'===',str2.substr(n2,len),n1,n2,str1.length,str2.length)
}

return [len,dis];
}
}

//查找字符在数组的最大相等长度和大小
function findLen(str,hasSortArr,callback) {
var l=0,r=hasSortArr.length;
var lock=-1;
var len1=0,len2=0;
var len=0,dis=0;

if(hasSortArr.length>0){
[len1,dis]=callback(str,hasSortArr[0]);
if(dis<1){
return [0,len1,dis]
}
[len2,dis]=callback(str,hasSortArr[r]);
if(dis>-1){
return [r,len2,dis]
}
while(lock===-1){
var m=(l+r+1)>>1;
//比较下坐标大小
[len,dis]=callback(str,hasSortArr[m])
if(dis===1){
if(m+1===r){
if(len<len2){
lock=r;
len=len2;
dis=-1
}else{
lock=m;
}
}else{
len1=len;
l=m
}
}else if(dis===-1){
if(l+1===m){
if(len<len1){
lock=l;
len=len1;
dis=1
}else{
lock=m;
}
}else{
len2=len;
r=m
}
}else{
lock=m;
}
}
return [lock,len,dis]
}
return [lock,0,1]
}

//SA[i]表示排名为i的后缀下标、rk[i]表示起始位置的下标为i的后缀的排名
function getSa(str) {
var sLen=str.length;//总共排名长度
//后缀数组
var sa=[];
for(var i=0;i<sLen;i++){
var [n,len,dis]=findLen(i,sa,function (n1,n2) {
return compareLen(n1,n2,str,str)
})
if(dis===1){
sa.splice(n+1,0,i)
}else{
sa.splice(n,0,i)
}

}
return sa
}
//用后缀数组,求两个字符的公共子串,也就是两个文件的公共部分
function makeChunk(s1,s2){
var sa1=getSa(s1);//后缀数组,排序
const chunkArr=[]
let i=0;
while (i<s2.length){
var [n,len,dis]=findLen(i,sa1,function (n2,n1) {
return compareLen(n2,n1,s2,s1)
})
if(len>0){
chunkArr.push(['e',sa1[n],len])
i=i+len;
}else{
chunkArr.push(['a',s2[i]])
i++
}
}
return chunkArr;
}
//执行增量包
function execChunk(s1,chunk){
var ns='';
for(var i=0;i<chunk.length;i++){
var arr=chunk[i];
if(Object.prototype.toString.call(arr)==='[object Array]'){
ns=ns+s1.substr(arr[0],arr[1])
}else{
ns=ns+arr
}
}
return ns;
}
module.exports=function (s1,s2) {
const chunk=makeChunk(s1,s2);

const chunkArr=MinChunk(chunk,s1)
const s3=execChunk(s1,chunkArr)
if(s3!==s2){
throw '增量包打包错误'
}
return chunkArr
};

  

  

posted @ 2020-07-05 19:03  无工时代  阅读(258)  评论(0编辑  收藏  举报