【发布时间】:2017-03-15 08:30:21
【问题描述】:
我有一个应用程序,它作为它的一部分查找输入字符串的所有回文子字符串。输入字符串的长度可达 100,000,因此子字符串可能非常大。例如,应用程序的一个输入导致超过 300,000 个长度超过 10,000 的子串回文。该应用程序稍后计算所有回文是否相等,并通过使用标准哈希的哈希计算唯一的回文,该标准哈希在查找回文的函数中完成。哈希值存储在一个向量中,然后在应用程序中计算其唯一性。这种输入和输出条件的问题是非常大的子字符串的散列需要太长时间,而且在散列中会发生冲突。所以我想知道是否有一种算法(散列)可以快速且唯一地散列一个非常大的子字符串(最好通过子字符串的索引范围来提高速度,但具有唯一性的准确性)。散列在函数 get_palins 的末尾完成。代码如下。
#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <map>
#include <cstdio>
#include <cmath>
#include <ctgmath>
using namespace std;
#define MAX 100000
#define mod 1000000007
vector<long long> palins[MAX+5];
// Finds all palindromes for the string
void get_palins(string &s)
{
int N = s.length();
int i, j, k, // iterators
rp, // length of 'palindrome radius'
R[2][N+1]; // table for storing results (2 rows for odd- and even-length palindromes
s = "@" + s + "#"; // insert 'guards' to iterate easily over s
for(j = 0; j <= 1; j++)
{
R[j][0] = rp = 0; i = 1;
while(i <= N)
{
while(s[i - rp - 1] == s[i + j + rp]) { rp++; }
R[j][i] = rp;
k = 1;
while((R[j][i - k] != rp - k) && (k < rp))
{
R[j][i + k] = min(R[j][i - k],rp - k);
k++;
}
rp = max(rp - k,0);
i += k;
}
}
s = s.substr(1,N); // remove 'guards'
for(i = 1; i <= N; i++)
{
for(j = 0; j <= 1; j++)
for(rp = R[j][i]; rp > 0; rp--)
{
int begin = i - rp - 1;
int end_count = 2 * rp + j;
int end = begin + end_count - 1;
if (!(begin == 0 && end == N -1 ))
{
string ss = s.substr(begin, end_count);
long long hsh = hash<string>{}(ss);
palins[begin].push_back(hsh);
}
}
}
}
unordered_map<long long, int> palin_counts;
unordered_map<char, int> end_matches;
// Solve when at least 1 character in string is different
void solve_all_not_same(string &s)
{
int n = s.length();
long long count = 0;
get_palins(s);
long long palin_count = 0;
// Gets all palindromes into unordered map
for (int i = 0; i <= n; i++)
{
for (auto& it : palins[i])
{
if (palin_counts.find(it) == palin_counts.end())
{
palin_counts.insert({it,1});
}
else
{
palin_counts[it]++;
}
}
}
// From total palindromes, get proper border count
// minus end characters of substrings
for ( auto it = palin_counts.begin(); it != palin_counts.end(); ++it )
{
int top = it->second - 1;
palin_count += (top * (top + 1)) / 2;
palin_count %= mod;
}
// Store string character counts in unordered map
for (int i = 0; i <= n; i++)
{
char c = s[i];
//long long hsh = hash<char>{}(c);
if (end_matches[c] == 0)
end_matches[c] = 1;
else
end_matches[c]++;
}
// From substring end character matches, get proper border count
// for end characters of substrings
for ( auto it = end_matches.begin(); it != end_matches.end(); it++ )
{
int f = it->second - 1;
count += (f * (f + 1)) / 2;
}
cout << (count + palin_count) % mod << endl;
for (int i = 0; i < MAX+5; i++)
palins[i].clear();
}
int main()
{
string s;
cin >> s;
solve_all_not_same(s);
return 0;
}
【问题讨论】:
-
你确定它只是这里的瓶颈吗?仅仅通过扫描上面的代码,我就看到了很多低效的东西。例如:为已经很大的字符串添加后缀和前缀以及大量额外的子字符串副本,我不确定,但如果您使用一对指示字符串中开始和结束位置的值,则绝对可以避免。
-
另外,
R[2][N+1]不是标准 C++。它可能适用于您的平台... -
stackoverflow.com/questions/98153/… 可能是一个可能的解决方案。此外,如果您可以添加一些智能(我们在 rabin-karp 中所做的更新哈希),您可能会获得巨大的加速。
-
@Arunmu 该代码用于检查子字符串的正确回文边界。这就是前缀和后缀计算的原因。我需要通过哈希识别每个唯一的回文。在代码中,它稍后会更新由向量中的散列映射的无序。我首先尝试使用无序地图而不是矢量,但由于插入地图需要查找重复项,因此我没有看到性能改进。 R[2][N+1] 也适用于 Eclipse,但不适用于 Visual Studio。感谢您提供哈希帖子的链接。我在想瓶颈是对字符串“substr”的调用。
-
> 我在想瓶颈是对字符串“substr”的调用。只存储感兴趣的子字符串的开始和结束位置会有帮助吗?