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

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

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

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

空间换时间版本

 /*
利用后缀数组获取两个字符串最长公共子字符串
空间换时间版本
@params
s1 String,要分析的字符串
s2 String,要分析的字符串
norepeat Boolean,是否对结果进行去重,默认true
*/
function commonSubString(s1, s2, norepeat){
var norepeat = norepeat == undefined ? true : norepeat,
array = suffixArray(s1, 1).concat(suffixArray(s2, 2)),
maxLength = 0,
maxStrings = [],
tempLength = 0,
i = 0,
length = 0,
result = {}; //排序,根据字符串排序,直接比较即可
array.sort(function(s1, s2){
return (s1.s == s2.s) ? 0 : (s1.s > s2.s) ? 1 : -1;
}); //寻找最长公共子字符串
for(i = 0, length = array.length - 1; i < length; i++){
tempLength = commonLength(array[i].s, array[i+1].s);
if(array[i].g != array[i+1].g){
if(maxLength == tempLength){
maxStrings.push(array[i]);
}
if(maxLength < tempLength){
maxLength = tempLength;
maxStrings = [];
maxStrings.push(array[i]);
}
}
} //构造结果
result.length = maxLength;
result.contents = [];
for(i in maxStrings){
result.contents.push(maxStrings[i].s.substring(0, maxLength));
} //去重
if(norepeat){
result.contents = norepeatArray(result.contents);
} return result; /*
获取字符串的后缀数组
*/
function suffixArray(s, g){
var array = [],
i = 0,
length = s.length;
for(i = 0; i < length; i++){
array.push({
s: s.substring(i),
g: g //加分组是为了保证最长公共子字符串分别来自两个字符串
});
} return array;
}
/*
获取最大匹配长度
*/
function commonLength(s1, s2){
var slength = s1.length > s2.length ? s2.length : s1.length,
i = 0; //循环次数=较短的字符串长度
for(i = 0; i < slength; i++){
//逐位比较
if(s1.charAt(i) != s2.charAt(i)){
break;
}
} return i;
} /*
字符串数组去重,不会影响原数组,返回一个新数组
*/
function norepeatArray(array){
var _array = array.slice(0),
map = {},
i = 0,
key = ""; //将内容作为散列的key
for(i in _array){
map[_array[i]] = 1;
} //提取散列key,重新填充到数组
_array.splice(0, _array.length);
for(key in map){
_array.push(key);
} return _array;
}
}

时间换空间版本

 /*
利用后缀数组获取两个字符串最长公共子字符串
时间换空间版本
@params
s1 String,要分析的字符串
s2 String,要分析的字符串
norepeat Boolean,是否对结果进行去重,默认true
*/
function commonSubStringPro(s1, s2, norepeat){
var norepeat = norepeat == undefined ? true : norepeat,
array = suffixArray(s1, 1).concat(suffixArray(s2, 2)),
maxLength = 0,
maxStrings = [],
tempLength = 0,
i = 0,
length = 0,
result = {}; //排序,根据实际内容排序,不能根据指针排序
array.sort(function(s1, s2){
var ts1 = s1.str.substring(s1.index),
ts2 = s2.str.substring(s2.index);
return (ts1 == ts2) ? 0 : (ts1 > ts2) ? 1 : -1;
}); //寻找最长公共子字符串
for(i = 0, length = array.length - 1; i < length; i++){
tempLength = commonLength(array[i], array[i+1]);
if(array[i].group != array[i+1].group){
if(maxLength == tempLength){
maxStrings.push(array[i]);
}
if(maxLength < tempLength){
maxLength = tempLength;
maxStrings = [];
maxStrings.push(array[i]);
}
}
} //构造结果
result.length = maxLength;
result.contents = [];
for(i in maxStrings){
result.contents.push(maxStrings[i].str.substr(maxStrings[i].index, maxLength));
} //去重
if(norepeat){
result.contents = norepeatArray(result.contents);
} return result; /*
获取字符串的后缀数组
只存指针,不存实际内容
*/
function suffixArray(s, g){
var array = [],
i = 0,
length = 0;
for(i = 0, length = s.length; i < length; i++){
array.push({
index: i,
str: s, //这里仅仅存放的是字符串指针,不会创建多个副本
group: g //加分组是为了保证最长公共子字符串分别来自两个字符串
});
} return array;
}
/*
获取最大匹配长度
*/
function commonLength(s1, s2){
var slength = 0,
i = 0; //循环次数=较短的字符串长度
slength = (s1.str.length - s1.index) > (s2.str.length - s2.index) ? (s2.str.length - s2.index) : (s1.str.length - s1.index);
for(i = 0; i < slength; i++){
//逐位比较
if(s1.str.substr(i + s1.index, 1) != s2.str.substr(i + s2.index, 1)){
break;
}
} return i;
} /*
字符串数组去重,不会影响原数组,返回一个新数组
*/
function norepeatArray(array){
var _array = array.slice(0),
map = {},
i = 0,
key = ""; //将内容作为散列的key
for(i in _array){
map[_array[i]] = 1;
} //提取散列key,重新填充到数组
_array.splice(0, _array.length);
for(key in map){
_array.push(key);
} return _array;
}
}

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

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

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

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

