【问题标题】:How to hash very large substrings quickly without collisions?如何快速散列非常大的子字符串而不会发生冲突?
【发布时间】: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”的调用。只存储感兴趣的子字符串的开始和结束位置会有帮助吗?

标签: c++ algorithm c++11 hash


【解决方案1】:

面对问题X找出所有回文子串),你问怎么解决Y快速散列子串 em>): The XY Problem.
对于回文检测,请考虑后缀数组(一个用于输入的反转,或者附加到输入)。
对于重叠字符串的快速哈希,请查看rolling hashes

【讨论】:

  • 后来,我发现 te7 的评论包含指向 HackerRank 问题的链接:约束不同,第一个示例输出的解释与该输出的第一个语句相矛盾。
  • 感谢您的回复。我很感激。我使用 Murmur3 哈希 64x128 解决了这个问题。我会调查这些链接。谢谢
  • 对于字符串匹配,请尝试 Rabin-Karp 滚动哈希
猜你喜欢
  • 2011-07-20
  • 1970-01-01
  • 2017-06-23
  • 2019-12-18
  • 2010-09-11
  • 2016-10-08
  • 1970-01-01
  • 2015-09-07
  • 2013-07-04
相关资源
最近更新 更多