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

复杂度

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

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

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

所以:

时间复杂度是 O(2n*logn)和O(2n*logn+2n)之间,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

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

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

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

[
[str1位置i、长度len、str2位置j],
[str1位置i、长度len、str2位置j]
]

//查找
function find(str,hasSortArr,callback) {
    let l=0,r=hasSortArr.length;
    let index=-1;
    if(hasSortArr.length>0){
        const ri=callback(str,hasSortArr[r-1]);
        if(ri===1){
            return [r,-1]
        }else if(ri===0){
            return [r-1,r-1]
        }else{
            r=r-1;
        }
        const li=callback(str,hasSortArr[0]);
        if(li===-1){
            return [0,-1]
        }else if(li===0){
            return [0,0]
        }else{
            l=l+1;
        }
        while(r-l>0){
            const m=(l+r)>>1;
            //比较下坐标大小
            const order=callback(str,hasSortArr[m])
            if(order===1){
                l=Math.max(l+1,m)
            }else if(order===-1){
                r=Math.min(r-1,m)
            }else{
                l=r=m;
                index=m;
            }
        }
    }
    return [(l+r)>>1,index]
}
//SA[i]表示排名为i的后缀下标、rk[i]表示起始位置的下标为i的后缀的排名
function getSa(str) {
    const sLen=str.length;//总共排名长度
    //排名函数
    const compare=function (n1,n2) {
        let dis=0;
        let len=0;
        while (dis===0){
            //超过字符,返回小于
            if(n1+len===sLen){
                dis=-1
            }else if(str[n1+len]>str[n2+len]){
                dis=1;
            }else if(str[n1+len]len]){
                dis=-1;
            }else{
                len++;
            }
        }
        return dis;
    };
    //后缀数组
    const sa=[];
    for(let i=0;i){
        const [n,index]=find(i,sa,compare)
        sa.splice(n,0,i)
    }

    return sa
}


//用后缀数组,求两个字符的公共子串,也就是两个文件的公共部分
function getHotCommon(str1,str2) {
    const sa1=getSa(str1);//后缀数组,排序
    const sa2=getSa(str2);//后缀数组,排序
    //没有相同部分
    if(str1.charCodeAt(sa1[0])>str2.charCodeAt(sa2[sa2.length-1])||str1.charCodeAt(sa1[sa1.length-1])])){
        return [];
    }
    const common=[]
    let n=0,m=0;
    while (nsa2.length){
        //寻找相同部分
        let len=0;
        for(let i=m;i){
            //大于就不相同了,不用在计算了
            if(str1.charCodeAt(sa1[n])<str2.charCodeAt(sa2[i])){
                m=i;
                break;
            }else if(str1.charCodeAt(sa1[n])===str2.charCodeAt(sa2[i])){
                m=i;
                len=1;
                break;
            }
        }
        //得出相同部分的长度
        if(len===1){
            while(str2[sa2[m]+len]===str1[sa1[n]+len]){
                len++;
            }
            common.push([sa1[n],len,sa2[m]]);
            //如果要重复利用,就注释这一行
            m++
        }
        n++
    }
    //排序
    common.sort(function (arr1,arr2) {
        return arr1[0]-arr2[0]
    })
    //去掉相交的部分
    const narr=[]
    for(let i=0;i){
        const last=narr[narr.length-1]
        if(narr.length===0||last[0]+last[1]<=common[i][0]){
            narr.push(common[i])
        }
    }
    return narr;
}
//demo
const str1='1热点671热点112';
const str2='123热点11212热点3';
//获取公共子串的位置和长度,arr[字符1的位置、长度、字符2的位置]
const narr=getHotCommon(str1,str2);
//字符1的公共子串的位置和长度
const str1Arr=narr.map(function (arr) {
    return str1.substr(arr[0],arr[1])
})
//字符2的公共子串的位置和长度
const str2Arr=narr.map(function (arr) {
    return str2.substr(arr[2],arr[1])
})
//相同输出
console.log(str1Arr)//=>[ '1', '热点', '1', '热点112' ]
console.log(str2Arr)//=>[ '1', '热点', '1', '热点112' ]

 

你可能感兴趣的:(用后缀数组,求两个字符的公共子串,或者两个文件的公共子串)