使用后缀数组寻找最长公共子字符串JavaScript版的更多相关文章

  1. hdu 1403 Longest Common Substring(最长公共子字符串)(后缀数组)

    http://acm.hdu.edu.cn/showproblem.php?pid=1403 Longest Common Substring Time Limit: 8000/4000 MS (Ja ...

  2. HDU 1403 Longest Common Substring(后缀数组,最长公共子串)

    hdu题目 poj题目 参考了 罗穗骞的论文<后缀数组——处理字符串的有力工具> 题意:求两个序列的最长公共子串 思路:后缀数组经典题目之一(模版题) //后缀数组sa:将s的n个后缀从小 ...

  3. poj2774 Long Long Message 后缀数组求最长公共子串

    题目链接:http://poj.org/problem?id=2774 这是一道很好的后缀数组的入门题目 题意:给你两个字符串,然后求这两个的字符串的最长连续的公共子串 一般用后缀数组解决的两个字符串 ...

  4. URAL 1517 Freedom of Choice(后缀数组,最长公共字串)

    题目 输出最长公共字串 #define maxn 200010 int wa[maxn],wb[maxn],wv[maxn],ws[maxn]; int cmp(int *r,int a,int b, ...

  5. Palindrome--poj 1159(最长公共子字符串+滚动数字)

    http://poj.org/problem?id=1159 题目大意:  给你一个n  代表n个字符   第二行给你一个字符串  求使这个字符串变成回文字符串 最少需要添加多少个字符 分析:   原 ...

  6. poj2774 后缀数组 求最长公共子串

    Reference:IOI2009论文 http://www.cnblogs.com/ziyi--caolu/p/3192731.html #include "stdio.h" # ...

  7. Long Long Message (poj2774 后缀数组求最长公共子串)

    Long Long Message Time Limit: 4000MS   Memory Limit: 131072K Total Submissions: 19206   Accepted: 79 ...

  8. POJ 2774 后缀数组:查找最长公共子

    思考:其实很easy.就在两个串在一起.通过一个特殊字符,中间分隔,然后找到后缀数组的最长的公共前缀.然后在两个不同的串,最长是最长的公共子串. 注意的是:用第一个字符串来推断是不是在同一个字符中,刚 ...

  9. poj 1743 男人八题之后缀数组求最长不可重叠最长重复子串

    Musical Theme Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 14874   Accepted: 5118 De ...

随机推荐

  1. 从多列的DataTable里取需要的几列(转)

    方法一: 也是广为人知的一种: YourDataTable.Columns.Remove("列名"); 但是这种情况只适合于去掉很少列的情况. 如果有很多列我却只要一两列呢,那就得 ...

  2. Zip压缩和解压缩

    这个功能完全依靠一个第三方的类,ICSharpCode.SharpZipLib.dll,只是在网上搜了大半天,都没有关于这个类的详细解释,搜索的demo也是各种错误,感觉作者完全没有跑过,就那么贸贸然 ...

  3. 解决拖拽有内容的div的bug和兼容问题

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 31.0px Consolas; color: #2b7ec3 } p.p2 { margin: 0.0px ...

  4. 【洛谷P1196】银河英雄传说

    有特殊意义的一道题-- 加权并查集,我们增加cnt.deep数组 分别表示i点所在链共有多少个点,以及路径压缩之前点i在链中的深度 每次合并时直接修改cnt,路径压缩的同时更新deep 因为每次查询之 ...

  5. Redis多机常用架构-sentinel

    哨兵经典架构 sentinel结构 哨兵工作原理 Sentinel 通过配置文件发现Master,如下: Sentinel 通过向Master发送 INFO 命令来自动获得所有Slave的地址 跟Ma ...

  6. js 字符串转换成数字(转)

    转换函数: js提供了parseInt()和parseFloat()两个转换函数.前者把值转换成整数,后者把值转换成浮点数.只有对String类型调用这些方法,这两个函数才能正确运行:对其他类型返回的 ...

  7. 批量创建SQL Server分区文件

    ) declare @i int set @table = 'v3_yqsd_report' begin exec('alter database '+@table+' add filegroup O ...

  8. 无法打开注册表项 unknown 没有足够的权限访问

    secedit /configure /cfg %windir%\inf\defltbase.inf /db defltbase.sdb /verbose 执行完,重新安装即可.

  9. Activity设置全屏的三种方法

    1.super.onCreate(savedInstanceState)方法之前调用:            setTheme(android.R.style.Theme_Light_NoTitleB ...

  10. Win8.1下FreeImage.lib库的配置和简单使用(转)

    转自http://www.codeweblog.com/win8-1下freeimage-lib库的配置和简单使用/ 首先,你可以从这里获取FreeImage的完整库文件(访问密码 9a5e). 配置 ...