zoukankan      html  css  js  c++  java
  • 使用后缀数组寻找最长公共子字符串JavaScript版

         后缀数组很久很久以前就出现了,具体的概念读者自行搜索,小菜仅略知一二,不便讨论。

         本文通过寻找两个字符串的最长公共子字符串,演示了后缀数组的经典应用。

         首先需要说明,小菜实现的这个后缀数组算法,并非标准,只是借鉴了其中的思想。

         小菜实现的算法,有两个版本,第一个是空间换时间,第二个是时间换空间。

    空间换时间版本

      1 /*
      2   利用后缀数组获取两个字符串最长公共子字符串
      3   空间换时间版本
      4   @params
      5     s1 String,要分析的字符串
      6     s2 String,要分析的字符串
      7     norepeat Boolean,是否对结果进行去重,默认true
      8 */
      9 function commonSubString(s1, s2, norepeat){
     10   var norepeat = norepeat == undefined ? true : norepeat,
     11       array = suffixArray(s1, 1).concat(suffixArray(s2, 2)),
     12       maxLength = 0,
     13       maxStrings = [],
     14       tempLength = 0,
     15       i = 0,
     16       length = 0,
     17       result = {};
     18   
     19   //排序,根据字符串排序,直接比较即可
     20   array.sort(function(s1, s2){
     21     return (s1.s == s2.s) ? 0 : (s1.s > s2.s) ? 1 : -1;
     22   });
     23   
     24   //寻找最长公共子字符串
     25   for(i = 0, length = array.length - 1; i < length; i++){
     26     tempLength = commonLength(array[i].s, array[i+1].s);
     27     if(array[i].g != array[i+1].g){
     28       if(maxLength == tempLength){
     29         maxStrings.push(array[i]);
     30       }
     31       if(maxLength < tempLength){
     32         maxLength = tempLength;
     33         maxStrings = [];
     34         maxStrings.push(array[i]);
     35       }
     36     }
     37   }
     38   
     39   //构造结果
     40   result.length = maxLength;
     41   result.contents = [];
     42   for(i in maxStrings){
     43     result.contents.push(maxStrings[i].s.substring(0, maxLength));
     44   }
     45   
     46   //去重
     47   if(norepeat){
     48     result.contents  = norepeatArray(result.contents);
     49   }
     50   
     51   return result;
     52   
     53   /*
     54     获取字符串的后缀数组
     55   */
     56   function suffixArray(s, g){
     57     var array = [],
     58         i = 0,
     59         length = s.length;
     60     for(i = 0; i < length; i++){
     61       array.push({
     62         s: s.substring(i),
     63         g: g   //加分组是为了保证最长公共子字符串分别来自两个字符串
     64       });
     65     }
     66     
     67     return array;
     68   }
     69   /*
     70     获取最大匹配长度
     71   */
     72   function commonLength(s1, s2){
     73     var slength = s1.length > s2.length ? s2.length : s1.length,
     74         i = 0;
     75     
     76     //循环次数=较短的字符串长度
     77     for(i = 0; i < slength; i++){
     78       //逐位比较
     79       if(s1.charAt(i) != s2.charAt(i)){
     80         break;
     81       }
     82     }
     83     
     84     return i;
     85   }
     86   
     87   /*
     88     字符串数组去重,不会影响原数组,返回一个新数组
     89   */
     90   function norepeatArray(array){
     91     var _array = array.slice(0),
     92         map = {},
     93         i = 0,
     94         key = "";
     95     
     96     //将内容作为散列的key
     97     for(i in _array){
     98       map[_array[i]] = 1;
     99     }
    100     
    101     //提取散列key,重新填充到数组
    102     _array.splice(0, _array.length);
    103     for(key in map){
    104       _array.push(key);
    105     }
    106     
    107     return _array;
    108   }
    109 }
    View Code

    时间换空间版本

      1 /*
      2   利用后缀数组获取两个字符串最长公共子字符串
      3   时间换空间版本
      4   @params
      5     s1 String,要分析的字符串
      6     s2 String,要分析的字符串
      7     norepeat Boolean,是否对结果进行去重,默认true
      8 */
      9 function commonSubStringPro(s1, s2, norepeat){
     10   var norepeat = norepeat == undefined ? true : norepeat,
     11       array = suffixArray(s1, 1).concat(suffixArray(s2, 2)),
     12       maxLength = 0,
     13       maxStrings = [],
     14       tempLength = 0,
     15       i = 0,
     16       length = 0,
     17       result = {};
     18   
     19   //排序,根据实际内容排序,不能根据指针排序
     20   array.sort(function(s1, s2){
     21     var ts1 = s1.str.substring(s1.index),
     22         ts2 = s2.str.substring(s2.index);
     23     return (ts1 == ts2) ? 0 : (ts1 > ts2) ? 1 : -1;
     24   });
     25   
     26   //寻找最长公共子字符串
     27   for(i = 0, length = array.length - 1; i < length; i++){
     28     tempLength = commonLength(array[i], array[i+1]);
     29     if(array[i].group != array[i+1].group){
     30       if(maxLength == tempLength){
     31         maxStrings.push(array[i]);
     32       }
     33       if(maxLength < tempLength){
     34         maxLength = tempLength;
     35         maxStrings = [];
     36         maxStrings.push(array[i]);
     37       }
     38     }
     39   }
     40   
     41   //构造结果
     42   result.length = maxLength;
     43   result.contents = [];
     44   for(i in maxStrings){
     45     result.contents.push(maxStrings[i].str.substr(maxStrings[i].index, maxLength));
     46   }
     47   
     48   //去重
     49   if(norepeat){
     50     result.contents  = norepeatArray(result.contents);
     51   }
     52   
     53   return result;
     54   
     55   /*
     56     获取字符串的后缀数组
     57     只存指针,不存实际内容
     58   */
     59   function suffixArray(s, g){
     60     var array = [],
     61         i = 0,
     62         length = 0;
     63     for(i = 0, length = s.length; i < length; i++){
     64       array.push({
     65         index: i,
     66         str: s,    //这里仅仅存放的是字符串指针,不会创建多个副本
     67         group: g   //加分组是为了保证最长公共子字符串分别来自两个字符串
     68       });
     69     }
     70     
     71     return array;
     72   }
     73   /*
     74     获取最大匹配长度
     75   */
     76   function commonLength(s1, s2){
     77     var slength = 0,
     78         i = 0;
     79     
     80     //循环次数=较短的字符串长度
     81     slength = (s1.str.length - s1.index) > (s2.str.length - s2.index) ? (s2.str.length - s2.index) : (s1.str.length - s1.index);
     82     for(i = 0; i < slength; i++){
     83       //逐位比较
     84       if(s1.str.substr(i + s1.index, 1) != s2.str.substr(i  + s2.index, 1)){
     85         break;
     86       }
     87     }
     88     
     89     return i;
     90   }
     91   
     92   /*
     93     字符串数组去重,不会影响原数组,返回一个新数组
     94   */
     95   function norepeatArray(array){
     96     var _array = array.slice(0),
     97         map = {},
     98         i = 0,
     99         key = "";
    100     
    101     //将内容作为散列的key
    102     for(i in _array){
    103       map[_array[i]] = 1;
    104     }
    105     
    106     //提取散列key,重新填充到数组
    107     _array.splice(0, _array.length);
    108     for(key in map){
    109       _array.push(key);
    110     }
    111     
    112     return _array;
    113   }
    114 }
    View Code

         为啥会有两个版本呢?小菜原本只写了空间换时间版本,这个版本实现复杂度低,但是有一个明显的弊端,它占用了太多无谓的内存,分析数据量不大的时候,可以完美胜任,一旦数据量达到一定程度,它表现出来的不仅仅是执行时间变长,而是根本无法运行,除非有足够大的内存。

         基于以上思考,小菜发现在生成后缀数组的时候,根本没必要保存实际字符串,只需记录位置信息即可,这样一来,内存中的大量字符串,均变成一个个整型数值,在做比较的时候,我们甚至不需要还原字符串,直接用位置去截取单个字符即可,最终内存极大节省,这就是时间换空间版本。

         通过时间换空间,带来的不仅仅是节省内存,而是一种质的变化,从不可能变成可能,现在,无论有多大的数据量,只需很小一部分内存,即可支持程序运转,就算运行时间再长,它也是可行的,不会直接崩溃。当然,现在的CPU运行速度已经很快了。

         希望对读者有所启发,至于具体代码,就不多说了,注释很详细。

  • 相关阅读:
    常见的MYSQL高可用解决方案
    CDN——到底用还是不用?
    Maven学习总结
    Git – Fast Forward 和 no fast foward
    Spring boot 打成jar包问题总结
    Spring Data JPA进阶——Specifications和Querydsl
    Arp攻击实战
    crontab命令
    mtr命令
    如何使用qperf来衡量网络带宽和延迟性能?
  • 原文地址:https://www.cnblogs.com/iyangyuan/p/4470957.html
Copyright © 2011-2022 走看看