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

复杂度

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<sa1.length&&m<sa2.length,重复上述过程

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

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

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

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

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

    return sa
}


//用后缀数组,求两个字符的公共子串,也就是两个文件的公共部分
function getHotCommon(str1,str2) {
    var sa1=getSa(str1);//后缀数组,排序
    var sa2=getSa(str2);//后缀数组,排序

    //再次排序,获取合并后的后缀数组
    var arr=[]
    var n1=0,n2=0;
    while (n1<sa1.length&&n2<sa2.length) {
        var d=compare(sa1[n1],sa2[n2],str1,str2);
        if(d===1){
            arr.push([1,sa2[n2]])
            n2++;
        }else if(d===-1){
            arr.push([0,sa1[n1]])
            n1++
        }else{
            arr.push([0,sa1[n1]])
            arr.push([1,sa2[n2]])
            n1++;
            n2++;
        }
        if(n1===sa1.length){
            arr.push([1,sa2[n2]])
        }else if(n2===sa2.length){
            arr.push([0,sa1[n1]])
        }
    }
    //求height数组,获取公共子序列
    var common=[]
    for(var i=0;i<arr.length-1;i++){
        var cur=arr[i]
        var next=arr[i+1]
        if(cur[0]!==next[0]){
            var n
            var m
            if(cur[0]===0){
                n=cur[1]
                m=next[1]
            }else{
                n=next[1]
                m=cur[1]
            }
            var len=0;
            while(n+len<str1.length&&m+len<str2.length&&str1[n+len]===str2[m+len]){
                len++;
            }
            if(len>0){
                common.push([n,m,len]);
            }
        }
    }
    //排序
    common.sort(function (arr1,arr2) {
        return arr1[0]-arr2[0]
    })
    //去掉相交的部分
    var narr1=[]
    for(var i=0;i<common.length;i++){
        if(narr1.length===0){
            narr1.push(common[i])
        }else{
            var last=narr1[narr1.length-1];
            var cur=common[i]
            if(last[0]+last[2]>cur[0]){
                if(cur[2]>last[2]){
                    last[2]=cur[0]-last[0]
                    if(last[2]>0){
                        narr1.push(cur)
                    }else{
                        last[0]=cur[0]
                        last[1]=cur[1]
                        last[2]=cur[2]
                    }
                }
            }else{
                narr1.push(cur)
            }
        }
    }
//排序
    narr1.sort(function (arr1,arr2) {
        return arr1[1]-arr2[1]
    })
    var narr=[]
    for(var i=0;i<narr1.length;i++){
        if(narr.length===0){
            narr.push(narr1[i])
        }else{
            var last=narr[narr.length-1];
            var cur=narr1[i]
            if(last[2]+last[1]>cur[1]){
                if(cur[2]>last[2]){
                    last[2]=cur[1]-last[1]
                    if(last[2]>0){
                        narr.push(cur)
                    }else{
                        last[0]=cur[0]
                        last[1]=cur[1]
                        last[2]=cur[2]
                    }
                }
            }else{
                narr.push(cur)
            }
        }
    }
    return narr;
}

//demo
const str1='211234567123';
const str2='4123456711';
//获取公共子串的位置和长度,arr[字符1的位置、长度、字符2的位置]
const narr=getHotCommon(str1,str2);
console.log(narr)
//字符1的公共子串的位置和长度
const str1Arr=narr.map(function (arr) {
    return str1.substr(arr[0],arr[2])
})
//字符2的公共子串的位置和长度
const str2Arr=narr.map(function (arr) {
    return str2.substr(arr[1],arr[2])
})
//相同输出
console.log(str1Arr)//=>[ '12345671', '1' ]
console.log(str2Arr)//=>[ '12345671', '1' ]

 getLongCommon.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>后缀数组求公共部分</title>
