是否有任何算法可用于查找字符串中最常见的短语(或子字符串)?例如,以下字符串将“hello world”作为其最常见的两个词短语:
"hello world this is hello world. hello world repeats three times in this string!"
在上面的字符串中,最常见的字符串(在空字符串字符之后,重复无数次)是空格字符。
有没有办法在这个字符串中生成一个常见的子字符串列表,从最常见到最不常见?
是否有任何算法可用于查找字符串中最常见的短语(或子字符串)?例如,以下字符串将“hello world”作为其最常见的两个词短语:
"hello world this is hello world. hello world repeats three times in this string!"
在上面的字符串中,最常见的字符串(在空字符串字符之后,重复无数次)是空格字符。
有没有办法在这个字符串中生成一个常见的子字符串列表,从最常见到最不常见?
这与 Nussinov 算法的任务类似,实际上更简单,因为我们不允许对齐中的任何间隙、插入或不匹配。
对于长度为 N 的字符串 A,定义一个F[-1 .. N, -1 .. N]
表格并使用以下规则填写:
for i = 0 to N
for j = 0 to N
if i != j
{
if A[i] == A[j]
F[i,j] = F [i-1,j-1] + 1;
else
F[i,j] = 0;
}
例如,对于BA O BA B:
这是O(n^2)
及时的。表中的最大值现在指向最长的自匹配子序列的结束位置(i - 一次出现的结束,j - 另一个)。一开始,假定数组是零初始化的。我添加了条件来排除最长但可能不有趣的自我匹配对角线。
再想一想,这张表在对角线上是对称的,所以只计算它的一半就足够了。此外,数组是零初始化的,因此分配零是多余的。剩下的
for i = 0 to N
for j = i + 1 to N
if A[i] == A[j]
F[i,j] = F [i-1,j-1] + 1;
更短但可能更难以理解。计算表包含所有匹配项,短匹配和长匹配。您可以根据需要添加进一步的过滤。
在下一步中,您需要从非零单元格向上和向左对角线恢复字符串。在此步骤中,使用一些 hashmap 来计算同一字符串的自相似匹配数也很简单。对于普通字符串和普通最小长度,只有少量表格单元格将通过此映射处理。
我认为直接使用hashmap实际上需要O(n ^ 3),因为必须以某种方式比较访问结束时的键字符串是否相等。这种比较可能是 O(n)。
Python。这有点快速和肮脏,数据结构完成了大部分工作。
from collections import Counter
accumulator = Counter()
text = 'hello world this is hello world.'
for length in range(1,len(text)+1):
for start in range(len(text) - length):
accumulator[text[start:start+length]] += 1
Counter 结构是一个哈希支持的字典,旨在计算您看到某物的次数。添加到不存在的密钥将创建它,而检索不存在的密钥将为您提供零而不是错误。因此,您所要做的就是遍历所有子字符串。
只是伪代码,也许这不是最漂亮的解决方案,但我会这样解决:
function separateWords(String incomingString) returns StringArray{
//Code
}
function findMax(Map map) returns String{
//Code
}
function mainAlgorithm(String incomingString) returns String{
StringArray sArr = separateWords(incomingString);
Map<String, Integer> map; //init with no content
for(word: sArr){
Integer count = map.get(word);
if(count == null){
map.put(word,1);
} else {
//remove if neccessary
map.put(word,count++);
}
}
return findMax(map);
}
其中 map 可以包含一个键值对,如 Java HashMap 中的。
Perl,O(n²)
解决方案
my $str = "hello world this is hello world. hello world repeats three times in this string!";
my @words = split(/[^a-z]+/i, $str);
my ($display,$ix,$i,%ocur) = 10;
# calculate
for ($ix=0 ; $ix<=$#words ; $ix++) {
for ($i=$ix ; $i<=$#words ; $i++) {
$ocur{ join(':', @words[$ix .. $i]) }++;
}
}
# display
foreach (sort { my $c = $ocur{$b} <=> $ocur{$a} ; return $c ? $c : split(/:/,$b)-split(/:/,$a); } keys %ocur) {
print "$_: $ocur{$_}\n";
last if !--$display;
}
显示最常见子字符串的 10 个最佳分数(如果出现平局,首先显示最长的单词链)。改为只有结果$display
。有迭代。1
n(n+1)/2
由于对于长度 >= 2 的字符串的每个子字符串,文本至少包含一个长度为 2 的子字符串,因此我们只需要研究长度为 2 的子字符串。
val s = "hello world this is hello world. hello world repeats three times in this string!"
val li = s.sliding (2, 1).toList
// li: List[String] = List(he, el, ll, lo, "o ", " w", wo, or, rl, ld, "d ", " t", th, hi, is, "s ", " i", is, "s ", " h", he, el, ll, lo, "o ", " w", wo, or, rl, ld, d., ". ", " h", he, el, ll, lo, "o ", " w", wo, or, rl, ld, "d ", " r", re, ep, pe, ea, at, ts, "s ", " t", th, hr, re, ee, "e ", " t", ti, im, me, es, "s ", " i", in, "n ", " t", th, hi, is, "s ", " s", st, tr, ri, in, ng, g!)
val uniques = li.toSet
uniques.toList.map (u => li.count (_ == u))
// res18: List[Int] = List(1, 2, 1, 1, 3, 1, 5, 1, 1, 3, 1, 1, 3, 2, 1, 3, 1, 3, 2, 3, 1, 1, 1, 1, 1, 3, 1, 3, 3, 1, 3, 1, 1, 1, 3, 3, 2, 4, 1, 2, 2, 1)
uniques.toList(6)
res19: String = "s "