</head>
<script>
    //查找
    function find(str,hasSortArr,callback) {
        var l=0,r=hasSortArr.length;
        var index=-1;
        if(hasSortArr.length>0){
            var 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;
            }
            var 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){
                var m=(l+r)>>1;
                //比较下坐标大小
                var 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]
    }
    //比较函数
    var compare=function (n1,n2,str1,str2) {
        var dis=0;
        var len=0;
        while (dis===0&&n1+len<=str1.length&&n2+len<=str2.length){
            //超过字符,返回小于
            if(n1+len===str1.length){
                dis=-1
            }else if(n2+len===str2.length){
                dis=1
            }else if(str1.charCodeAt(n1+len)>str2.charCodeAt(n2+len)){
                dis=1;
            }else if(str1.charCodeAt(n1+len)<str2.charCodeAt(n2+len)){
                dis=-1;
            }else{
                len++;
            }
        }
        return dis;
    };
    //SA[i]表示排名为i的后缀下标、rk[i]表示起始位置的下标为i的后缀的排名
    function getSa(str) {
        var sLen=str.length;//总共排名长度
        //后缀数组
        var sa=[];
        for(var i=0;i<sLen;i++){
            var [n,index]=find(i,sa,function (n1,n2) {
                return compare(n1,n2,str,str)
            })
            sa.splice(n,0,i)
        }

        return sa
    }


    //用后缀数组,求两个字符的公共子串,也就是两个文件的公共部分
    function getHotCommon(str1,str2) {
        var sa1=getSa(str1);//后缀数组,排序
        var sa2=getSa(str2);//后缀数组,排序

        //再次排序,获取合并后的后缀数组
        var arr=[]
        var n1=0,n2=0;
        while (n1<sa1.length&&n2<sa2.length) {
            var d=compare(sa1[n1],sa2[n2],str1,str2);
            if(d===1){
                arr.push([1,sa2[n2]])
                n2++;
            }else if(d===-1){
                arr.push([0,sa1[n1]])
                n1++
            }else{
                arr.push([0,sa1[n1]])
                arr.push([1,sa2[n2]])
                n1++;
                n2++;
            }
            if(n1===sa1.length){
                arr.push([1,sa2[n2]])
            }else if(n2===sa2.length){
                arr.push([0,sa1[n1]])
            }
        }
        //求height数组,获取公共子序列
        var common=[]
        for(var i=0;i<arr.length-1;i++){
            var cur=arr[i]
            var next=arr[i+1]
            if(cur[0]!==next[0]){
                var n
                var m
                if(cur[0]===0){
                    n=cur[1]
                    m=next[1]
                }else{
                    n=next[1]
                    m=cur[1]
                }
                var len=0;
                while(n+len<str1.length&&m+len<str2.length&&str1[n+len]===str2[m+len]){
                    len++;
                }
                if(len>0){
                    common.push([n,m,len]);
                }
            }
        }
        //排序
        common.sort(function (arr1,arr2) {
            return arr1[0]-arr2[0]
        })
        //去掉相交的部分
        var narr1=[]
        for(var i=0;i<common.length;i++){
            if(narr1.length===0){
                narr1.push(common[i])
            }else{
                var last=narr1[narr1.length-1];
                var cur=common[i]
                if(last[0]+last[2]>cur[0]){
                    if(cur[2]>last[2]){
                        last[2]=cur[0]-last[0]
                        if(last[2]>0){
                            narr1.push(cur)
                        }else{
                            last[0]=cur[0]
                            last[1]=cur[1]
                            last[2]=cur[2]
                        }
                    }
                }else{
                    narr1.push(cur)
                }
            }
        }
//排序
        narr1.sort(function (arr1,arr2) {
            return arr1[1]-arr2[1]
        })
        var narr=[]
        for(var i=0;i<narr1.length;i++){
            if(narr.length===0){
                narr.push(narr1[i])
            }else{
                var last=narr[narr.length-1];
                var cur=narr1[i]
                if(last[2]+last[1]>cur[1]){
                    if(cur[2]>last[2]){
                        last[2]=cur[1]-last[1]
                        if(last[2]>0){
                            narr.push(cur)
                        }else{
                            last[0]=cur[0]
                            last[1]=cur[1]
                            last[2]=cur[2]
                        }
                    }
                }else{
                    narr.push(cur)
                }
            }
        }
        return narr;
    }
</script>
<style>
    .container{
        width: 1000px;
        margin: 0 auto;
    }
    .item{
        margin-bottom: 20px;
        vertical-align: middle;
        float: none;
        clear: both;
    }
    .item label{
        width: 100px;
        float: left;
    }
    .item textarea{
        width: 60%;
        min-height: 100px;
    }
</style>
<body>

<div class="container">
    <div class="item">
        后缀数组求公共部分:时间复杂度 2*nlog2N
    </div>
    <div class="item">
        <label>输入字符s1:</label> <textarea id="s1">ill:"071b136b",test:"acf98235",wlh_audit_ok:"0cfbdb93",wl</textarea>
    </div>
    <div class="item">
        <label>输入字符s2:</label> <textarea id='s2'>ill:"acf98235",test:"0cfbdb93",wlh_audit_ok:"0cfbdb93",wl</textarea>
    </div>
    <div class="item">
        <label>s1:</label> <textarea id='chunk'></textarea>
    </div>
    <div class="item">
        <label>s2:</label> <textarea id="chunk2"></textarea>
    </div>
    <div class="item">
        <label>common:</label> <textarea id='common'></textarea>
        <button onClick="s1execChunk()">执行common:</button>
    </div>
    <div id="result"></div>
</div>

</body>
<script>
    function s1execChunk() {
        var str1=document.getElementById('s1').value;
        var str2=document.getElementById('s2').value;
        //获取公共子串的位置和长度,arr[字符1的位置、长度、字符2的位置]
        var narr=getHotCommon(str1,str2);
        console.log(narr)
//字符1的公共子串的位置和长度
        var arr1=[]
        var str1Arr=narr.map(function (arr) {
            arr1.push([arr[0],arr[2]])
            return str1.substr(arr[0],arr[2])
        })
//字符2的公共子串的位置和长度
        var arr2=[]
        var str2Arr=narr.map(function (arr) {
            arr2.push([arr[1],arr[2]])
            return str2.substr(arr[1],arr[2])
        })
        document.getElementById('chunk').innerText=JSON.stringify(arr1);
        document.getElementById('chunk2').innerText=JSON.stringify(arr2);

        document.getElementById('common').innerText=str2Arr.join('');
        document.getElementById('result').innerText='结果为'+(str1Arr.join('')===str2Arr.join(''));
    }
</script>

</html>
原文地址:https://www.cnblogs.com/caoke/p/13248350.html